From 5443447eb2efa1dd48b2fc52824ceeb0ff5164a5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 14:42:59 -0700 Subject: [PATCH 001/111] Instead of maintaining queue for invalidated projects, use the pendingSet and graph queue --- src/compiler/tsbuild.ts | 70 ++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 8bc66e95edc..41031d34fc7 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -411,8 +411,6 @@ namespace ts { const diagnostics = createFileMap>(toPath); const projectPendingBuild = createFileMap(toPath); const projectErrorsReported = createFileMap(toPath); - const invalidatedProjectQueue = [] as ResolvedConfigFileName[]; - let nextProjectToBuild = 0; let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); @@ -456,8 +454,6 @@ namespace ts { diagnostics.clear(); projectPendingBuild.clear(); projectErrorsReported.clear(); - invalidatedProjectQueue.length = 0; - nextProjectToBuild = 0; if (timerToBuildInvalidatedProject) { clearTimeout(timerToBuildInvalidatedProject); timerToBuildInvalidatedProject = undefined; @@ -498,8 +494,8 @@ namespace ts { } function startWatching() { - const graph = getGlobalDependencyGraph(); - for (const resolved of graph.buildQueue) { + const { buildQueue } = getGlobalDependencyGraph(); + for (const resolved of buildQueue) { // Watch this file watchConfigFile(resolved); @@ -855,10 +851,10 @@ namespace ts { } function invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel) { - invalidateResolvedProject(resolveProjectName(configFileName), reloadLevel); + invalidateResolvedProject(resolveProjectName(configFileName), reloadLevel || ConfigFileProgramReloadLevel.None); } - function invalidateResolvedProject(resolved: ResolvedConfigFileName, reloadLevel?: ConfigFileProgramReloadLevel) { + function invalidateResolvedProject(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { if (reloadLevel === ConfigFileProgramReloadLevel.Full) { configFileCache.removeKey(resolved); globalDependencyGraph = undefined; @@ -872,28 +868,24 @@ namespace ts { /** * return true if new addition */ - function addProjToQueue(proj: ResolvedConfigFileName, reloadLevel?: ConfigFileProgramReloadLevel) { + function addProjToQueue(proj: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { const value = projectPendingBuild.getValue(proj); if (value === undefined) { - projectPendingBuild.setValue(proj, reloadLevel || ConfigFileProgramReloadLevel.None); - invalidatedProjectQueue.push(proj); + projectPendingBuild.setValue(proj, reloadLevel); } - else if (value < (reloadLevel || ConfigFileProgramReloadLevel.None)) { - projectPendingBuild.setValue(proj, reloadLevel || ConfigFileProgramReloadLevel.None); + else if (value < reloadLevel) { + projectPendingBuild.setValue(proj, reloadLevel); } } function getNextInvalidatedProject() { - if (nextProjectToBuild < invalidatedProjectQueue.length) { - const project = invalidatedProjectQueue[nextProjectToBuild]; - nextProjectToBuild++; - const reloadLevel = projectPendingBuild.getValue(project)!; - projectPendingBuild.removeKey(project); - if (!projectPendingBuild.getSize()) { - invalidatedProjectQueue.length = 0; - nextProjectToBuild = 0; + const { buildQueue } = getGlobalDependencyGraph(); + for (const project of buildQueue) { + const reloadLevel = projectPendingBuild.getValue(project); + if (reloadLevel !== undefined) { + projectPendingBuild.removeKey(project); + return { project, reloadLevel }; } - return { project, reloadLevel }; } } @@ -990,15 +982,20 @@ namespace ts { updateBundle(resolved); // Fake that files have been built by manipulating prepend and existing output if (buildResult & BuildResultFlags.AnyErrors) return; - const { referencingProjectsMap, buildQueue } = getGlobalDependencyGraph(); - const referencingProjects = referencingProjectsMap.getValue(resolved); - if (!referencingProjects) return; + // Only composite projects can be referenced by other projects + if (!proj.options.composite) return; + const { buildQueue } = getGlobalDependencyGraph(); // Always use build order to queue projects for (let index = buildQueue.indexOf(resolved) + 1; index < buildQueue.length; index++) { const project = buildQueue[index]; - const prepend = referencingProjects.getValue(project); - if (prepend !== undefined) { + if (projectPendingBuild.hasKey(project)) continue; + + const config = parseConfigFile(project); + if (!config || !config.projectReferences) continue; + for (const ref of config.projectReferences) { + const resolvedRefPath = resolveProjectName(ref.path); + if (resolvedRefPath !== resolved) 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 @@ -1013,7 +1010,7 @@ namespace ts { } } else if (status && status.type === UpToDateStatusType.UpToDate) { - if (prepend) { + if (ref.prepend) { projectStatus.setValue(project, { type: UpToDateStatusType.OutOfDateWithPrepend, outOfDateOutputFileName: status.oldestOutputFileName, @@ -1024,7 +1021,8 @@ namespace ts { status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; } } - addProjToQueue(project); + addProjToQueue(project, ConfigFileProgramReloadLevel.None); + break; } } } @@ -1341,9 +1339,9 @@ namespace ts { function getFilesToClean(): string[] { // Get the same graph for cleaning we'd use for building - const graph = getGlobalDependencyGraph(); + const { buildQueue } = getGlobalDependencyGraph(); const filesToDelete: string[] = []; - for (const proj of graph.buildQueue) { + for (const proj of buildQueue) { const parsed = parseConfigFile(proj); if (parsed === undefined) { // File has gone missing; fine to ignore here @@ -1403,10 +1401,10 @@ namespace ts { loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); } - const graph = getGlobalDependencyGraph(); - reportBuildQueue(graph); + const { buildQueue } = getGlobalDependencyGraph(); + reportBuildQueue(buildQueue); let anyFailed = false; - for (const next of graph.buildQueue) { + for (const next of buildQueue) { const proj = parseConfigFile(next); if (proj === undefined) { reportParseConfigFileDiagnostic(next); @@ -1494,9 +1492,9 @@ namespace ts { /** * Report the build ordering inferred from the current project graph if we're in verbose mode */ - function reportBuildQueue(graph: DependencyGraph) { + function reportBuildQueue(buildQueue: readonly ResolvedConfigFileName[]) { if (options.verbose) { - reportStatus(Diagnostics.Projects_in_this_build_Colon_0, graph.buildQueue.map(s => "\r\n * " + relName(s)).join("")); + reportStatus(Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(s)).join("")); } } From 1de70de09917f18248eafa920405ed4d196e712b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 15:31:09 -0700 Subject: [PATCH 002/111] No need to calculate and store project references graph --- src/compiler/tsbuild.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 41031d34fc7..81d382a4c6d 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -13,8 +13,6 @@ namespace ts { interface DependencyGraph { buildQueue: ResolvedConfigFileName[]; - /** value in config File map is true if project is referenced using prepend */ - referencingProjectsMap: ConfigFileMap>; } export interface BuildOptions extends OptionsBase { @@ -1032,14 +1030,12 @@ namespace ts { const permanentMarks = createFileMap(toPath); const circularityReportStack: string[] = []; const buildOrder: ResolvedConfigFileName[] = []; - const referencingProjectsMap = createFileMap>(toPath); for (const root of roots) { visit(root); } return { buildQueue: buildOrder, - referencingProjectsMap }; function visit(projPath: ResolvedConfigFileName, inCircularContext?: boolean) { @@ -1061,9 +1057,6 @@ namespace ts { for (const ref of parsed.projectReferences) { const resolvedRefPath = resolveProjectName(ref.path); visit(resolvedRefPath, inCircularContext || ref.circular); - // Get projects referencing resolvedRefPath and add projPath to it - const referencingProjects = getOrCreateValueFromConfigFileMap(referencingProjectsMap, resolvedRefPath, () => createFileMap(toPath)); - referencingProjects.setValue(projPath, !!ref.prepend); } } From b9eeaab050f9b491b2446cf08cc3a91638592554 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 15:32:22 -0700 Subject: [PATCH 003/111] Remove unused variable --- src/compiler/tsbuild.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 81d382a4c6d..12a1436ec63 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -390,7 +390,6 @@ namespace ts { const unchangedOutputs = createFileMap(toPath as ToPath); /** Map from config file name to up-to-date status */ const projectStatus = createFileMap(toPath); - const missingRoots = createMap(); let globalDependencyGraph: DependencyGraph | undefined; const writeFileName = host.trace ? (s: string) => host.trace!(s) : undefined; let readFileWithCache = (f: string) => host.readFile(f); @@ -445,7 +444,6 @@ namespace ts { configFileCache.clear(); unchangedOutputs.clear(); projectStatus.clear(); - missingRoots.clear(); globalDependencyGraph = undefined; buildInfoChecked.clear(); From 3e77b96824b5a11b6f4b216f3016d5f2213129f3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 15:45:45 -0700 Subject: [PATCH 004/111] Fix the graph ordering test case to check actual order and not just members as set --- src/testRunner/unittests/tsbuild/graphOrdering.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/testRunner/unittests/tsbuild/graphOrdering.ts b/src/testRunner/unittests/tsbuild/graphOrdering.ts index bc1af3f71da..bb29892bc61 100644 --- a/src/testRunner/unittests/tsbuild/graphOrdering.ts +++ b/src/testRunner/unittests/tsbuild/graphOrdering.ts @@ -22,19 +22,19 @@ namespace ts { }); it("orders the graph correctly - specify two roots", () => { - checkGraphOrdering(["A", "G"], ["A", "B", "C", "D", "E", "G"]); + checkGraphOrdering(["A", "G"], ["D", "E", "C", "B", "A", "G"]); }); it("orders the graph correctly - multiple parts of the same graph in various orders", () => { - checkGraphOrdering(["A"], ["A", "B", "C", "D", "E"]); - checkGraphOrdering(["A", "C", "D"], ["A", "B", "C", "D", "E"]); - checkGraphOrdering(["D", "C", "A"], ["A", "B", "C", "D", "E"]); + checkGraphOrdering(["A"], ["D", "E", "C", "B", "A"]); + checkGraphOrdering(["A", "C", "D"], ["D", "E", "C", "B", "A"]); + checkGraphOrdering(["D", "C", "A"], ["D", "E", "C", "B", "A"]); }); it("orders the graph correctly - other orderings", () => { - checkGraphOrdering(["F"], ["F", "E"]); + checkGraphOrdering(["F"], ["E", "F"]); checkGraphOrdering(["E"], ["E"]); - checkGraphOrdering(["F", "C", "A"], ["A", "B", "C", "D", "E", "F"]); + checkGraphOrdering(["F", "C", "A"], ["E", "F", "D", "C", "B", "A"]); }); function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) { @@ -43,7 +43,7 @@ namespace ts { const projFileNames = rootNames.map(getProjectFileName); const graph = builder.getBuildGraph(projFileNames); - assert.sameMembers(graph.buildQueue, expectedBuildSet.map(getProjectFileName)); + assert.deepEqual(graph.buildQueue, expectedBuildSet.map(getProjectFileName)); for (const dep of deps) { const child = getProjectFileName(dep[0]); From 845f67a39413e2baea7b065086c9b59e47575ed5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 15:39:25 -0700 Subject: [PATCH 005/111] Make api to return build order --- src/compiler/tsbuild.ts | 55 +++++++------------ .../unittests/tsbuild/graphOrdering.ts | 12 ++-- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 12a1436ec63..f995b8d1b4d 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -11,10 +11,6 @@ namespace ts { message(diag: DiagnosticMessage, ...args: string[]): void; } - interface DependencyGraph { - buildQueue: ResolvedConfigFileName[]; - } - export interface BuildOptions extends OptionsBase { dry?: boolean; force?: boolean; @@ -313,7 +309,7 @@ namespace ts { // TODO:: All the below ones should technically only be in watch mode. but thats for later time /*@internal*/ resolveProjectName(name: string): ResolvedConfigFileName; /*@internal*/ getUpToDateStatusOfFile(configFileName: ResolvedConfigFileName): UpToDateStatus; - /*@internal*/ getBuildGraph(configFileNames: ReadonlyArray): DependencyGraph; + /*@internal*/ getBuildOrder(): ReadonlyArray; /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; /*@internal*/ buildInvalidatedProject(): void; @@ -390,7 +386,7 @@ namespace ts { const unchangedOutputs = createFileMap(toPath as ToPath); /** Map from config file name to up-to-date status */ const projectStatus = createFileMap(toPath); - let globalDependencyGraph: DependencyGraph | undefined; + let buildOrder: readonly ResolvedConfigFileName[] | undefined; const writeFileName = host.trace ? (s: string) => host.trace!(s) : undefined; let readFileWithCache = (f: string) => host.readFile(f); let projectCompilerOptions = baseCompilerOptions; @@ -422,7 +418,7 @@ namespace ts { getUpToDateStatusOfFile, cleanAllProjects, resetBuildContext, - getBuildGraph, + getBuildOrder, invalidateProject, buildInvalidatedProject, @@ -444,7 +440,7 @@ namespace ts { configFileCache.clear(); unchangedOutputs.clear(); projectStatus.clear(); - globalDependencyGraph = undefined; + buildOrder = undefined; buildInfoChecked.clear(); diagnostics.clear(); @@ -490,8 +486,7 @@ namespace ts { } function startWatching() { - const { buildQueue } = getGlobalDependencyGraph(); - for (const resolved of buildQueue) { + for (const resolved of getBuildOrder()) { // Watch this file watchConfigFile(resolved); @@ -615,12 +610,8 @@ namespace ts { return getUpToDateStatus(parseConfigFile(configFileName)); } - function getBuildGraph(configFileNames: ReadonlyArray) { - return createDependencyGraph(resolveProjectNames(configFileNames)); - } - - function getGlobalDependencyGraph() { - return globalDependencyGraph || (globalDependencyGraph = getBuildGraph(rootNames)); + function getBuildOrder() { + return buildOrder || (buildOrder = createBuildOrder(resolveProjectNames(rootNames))); } function getUpToDateStatus(project: ParsedCommandLine | undefined): UpToDateStatus { @@ -853,7 +844,7 @@ namespace ts { function invalidateResolvedProject(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { if (reloadLevel === ConfigFileProgramReloadLevel.Full) { configFileCache.removeKey(resolved); - globalDependencyGraph = undefined; + buildOrder = undefined; } projectStatus.removeKey(resolved); diagnostics.removeKey(resolved); @@ -875,8 +866,7 @@ namespace ts { } function getNextInvalidatedProject() { - const { buildQueue } = getGlobalDependencyGraph(); - for (const project of buildQueue) { + for (const project of getBuildOrder()) { const reloadLevel = projectPendingBuild.getValue(project); if (reloadLevel !== undefined) { projectPendingBuild.removeKey(project); @@ -923,7 +913,7 @@ namespace ts { function reportErrorSummary() { if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) { // Report errors from the other projects - getGlobalDependencyGraph().buildQueue.forEach(project => { + getBuildOrder().forEach(project => { if (!projectErrorsReported.hasKey(project)) { reportErrors(diagnostics.getValue(project) || emptyArray); } @@ -980,11 +970,11 @@ namespace ts { // Only composite projects can be referenced by other projects if (!proj.options.composite) return; - const { buildQueue } = getGlobalDependencyGraph(); + const buildOrder = getBuildOrder(); // Always use build order to queue projects - for (let index = buildQueue.indexOf(resolved) + 1; index < buildQueue.length; index++) { - const project = buildQueue[index]; + for (let index = buildOrder.indexOf(resolved) + 1; index < buildOrder.length; index++) { + const project = buildOrder[index]; if (projectPendingBuild.hasKey(project)) continue; const config = parseConfigFile(project); @@ -1023,18 +1013,16 @@ namespace ts { } } - function createDependencyGraph(roots: ResolvedConfigFileName[]): DependencyGraph { + function createBuildOrder(roots: ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { const temporaryMarks = createFileMap(toPath); const permanentMarks = createFileMap(toPath); const circularityReportStack: string[] = []; - const buildOrder: ResolvedConfigFileName[] = []; + let buildOrder: ResolvedConfigFileName[] | undefined; for (const root of roots) { visit(root); } - return { - buildQueue: buildOrder, - }; + return buildOrder || emptyArray; function visit(projPath: ResolvedConfigFileName, inCircularContext?: boolean) { // Already visited @@ -1060,7 +1048,7 @@ namespace ts { circularityReportStack.pop(); permanentMarks.setValue(projPath, true); - buildOrder.push(projPath); + (buildOrder || (buildOrder = [])).push(projPath); } } @@ -1330,9 +1318,8 @@ namespace ts { function getFilesToClean(): string[] { // Get the same graph for cleaning we'd use for building - const { buildQueue } = getGlobalDependencyGraph(); const filesToDelete: string[] = []; - for (const proj of buildQueue) { + for (const proj of getBuildOrder()) { const parsed = parseConfigFile(proj); if (parsed === undefined) { // File has gone missing; fine to ignore here @@ -1392,10 +1379,10 @@ namespace ts { loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); } - const { buildQueue } = getGlobalDependencyGraph(); - reportBuildQueue(buildQueue); + const buildOrder = getBuildOrder(); + reportBuildQueue(buildOrder); let anyFailed = false; - for (const next of buildQueue) { + for (const next of buildOrder) { const proj = parseConfigFile(next); if (proj === undefined) { reportParseConfigFileDiagnostic(next); diff --git a/src/testRunner/unittests/tsbuild/graphOrdering.ts b/src/testRunner/unittests/tsbuild/graphOrdering.ts index bb29892bc61..a8f4c3a57a9 100644 --- a/src/testRunner/unittests/tsbuild/graphOrdering.ts +++ b/src/testRunner/unittests/tsbuild/graphOrdering.ts @@ -38,18 +38,16 @@ namespace ts { }); function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) { - const builder = createSolutionBuilder(host!, rootNames, { dry: true, force: false, verbose: false }); + const builder = createSolutionBuilder(host!, rootNames.map(getProjectFileName), { dry: true, force: false, verbose: false }); + const buildQueue = builder.getBuildOrder(); - const projFileNames = rootNames.map(getProjectFileName); - const graph = builder.getBuildGraph(projFileNames); - - assert.deepEqual(graph.buildQueue, expectedBuildSet.map(getProjectFileName)); + assert.deepEqual(buildQueue, expectedBuildSet.map(getProjectFileName)); for (const dep of deps) { const child = getProjectFileName(dep[0]); - if (graph.buildQueue.indexOf(child) < 0) continue; + if (buildQueue.indexOf(child) < 0) continue; const parent = getProjectFileName(dep[1]); - assert.isAbove(graph.buildQueue.indexOf(child), graph.buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); + assert.isAbove(buildQueue.indexOf(child), buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); } } From e9d4e0b104307f8055f5e7023de821f1daaca9c1 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 16:04:43 -0700 Subject: [PATCH 006/111] Unchanged output time is no more required --- src/compiler/tsbuild.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f995b8d1b4d..ede9354165d 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -382,8 +382,6 @@ namespace ts { let baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; const configFileCache = createFileMap(toPath); - /** Map from output file name to its pre-build timestamp */ - const unchangedOutputs = createFileMap(toPath as ToPath); /** Map from config file name to up-to-date status */ const projectStatus = createFileMap(toPath); let buildOrder: readonly ResolvedConfigFileName[] | undefined; @@ -438,7 +436,6 @@ namespace ts { options = opts; baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); configFileCache.clear(); - unchangedOutputs.clear(); projectStatus.clear(); buildOrder = undefined; buildInfoChecked.clear(); @@ -697,14 +694,8 @@ namespace ts { // had its file touched but not had its contents changed - this allows us // to skip a downstream typecheck if (isDeclarationFile(output)) { - const unchangedTime = unchangedOutputs.getValue(output); - if (unchangedTime !== undefined) { - newestDeclarationFileContentChangedTime = newer(unchangedTime, newestDeclarationFileContentChangedTime); - } - else { - const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; - newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); - } + const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; + newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); } } @@ -1161,7 +1152,6 @@ namespace ts { writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); if (priorChangeTime !== undefined) { newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); - unchangedOutputs.setValue(name, priorChangeTime); } }); From 92365027a1804c767b63b220b84c1dd350678c1e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 16:22:13 -0700 Subject: [PATCH 007/111] Remove `getUpToDateStatusOfFile` from solution builder since that test anyways is tested with the --watch mode --- src/compiler/tsbuild.ts | 20 +++----- src/testRunner/unittests/tsbuild/sample.ts | 55 ---------------------- 2 files changed, 7 insertions(+), 68 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index ede9354165d..18eaeed188d 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -306,10 +306,13 @@ namespace ts { buildAllProjects(): ExitStatus; cleanAllProjects(): ExitStatus; + // Currently used for testing but can be made public if needed: + /*@internal*/ getBuildOrder(): ReadonlyArray; + + // Testing only + // TODO:: All the below ones should technically only be in watch mode. but thats for later time /*@internal*/ resolveProjectName(name: string): ResolvedConfigFileName; - /*@internal*/ getUpToDateStatusOfFile(configFileName: ResolvedConfigFileName): UpToDateStatus; - /*@internal*/ getBuildOrder(): ReadonlyArray; /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; /*@internal*/ buildInvalidatedProject(): void; @@ -413,7 +416,6 @@ namespace ts { return { buildAllProjects, - getUpToDateStatusOfFile, cleanAllProjects, resetBuildContext, getBuildOrder, @@ -603,12 +605,8 @@ namespace ts { scheduleBuildInvalidatedProject(); } - function getUpToDateStatusOfFile(configFileName: ResolvedConfigFileName): UpToDateStatus { - return getUpToDateStatus(parseConfigFile(configFileName)); - } - function getBuildOrder() { - return buildOrder || (buildOrder = createBuildOrder(resolveProjectNames(rootNames))); + return buildOrder || (buildOrder = createBuildOrder(rootNames.map(resolveProjectName))); } function getUpToDateStatus(project: ParsedCommandLine | undefined): UpToDateStatus { @@ -1004,7 +1002,7 @@ namespace ts { } } - function createBuildOrder(roots: ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { + function createBuildOrder(roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { const temporaryMarks = createFileMap(toPath); const permanentMarks = createFileMap(toPath); const circularityReportStack: string[] = []; @@ -1344,10 +1342,6 @@ namespace ts { return resolveConfigFileProjectName(resolvePath(host.getCurrentDirectory(), name)); } - function resolveProjectNames(configFileNames: ReadonlyArray): ResolvedConfigFileName[] { - return configFileNames.map(resolveProjectName); - } - function buildAllProjects(): ExitStatus { if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } // TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 6e31ada9ee5..2621bd303ea 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -337,61 +337,6 @@ namespace ts { }); }); - describe("project invalidation", () => { - it("invalidates projects correctly", () => { - const fs = projFs.shadow(); - const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); - - builder.buildAllProjects(); - host.assertDiagnosticMessages(/*empty*/); - - // Update a timestamp in the middle project - tick(); - appendText(fs, "/src/logic/index.ts", "function foo() {}"); - const originalWriteFile = fs.writeFileSync; - const writtenFiles = createMap(); - fs.writeFileSync = (path, data, encoding) => { - writtenFiles.set(path, true); - originalWriteFile.call(fs, path, data, encoding); - }; - // Because we haven't reset the build context, the builder should assume there's nothing to do right now - const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic")); - assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); - verifyInvalidation(/*expectedToWriteTests*/ false); - - // Rebuild this project - fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")} -export class cNew {}`); - verifyInvalidation(/*expectedToWriteTests*/ true); - - function verifyInvalidation(expectedToWriteTests: boolean) { - // Rebuild this project - tick(); - builder.invalidateProject("/src/logic"); - builder.buildInvalidatedProject(); - // The file should be updated - assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); - assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); - assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt"); - assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); - writtenFiles.clear(); - - // Build downstream projects should update 'tests', but not 'core' - tick(); - builder.buildInvalidatedProject(); - if (expectedToWriteTests) { - assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); - } - else { - assert.equal(writtenFiles.size, 0, "Should not write any new files"); - } - assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp"); - assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); - } - }); - }); - describe("lists files", () => { it("listFiles", () => { const fs = projFs.shadow(); From b42e76b1e5e1712856190b5d2f5ae6022b344d5f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 16:35:08 -0700 Subject: [PATCH 008/111] Remove usage of fileMap for output unchanged timestamp modification and simplify to use configFileMap --- src/compiler/tsbuild.ts | 43 ++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 18eaeed188d..f119481ac08 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -194,27 +194,22 @@ namespace ts { } } - interface FileMap { - setValue(fileName: U, value: T): void; - getValue(fileName: U): T | undefined; - hasKey(fileName: U): boolean; - removeKey(fileName: U): void; - forEach(action: (value: T, key: V) => void): void; + interface ConfigFileMap { + setValue(fileName: ResolvedConfigFileName, value: T): void; + getValue(fileName: ResolvedConfigFileName): T | undefined; + hasKey(fileName: ResolvedConfigFileName): boolean; + removeKey(fileName: ResolvedConfigFileName): void; + forEach(action: (value: T, key: ResolvedConfigFilePath) => void): void; getSize(): number; clear(): void; } type ResolvedConfigFilePath = ResolvedConfigFileName & Path; - type ConfigFileMap = FileMap; - type ToResolvedConfigFilePath = (fileName: ResolvedConfigFileName) => ResolvedConfigFilePath; - type ToPath = (fileName: string) => Path; /** * A FileMap maintains a normalized-key to value relationship */ - function createFileMap(toPath: ToResolvedConfigFilePath): ConfigFileMap; - function createFileMap(toPath: ToPath): FileMap; - function createFileMap(toPath: (fileName: U) => V): FileMap { + function createFileMap(toPath: (fileName: ResolvedConfigFileName) => ResolvedConfigFilePath): ConfigFileMap { // tslint:disable-next-line:no-null-keyword const lookup = createMap(); @@ -228,23 +223,23 @@ namespace ts { clear }; - function forEach(action: (value: T, key: V) => void) { + function forEach(action: (value: T, key: ResolvedConfigFilePath) => void) { lookup.forEach(action); } - function hasKey(fileName: U) { + function hasKey(fileName: ResolvedConfigFileName) { return lookup.has(toPath(fileName)); } - function removeKey(fileName: U) { + function removeKey(fileName: ResolvedConfigFileName) { lookup.delete(toPath(fileName)); } - function setValue(fileName: U, value: T) { + function setValue(fileName: ResolvedConfigFileName, value: T) { lookup.set(toPath(fileName), value); } - function getValue(fileName: U): T | undefined { + function getValue(fileName: ResolvedConfigFileName): T | undefined { return lookup.get(toPath(fileName)); } @@ -1132,7 +1127,7 @@ namespace ts { // Actual Emit const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createFileMap(toPath as ToPath); + const emittedOutputs = createMap(); outputFiles.forEach(({ name, text, writeByteOrderMark }) => { let priorChangeTime: Date | undefined; if (!anyDtsChanged && isDeclarationFile(name)) { @@ -1146,7 +1141,7 @@ namespace ts { } } - emittedOutputs.setValue(name, name); + emittedOutputs.set(toPath(name), name); writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); if (priorChangeTime !== undefined) { newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); @@ -1235,9 +1230,9 @@ namespace ts { // Actual Emit Debug.assert(!!outputFiles.length); const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createFileMap(toPath as ToPath); + const emittedOutputs = createMap(); outputFiles.forEach(({ name, text, writeByteOrderMark }) => { - emittedOutputs.setValue(name, name); + emittedOutputs.set(toPath(name), name); writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); }); const emitDiagnostics = emitterDiagnostics.getDiagnostics(); @@ -1280,15 +1275,15 @@ namespace ts { projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status); } - function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { + function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: Map) { const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); - if (!skipOutputs || outputs.length !== skipOutputs.getSize()) { + if (!skipOutputs || outputs.length !== skipOutputs.size) { if (options.verbose) { reportStatus(verboseMessage, proj.options.configFilePath!); } const now = host.now ? host.now() : new Date(); for (const file of outputs) { - if (skipOutputs && skipOutputs.hasKey(file)) { + if (skipOutputs && skipOutputs.has(toPath(file))) { continue; } From d4474a5dfca8ee80cd343696e6c3e42cef94fae5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 16:42:56 -0700 Subject: [PATCH 009/111] More refactoring to move file map creation inside solution builder --- src/compiler/tsbuild.ts | 126 +++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f119481ac08..76a93afc478 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -206,51 +206,6 @@ namespace ts { type ResolvedConfigFilePath = ResolvedConfigFileName & Path; - /** - * A FileMap maintains a normalized-key to value relationship - */ - function createFileMap(toPath: (fileName: ResolvedConfigFileName) => ResolvedConfigFilePath): ConfigFileMap { - // tslint:disable-next-line:no-null-keyword - const lookup = createMap(); - - return { - setValue, - getValue, - removeKey, - forEach, - hasKey, - getSize, - clear - }; - - function forEach(action: (value: T, key: ResolvedConfigFilePath) => void) { - lookup.forEach(action); - } - - function hasKey(fileName: ResolvedConfigFileName) { - return lookup.has(toPath(fileName)); - } - - function removeKey(fileName: ResolvedConfigFileName) { - lookup.delete(toPath(fileName)); - } - - function setValue(fileName: ResolvedConfigFileName, value: T) { - lookup.set(toPath(fileName), value); - } - - function getValue(fileName: ResolvedConfigFileName): T | undefined { - return lookup.get(toPath(fileName)); - } - - function getSize() { - return lookup.size; - } - - function clear() { - lookup.clear(); - } - } function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFileName, createT: () => T): T { const existingValue = configFileMap.getValue(resolved); @@ -378,10 +333,11 @@ namespace ts { // State of the solution let options = defaultOptions; let baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); + const resolvedConfigFilePaths = createMap(); type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; - const configFileCache = createFileMap(toPath); + const configFileCache = createFileMap(); /** Map from config file name to up-to-date status */ - const projectStatus = createFileMap(toPath); + const projectStatus = createFileMap(); let buildOrder: readonly ResolvedConfigFileName[] | undefined; const writeFileName = host.trace ? (s: string) => host.trace!(s) : undefined; let readFileWithCache = (f: string) => host.readFile(f); @@ -393,21 +349,21 @@ namespace ts { compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); let moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; - const buildInfoChecked = createFileMap(toPath); + const buildInfoChecked = createFileMap(); // Watch state - const builderPrograms = createFileMap(toPath); - const diagnostics = createFileMap>(toPath); - const projectPendingBuild = createFileMap(toPath); - const projectErrorsReported = createFileMap(toPath); + const builderPrograms = createFileMap(); + const diagnostics = createFileMap>(); + const projectPendingBuild = createFileMap(); + const projectErrorsReported = createFileMap(); let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); // Watches for the solution - const allWatchedWildcardDirectories = createFileMap>(toPath); - const allWatchedInputFiles = createFileMap>(toPath); - const allWatchedConfigFiles = createFileMap(toPath); + const allWatchedWildcardDirectories = createFileMap>(); + const allWatchedInputFiles = createFileMap>(); + const allWatchedConfigFiles = createFileMap(); return { buildAllProjects, @@ -423,15 +379,67 @@ namespace ts { startWatching }; - function toPath(fileName: ResolvedConfigFileName): ResolvedConfigFilePath; - function toPath(fileName: string): Path; function toPath(fileName: string) { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } + function toResolvedConfigFilePath(fileName: ResolvedConfigFileName): ResolvedConfigFilePath { + const path = resolvedConfigFilePaths.get(fileName); + if (path !== undefined) return path; + + const resolvedPath = toPath(fileName) as ResolvedConfigFilePath; + resolvedConfigFilePaths.set(fileName, resolvedPath); + return resolvedPath; + } + + + // TODO remove this and use normal map so we arent transforming paths constantly + function createFileMap(): ConfigFileMap { + const lookup = createMap(); + return { + setValue, + getValue, + removeKey, + forEach, + hasKey, + getSize, + clear + }; + + function forEach(action: (value: T, key: ResolvedConfigFilePath) => void) { + lookup.forEach(action); + } + + function hasKey(fileName: ResolvedConfigFileName) { + return lookup.has(toResolvedConfigFilePath(fileName)); + } + + function removeKey(fileName: ResolvedConfigFileName) { + lookup.delete(toResolvedConfigFilePath(fileName)); + } + + function setValue(fileName: ResolvedConfigFileName, value: T) { + lookup.set(toResolvedConfigFilePath(fileName), value); + } + + function getValue(fileName: ResolvedConfigFileName): T | undefined { + return lookup.get(toResolvedConfigFilePath(fileName)); + } + + function getSize() { + return lookup.size; + } + + function clear() { + lookup.clear(); + } + } + + function resetBuildContext(opts = defaultOptions) { options = opts; baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); + resolvedConfigFilePaths.clear(); configFileCache.clear(); projectStatus.clear(); buildOrder = undefined; @@ -998,8 +1006,8 @@ namespace ts { } function createBuildOrder(roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { - const temporaryMarks = createFileMap(toPath); - const permanentMarks = createFileMap(toPath); + const temporaryMarks = createFileMap(); + const permanentMarks = createFileMap(); const circularityReportStack: string[] = []; let buildOrder: ResolvedConfigFileName[] | undefined; for (const root of roots) { From c615d48727433d09912568285326956bba8d8748 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 16:49:06 -0700 Subject: [PATCH 010/111] Make use of maps in build order calculation --- src/compiler/tsbuild.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 76a93afc478..69da4bed77f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1006,8 +1006,8 @@ namespace ts { } function createBuildOrder(roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { - const temporaryMarks = createFileMap(); - const permanentMarks = createFileMap(); + const temporaryMarks = createMap(); + const permanentMarks = createMap(); const circularityReportStack: string[] = []; let buildOrder: ResolvedConfigFileName[] | undefined; for (const root of roots) { @@ -1016,11 +1016,12 @@ namespace ts { return buildOrder || emptyArray; - function visit(projPath: ResolvedConfigFileName, inCircularContext?: boolean) { + function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { + const projPath = toResolvedConfigFilePath(configFileName); // Already visited - if (permanentMarks.hasKey(projPath)) return; + if (permanentMarks.has(projPath)) return; // Circular - if (temporaryMarks.hasKey(projPath)) { + if (temporaryMarks.has(projPath)) { if (!inCircularContext) { // TODO:: Do we report this as error? reportStatus(Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n")); @@ -1028,9 +1029,9 @@ namespace ts { return; } - temporaryMarks.setValue(projPath, true); - circularityReportStack.push(projPath); - const parsed = parseConfigFile(projPath); + temporaryMarks.set(projPath, true); + circularityReportStack.push(configFileName); + const parsed = parseConfigFile(configFileName); if (parsed && parsed.projectReferences) { for (const ref of parsed.projectReferences) { const resolvedRefPath = resolveProjectName(ref.path); @@ -1039,8 +1040,8 @@ namespace ts { } circularityReportStack.pop(); - permanentMarks.setValue(projPath, true); - (buildOrder || (buildOrder = [])).push(projPath); + permanentMarks.set(projPath, true); + (buildOrder || (buildOrder = [])).push(configFileName); } } From 50741e647be6cea5406b8fb900167d1ab016c66d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 16:59:07 -0700 Subject: [PATCH 011/111] Use maps for all the watch data structures --- src/compiler/tsbuild.ts | 58 +++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 69da4bed77f..cc6899163fc 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -207,17 +207,17 @@ namespace ts { type ResolvedConfigFilePath = ResolvedConfigFileName & Path; - function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFileName, createT: () => T): T { - const existingValue = configFileMap.getValue(resolved); + function getOrCreateValueFromConfigFileMap(configFileMap: Map, resolved: ResolvedConfigFilePath, createT: () => T): T { + const existingValue = configFileMap.get(resolved); let newValue: T | undefined; if (!existingValue) { newValue = createT(); - configFileMap.setValue(resolved, newValue); + configFileMap.set(resolved, newValue); } return existingValue || newValue!; } - function getOrCreateValueMapFromConfigFileMap(configFileMap: ConfigFileMap>, resolved: ResolvedConfigFileName): Map { + function getOrCreateValueMapFromConfigFileMap(configFileMap: Map>, resolved: ResolvedConfigFilePath): Map { return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); } @@ -361,9 +361,9 @@ namespace ts { const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); // Watches for the solution - const allWatchedWildcardDirectories = createFileMap>(); - const allWatchedInputFiles = createFileMap>(); - const allWatchedConfigFiles = createFileMap(); + const allWatchedWildcardDirectories = createMap>(); + const allWatchedInputFiles = createMap>(); + const allWatchedConfigFiles = createMap(); return { buildAllProjects, @@ -489,28 +489,29 @@ namespace ts { function startWatching() { for (const resolved of getBuildOrder()) { + const resolvedPath = toResolvedConfigFilePath(resolved); // Watch this file - watchConfigFile(resolved); + watchConfigFile(resolved, resolvedPath); const cfg = parseConfigFile(resolved); if (cfg) { // Update watchers for wildcard directories - watchWildCardDirectories(resolved, cfg); + watchWildCardDirectories(resolved, resolvedPath, cfg); // Watch input files - watchInputFiles(resolved, cfg); + watchInputFiles(resolved, resolvedPath, cfg); } } } - function watchConfigFile(resolved: ResolvedConfigFileName) { - if (options.watch && !allWatchedConfigFiles.hasKey(resolved)) { - allWatchedConfigFiles.setValue(resolved, watchFile( + function watchConfigFile(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { + if (options.watch && !allWatchedConfigFiles.has(resolvedPath)) { + allWatchedConfigFiles.set(resolvedPath, watchFile( hostWithWatch, resolved, () => { - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full); + invalidateProjectAndScheduleBuilds(resolvedPath, ConfigFileProgramReloadLevel.Full); }, PollingInterval.High, WatchType.ConfigFile, @@ -519,10 +520,10 @@ namespace ts { } } - function watchWildCardDirectories(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) { + function watchWildCardDirectories(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { if (!options.watch) return; updateWatchingWildcardDirectories( - getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolved), + getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolvedPath), createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), (dir, flags) => { return watchDirectory( @@ -540,7 +541,7 @@ namespace ts { return; } - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial); + invalidateProjectAndScheduleBuilds(resolvedPath, ConfigFileProgramReloadLevel.Partial); }, flags, WatchType.WildcardDirectory, @@ -550,16 +551,16 @@ namespace ts { ); } - function watchInputFiles(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) { + function watchInputFiles(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { if (!options.watch) return; mutateMap( - getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolved), + getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolvedPath), arrayToMap(parsed.fileNames, toPath), { createNewValue: (path, input) => watchFilePath( hostWithWatch, input, - () => invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None), + () => invalidateProjectAndScheduleBuilds(resolvedPath, ConfigFileProgramReloadLevel.None), PollingInterval.Low, path as Path, WatchType.SourceFile, @@ -602,9 +603,9 @@ namespace ts { return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; } - function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { + function invalidateProjectAndScheduleBuilds(resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { reportFileChangeDetected = true; - invalidateResolvedProject(resolved, reloadLevel); + invalidateResolvedProject(resolvedPath, reloadLevel); scheduleBuildInvalidatedProject(); } @@ -830,10 +831,10 @@ namespace ts { } function invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel) { - invalidateResolvedProject(resolveProjectName(configFileName), reloadLevel || ConfigFileProgramReloadLevel.None); + invalidateResolvedProject(toResolvedConfigFilePath(resolveProjectName(configFileName)), reloadLevel || ConfigFileProgramReloadLevel.None); } - function invalidateResolvedProject(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { + function invalidateResolvedProject(resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { if (reloadLevel === ConfigFileProgramReloadLevel.Full) { configFileCache.removeKey(resolved); buildOrder = undefined; @@ -928,17 +929,18 @@ namespace ts { return; } + const resolvedPath = toResolvedConfigFilePath(resolved); if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - watchConfigFile(resolved); - watchWildCardDirectories(resolved, proj); - watchInputFiles(resolved, proj); + watchConfigFile(resolved, resolvedPath); + watchWildCardDirectories(resolved, resolvedPath, proj); + watchInputFiles(resolved, resolvedPath, proj); } else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { // Update file names const result = getFileNamesFromConfigSpecs(proj.configFileSpecs!, getDirectoryPath(resolved), proj.options, parseConfigFileHost); updateErrorForNoInputFiles(result, resolved, proj.configFileSpecs!, proj.errors, canJsonReportNoInutFiles(proj.raw)); proj.fileNames = result.fileNames; - watchInputFiles(resolved, proj); + watchInputFiles(resolved, resolvedPath, proj); } const status = getUpToDateStatus(proj); From 579d2bfe1f550f4bd88ca7590f52ee0fe0012682 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 17:16:36 -0700 Subject: [PATCH 012/111] Make FileMap Apis to match map but just ensure that keys are always paths Turn projectPendingBuild to config file map --- src/compiler/tsbuild.ts | 75 ++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index cc6899163fc..f1e32051310 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -194,20 +194,24 @@ namespace ts { } } - interface ConfigFileMap { - setValue(fileName: ResolvedConfigFileName, value: T): void; - getValue(fileName: ResolvedConfigFileName): T | undefined; - hasKey(fileName: ResolvedConfigFileName): boolean; - removeKey(fileName: ResolvedConfigFileName): void; - forEach(action: (value: T, key: ResolvedConfigFilePath) => void): void; - getSize(): number; + 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 ResolvedConfigFilePath = ResolvedConfigFileName & Path; + type ConfigFileMap = FileMap; - - function getOrCreateValueFromConfigFileMap(configFileMap: Map, resolved: ResolvedConfigFilePath, createT: () => T): T { + function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFilePath, createT: () => T): T { const existingValue = configFileMap.get(resolved); let newValue: T | undefined; if (!existingValue) { @@ -217,7 +221,7 @@ namespace ts { return existingValue || newValue!; } - function getOrCreateValueMapFromConfigFileMap(configFileMap: Map>, resolved: ResolvedConfigFilePath): Map { + function getOrCreateValueMapFromConfigFileMap(configFileMap: ConfigFileMap>, resolved: ResolvedConfigFilePath): Map { return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); } @@ -354,16 +358,16 @@ namespace ts { // Watch state const builderPrograms = createFileMap(); const diagnostics = createFileMap>(); - const projectPendingBuild = createFileMap(); + const projectPendingBuild = createMap() as ConfigFileMap; const projectErrorsReported = createFileMap(); let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); // Watches for the solution - const allWatchedWildcardDirectories = createMap>(); - const allWatchedInputFiles = createMap>(); - const allWatchedConfigFiles = createMap(); + const allWatchedWildcardDirectories = createMap() as ConfigFileMap>; + const allWatchedInputFiles = createMap() as ConfigFileMap>; + const allWatchedConfigFiles = createMap() as ConfigFileMap; return { buildAllProjects, @@ -394,7 +398,15 @@ namespace ts { // TODO remove this and use normal map so we arent transforming paths constantly - function createFileMap(): ConfigFileMap { + function createFileMap(): { + setValue(fileName: ResolvedConfigFileName, value: T): void; + getValue(fileName: ResolvedConfigFileName): T | undefined; + hasKey(fileName: ResolvedConfigFileName): boolean; + removeKey(fileName: ResolvedConfigFileName): void; + forEach(action: (value: T, key: ResolvedConfigFilePath) => void): void; + getSize(): number; + clear(): void; + } { const lookup = createMap(); return { setValue, @@ -435,7 +447,6 @@ namespace ts { } } - function resetBuildContext(opts = defaultOptions) { options = opts; baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); @@ -848,28 +859,29 @@ namespace ts { /** * return true if new addition */ - function addProjToQueue(proj: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { - const value = projectPendingBuild.getValue(proj); + function addProjToQueue(proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + const value = projectPendingBuild.get(proj); if (value === undefined) { - projectPendingBuild.setValue(proj, reloadLevel); + projectPendingBuild.set(proj, reloadLevel); } else if (value < reloadLevel) { - projectPendingBuild.setValue(proj, reloadLevel); + projectPendingBuild.set(proj, reloadLevel); } } function getNextInvalidatedProject() { for (const project of getBuildOrder()) { - const reloadLevel = projectPendingBuild.getValue(project); + const projectPath = toResolvedConfigFilePath(project); + const reloadLevel = projectPendingBuild.get(projectPath); if (reloadLevel !== undefined) { - projectPendingBuild.removeKey(project); + projectPendingBuild.delete(projectPath); return { project, reloadLevel }; } } } function hasPendingInvalidatedProjects() { - return !!projectPendingBuild.getSize(); + return !!projectPendingBuild.size; } function scheduleBuildInvalidatedProject() { @@ -969,7 +981,8 @@ namespace ts { // Always use build order to queue projects for (let index = buildOrder.indexOf(resolved) + 1; index < buildOrder.length; index++) { const project = buildOrder[index]; - if (projectPendingBuild.hasKey(project)) continue; + const projectPath = toResolvedConfigFilePath(project); + if (projectPendingBuild.has(projectPath)) continue; const config = parseConfigFile(project); if (!config || !config.projectReferences) continue; @@ -1001,15 +1014,15 @@ namespace ts { status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; } } - addProjToQueue(project, ConfigFileProgramReloadLevel.None); + addProjToQueue(projectPath, ConfigFileProgramReloadLevel.None); break; } } } function createBuildOrder(roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { - const temporaryMarks = createMap(); - const permanentMarks = createMap(); + const temporaryMarks = createMap() as ConfigFileMap; + const permanentMarks = createMap() as ConfigFileMap; const circularityReportStack: string[] = []; let buildOrder: ResolvedConfigFileName[] | undefined; for (const root of roots) { @@ -1138,7 +1151,7 @@ namespace ts { // Actual Emit const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createMap(); + const emittedOutputs = createMap() as FileMap; outputFiles.forEach(({ name, text, writeByteOrderMark }) => { let priorChangeTime: Date | undefined; if (!anyDtsChanged && isDeclarationFile(name)) { @@ -1241,7 +1254,7 @@ namespace ts { // Actual Emit Debug.assert(!!outputFiles.length); const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createMap(); + const emittedOutputs = createMap() as FileMap; outputFiles.forEach(({ name, text, writeByteOrderMark }) => { emittedOutputs.set(toPath(name), name); writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); @@ -1286,7 +1299,7 @@ namespace ts { projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status); } - function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: Map) { + function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); if (!skipOutputs || outputs.length !== skipOutputs.size) { if (options.verbose) { From 05257e8696d4adc2e620c1e85e7d799190df0d98 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 17:31:20 -0700 Subject: [PATCH 013/111] configFileCache, projectStatus, buildInfoChecked is now ConfigFileMap --- src/compiler/tsbuild.ts | 149 ++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f1e32051310..17c6d893764 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -339,9 +339,9 @@ namespace ts { let baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); const resolvedConfigFilePaths = createMap(); type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; - const configFileCache = createFileMap(); + const configFileCache = createMap() as ConfigFileMap; /** Map from config file name to up-to-date status */ - const projectStatus = createFileMap(); + const projectStatus = createMap() as ConfigFileMap; let buildOrder: readonly ResolvedConfigFileName[] | undefined; const writeFileName = host.trace ? (s: string) => host.trace!(s) : undefined; let readFileWithCache = (f: string) => host.readFile(f); @@ -353,7 +353,7 @@ namespace ts { compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); let moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; - const buildInfoChecked = createFileMap(); + const buildInfoChecked = createMap() as ConfigFileMap; // Watch state const builderPrograms = createFileMap(); @@ -474,17 +474,17 @@ namespace ts { return !!(entry as ParsedCommandLine).options; } - function parseConfigFile(configFilePath: ResolvedConfigFileName): ParsedCommandLine | undefined { - const value = configFileCache.getValue(configFilePath); + function parseConfigFile(configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { + const value = configFileCache.get(configFilePath); if (value) { return isParsedCommandLine(value) ? value : undefined; } let diagnostic: Diagnostic | undefined; parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; - const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost); + const parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost); parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; - configFileCache.setValue(configFilePath, parsed || diagnostic!); + configFileCache.set(configFilePath, parsed || diagnostic!); return parsed; } @@ -504,7 +504,7 @@ namespace ts { // Watch this file watchConfigFile(resolved, resolvedPath); - const cfg = parseConfigFile(resolved); + const cfg = parseConfigFile(resolved, resolvedPath); if (cfg) { // Update watchers for wildcard directories watchWildCardDirectories(resolved, resolvedPath, cfg); @@ -624,22 +624,22 @@ namespace ts { return buildOrder || (buildOrder = createBuildOrder(rootNames.map(resolveProjectName))); } - function getUpToDateStatus(project: ParsedCommandLine | undefined): UpToDateStatus { + function getUpToDateStatus(project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { if (project === undefined) { return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; } - const prior = projectStatus.getValue(project.options.configFilePath as ResolvedConfigFilePath); + const prior = projectStatus.get(resolvedPath); if (prior !== undefined) { return prior; } - const actual = getUpToDateStatusWorker(project); - projectStatus.setValue(project.options.configFilePath as ResolvedConfigFilePath, actual); + const actual = getUpToDateStatusWorker(project, resolvedPath); + projectStatus.set(resolvedPath, actual); return actual; } - function getUpToDateStatusWorker(project: ParsedCommandLine): UpToDateStatus { + function getUpToDateStatusWorker(project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { let newestInputFileName: string = undefined!; let newestInputFileTime = minimumDate; // Get timestamps of input files @@ -716,11 +716,12 @@ namespace ts { let usesPrepend = false; let upstreamChangedProject: string | undefined; if (project.projectReferences) { - projectStatus.setValue(project.options.configFilePath as ResolvedConfigFileName, { type: UpToDateStatusType.ComputingUpstream }); + projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); for (const ref of project.projectReferences) { usesPrepend = usesPrepend || !!(ref.prepend); const resolvedRef = resolveProjectReferencePath(ref); - const refStatus = getUpToDateStatus(parseConfigFile(resolvedRef)); + const resolvedRefPath = toResolvedConfigFilePath(resolvedRef); + const refStatus = getUpToDateStatus(parseConfigFile(resolvedRef, resolvedRefPath), resolvedRefPath); // Its a circular reference ignore the status of this project if (refStatus.type === UpToDateStatusType.ComputingUpstream) { @@ -794,8 +795,8 @@ namespace ts { if (extendedConfigStatus) return extendedConfigStatus; } - if (!buildInfoChecked.hasKey(project.options.configFilePath as ResolvedConfigFileName)) { - buildInfoChecked.setValue(project.options.configFilePath as ResolvedConfigFileName, true); + if (!buildInfoChecked.has(resolvedPath)) { + buildInfoChecked.set(resolvedPath, true); const buildInfoPath = getOutputPathForBuildInfo(project.options); if (buildInfoPath) { const value = readFileWithCache(buildInfoPath); @@ -847,10 +848,10 @@ namespace ts { function invalidateResolvedProject(resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - configFileCache.removeKey(resolved); + configFileCache.delete(resolved); buildOrder = undefined; } - projectStatus.removeKey(resolved); + projectStatus.delete(resolved); diagnostics.removeKey(resolved); addProjToQueue(resolved, reloadLevel); @@ -935,13 +936,13 @@ namespace ts { } function buildSingleInvalidatedProject(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { - const proj = parseConfigFile(resolved); + const resolvedPath = toResolvedConfigFilePath(resolved); + const proj = parseConfigFile(resolved, resolvedPath); if (!proj) { - reportParseConfigFileDiagnostic(resolved); + reportParseConfigFileDiagnostic(resolvedPath); return; } - const resolvedPath = toResolvedConfigFilePath(resolved); if (reloadLevel === ConfigFileProgramReloadLevel.Full) { watchConfigFile(resolved, resolvedPath); watchWildCardDirectories(resolved, resolvedPath, proj); @@ -955,7 +956,7 @@ namespace ts { watchInputFiles(resolved, resolvedPath, proj); } - const status = getUpToDateStatus(proj); + const status = getUpToDateStatus(proj, resolvedPath); verboseReportProjectStatus(resolved, status); if (status.type === UpToDateStatusType.UpstreamBlocked) { @@ -965,13 +966,13 @@ namespace ts { if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { // Fake that files have been built by updating output file stamps - updateOutputTimestamps(proj); + updateOutputTimestamps(proj, resolvedPath); return; } - const buildResult = needsBuild(status, resolved) ? - buildSingleProject(resolved) : // Actual build - updateBundle(resolved); // Fake that files have been built by manipulating prepend and existing output + const buildResult = needsBuild(status, proj) ? + buildSingleProject(resolved, resolvedPath) : // Actual build + updateBundle(resolved, resolvedPath); // Fake that files have been built by manipulating prepend and existing output if (buildResult & BuildResultFlags.AnyErrors) return; // Only composite projects can be referenced by other projects @@ -984,18 +985,18 @@ namespace ts { const projectPath = toResolvedConfigFilePath(project); if (projectPendingBuild.has(projectPath)) continue; - const config = parseConfigFile(project); + const config = parseConfigFile(project, projectPath); if (!config || !config.projectReferences) continue; for (const ref of config.projectReferences) { const resolvedRefPath = resolveProjectName(ref.path); - if (resolvedRefPath !== resolved) continue; + if (toResolvedConfigFilePath(resolvedRefPath) !== resolvedPath) 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 = projectStatus.getValue(project); + const status = projectStatus.get(projectPath); if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.OutOfDateWithPrepend)) { - projectStatus.setValue(project, { + projectStatus.set(projectPath, { type: UpToDateStatusType.OutOfDateWithUpstream, outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, newerProjectName: resolved @@ -1004,7 +1005,7 @@ namespace ts { } else if (status && status.type === UpToDateStatusType.UpToDate) { if (ref.prepend) { - projectStatus.setValue(project, { + projectStatus.set(projectPath, { type: UpToDateStatusType.OutOfDateWithPrepend, outOfDateOutputFileName: status.oldestOutputFileName, newerProjectName: resolved @@ -1046,7 +1047,7 @@ namespace ts { temporaryMarks.set(projPath, true); circularityReportStack.push(configFileName); - const parsed = parseConfigFile(configFileName); + const parsed = parseConfigFile(configFileName, projPath); if (parsed && parsed.projectReferences) { for (const ref of parsed.projectReferences) { const resolvedRefPath = resolveProjectName(ref.path); @@ -1060,7 +1061,7 @@ namespace ts { } } - function buildSingleProject(proj: ResolvedConfigFileName): BuildResultFlags { + function buildSingleProject(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath): BuildResultFlags { if (options.dry) { reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj); return BuildResultFlags.Success; @@ -1070,16 +1071,16 @@ namespace ts { let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; - const configFile = parseConfigFile(proj); + const configFile = parseConfigFile(proj, resolvedPath); if (!configFile) { // Failed to read the config file resultFlags |= BuildResultFlags.ConfigFileErrors; - reportParseConfigFileDiagnostic(proj); - projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); + reportParseConfigFileDiagnostic(resolvedPath); + projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); return resultFlags; } if (configFile.fileNames.length === 0) { - reportAndStoreErrors(proj, configFile.errors); + reportAndStoreErrors(resolvedPath, configFile.errors); // Nothing to build - must be a solution file, basically return BuildResultFlags.None; } @@ -1191,17 +1192,17 @@ namespace ts { oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) }; diagnostics.removeKey(proj); - projectStatus.setValue(proj, status); + projectStatus.set(resolvedPath, status); afterProgramCreate(proj, program); projectCompilerOptions = baseCompilerOptions; return resultFlags; function buildErrors(diagnostics: ReadonlyArray, errorFlags: BuildResultFlags, errorType: string) { resultFlags |= errorFlags; - reportAndStoreErrors(proj, diagnostics); + reportAndStoreErrors(resolvedPath, diagnostics); // List files if any other build error using program (emit errors already report files) if (writeFileName) listFiles(program, writeFileName); - projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); + projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); afterProgramCreate(proj, program); projectCompilerOptions = baseCompilerOptions; return resultFlags; @@ -1231,7 +1232,7 @@ namespace ts { return readBuilderProgram(parsed.options, readFileWithCache) as any as T; } - function updateBundle(proj: ResolvedConfigFileName): BuildResultFlags { + function updateBundle(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath): BuildResultFlags { if (options.dry) { reportStatus(Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); return BuildResultFlags.Success; @@ -1240,15 +1241,18 @@ namespace ts { if (options.verbose) reportStatus(Diagnostics.Updating_output_of_project_0, proj); // Update js, and source map - const config = Debug.assertDefined(parseConfigFile(proj)); + const config = Debug.assertDefined(parseConfigFile(proj, resolvedPath)); projectCompilerOptions = config.options; const outputFiles = emitUsingBuildInfo( config, compilerHost, - ref => parseConfigFile(resolveProjectName(ref.path))); + ref => { + const refName = resolveProjectName(ref.path); + return parseConfigFile(refName, toResolvedConfigFilePath(refName)); + }); if (isString(outputFiles)) { reportStatus(Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(outputFiles)); - return buildSingleProject(proj); + return buildSingleProject(proj, resolvedPath); } // Actual Emit @@ -1261,8 +1265,8 @@ namespace ts { }); const emitDiagnostics = emitterDiagnostics.getDiagnostics(); if (emitDiagnostics.length) { - reportAndStoreErrors(proj, emitDiagnostics); - projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Emit errors" }); + reportAndStoreErrors(resolvedPath, emitDiagnostics); + projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Emit errors" }); projectCompilerOptions = baseCompilerOptions; return BuildResultFlags.DeclarationOutputUnchanged | BuildResultFlags.EmitErrors; } @@ -1281,12 +1285,12 @@ namespace ts { }; diagnostics.removeKey(proj); - projectStatus.setValue(proj, status); + projectStatus.set(resolvedPath, status); projectCompilerOptions = baseCompilerOptions; return BuildResultFlags.DeclarationOutputUnchanged; } - function updateOutputTimestamps(proj: ParsedCommandLine) { + function updateOutputTimestamps(proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { if (options.dry) { return reportStatus(Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); } @@ -1296,7 +1300,7 @@ namespace ts { newestDeclarationFileContentChangedTime: priorNewestUpdateTime, oldestOutputFileName: getFirstProjectOutput(proj, !host.useCaseSensitiveFileNames()) }; - projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status); + projectStatus.set(resolvedPath, status); } function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { @@ -1327,10 +1331,11 @@ namespace ts { // Get the same graph for cleaning we'd use for building const filesToDelete: string[] = []; for (const proj of getBuildOrder()) { - const parsed = parseConfigFile(proj); + const resolvedPath = toResolvedConfigFilePath(proj); + const parsed = parseConfigFile(proj, resolvedPath); if (parsed === undefined) { // File has gone missing; fine to ignore here - reportParseConfigFileDiagnostic(proj); + reportParseConfigFileDiagnostic(resolvedPath); continue; } const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); @@ -1386,51 +1391,51 @@ namespace ts { reportBuildQueue(buildOrder); let anyFailed = false; for (const next of buildOrder) { - const proj = parseConfigFile(next); + const resolvedPath = toResolvedConfigFilePath(next); + const proj = parseConfigFile(next, resolvedPath); if (proj === undefined) { - reportParseConfigFileDiagnostic(next); + reportParseConfigFileDiagnostic(resolvedPath); anyFailed = true; break; } // report errors early when using continue or break statements const errors = proj.errors; - const status = getUpToDateStatus(proj); + const status = getUpToDateStatus(proj, resolvedPath); verboseReportProjectStatus(next, status); - const projName = proj.options.configFilePath!; if (status.type === UpToDateStatusType.UpToDate && !options.force) { - reportAndStoreErrors(next, errors); + reportAndStoreErrors(resolvedPath, errors); // Up to date, skip if (defaultOptions.dry) { // In a dry build, inform the user of this fact - reportStatus(Diagnostics.Project_0_is_up_to_date, projName); + reportStatus(Diagnostics.Project_0_is_up_to_date, next); } continue; } if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) { - reportAndStoreErrors(next, errors); + reportAndStoreErrors(resolvedPath, errors); // Fake build - updateOutputTimestamps(proj); + updateOutputTimestamps(proj, resolvedPath); continue; } if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportAndStoreErrors(next, errors); - if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName); + reportAndStoreErrors(resolvedPath, errors); + if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, next, status.upstreamProjectName); continue; } if (status.type === UpToDateStatusType.ContainerOnly) { - reportAndStoreErrors(next, errors); + reportAndStoreErrors(resolvedPath, errors); // Do nothing continue; } - const buildResult = needsBuild(status, next) ? - buildSingleProject(next) : // Actual build - updateBundle(next); // Fake that files have been built by manipulating prepend and existing output + const buildResult = needsBuild(status, proj) ? + buildSingleProject(next, resolvedPath) : // Actual build + updateBundle(next, resolvedPath); // Fake that files have been built by manipulating prepend and existing output anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors); } @@ -1447,20 +1452,18 @@ namespace ts { return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; } - function needsBuild(status: UpToDateStatus, configFile: ResolvedConfigFileName) { + function needsBuild(status: UpToDateStatus, config: ParsedCommandLine) { if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; - const config = parseConfigFile(configFile); - return !config || - config.fileNames.length === 0 || + return config.fileNames.length === 0 || !!config.errors.length || !isIncrementalCompilation(config.options); } - function reportParseConfigFileDiagnostic(proj: ResolvedConfigFileName) { - reportAndStoreErrors(proj, [configFileCache.getValue(proj) as Diagnostic]); + function reportParseConfigFileDiagnostic(proj: ResolvedConfigFilePath) { + reportAndStoreErrors(proj, [configFileCache.get(proj) as Diagnostic]); } - function reportAndStoreErrors(proj: ResolvedConfigFileName, errors: ReadonlyArray) { + function reportAndStoreErrors(proj: ResolvedConfigFilePath, errors: ReadonlyArray) { reportErrors(errors); projectErrorsReported.setValue(proj, true); diagnostics.setValue(proj, errors); From fd6773f94416008149b9c92b1db22179f5b9d747 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 17:55:15 -0700 Subject: [PATCH 014/111] Diagnostics as ConfigFileMap --- src/compiler/tsbuild.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 17c6d893764..d8d55c346f9 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -357,9 +357,9 @@ namespace ts { // Watch state const builderPrograms = createFileMap(); - const diagnostics = createFileMap>(); + const diagnostics = createMap() as ConfigFileMap>; const projectPendingBuild = createMap() as ConfigFileMap; - const projectErrorsReported = createFileMap(); + const projectErrorsReported = createMap() as ConfigFileMap; let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); @@ -852,7 +852,7 @@ namespace ts { buildOrder = undefined; } projectStatus.delete(resolved); - diagnostics.removeKey(resolved); + diagnostics.delete(resolved); addProjToQueue(resolved, reloadLevel); } @@ -920,8 +920,9 @@ namespace ts { if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) { // Report errors from the other projects getBuildOrder().forEach(project => { - if (!projectErrorsReported.hasKey(project)) { - reportErrors(diagnostics.getValue(project) || emptyArray); + const projectPath = toResolvedConfigFilePath(project); + if (!projectErrorsReported.has(projectPath)) { + reportErrors(diagnostics.get(projectPath) || emptyArray); } }); let totalErrors = 0; @@ -1191,7 +1192,7 @@ namespace ts { newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) }; - diagnostics.removeKey(proj); + diagnostics.delete(resolvedPath); projectStatus.set(resolvedPath, status); afterProgramCreate(proj, program); projectCompilerOptions = baseCompilerOptions; @@ -1284,7 +1285,7 @@ namespace ts { oldestOutputFileName: outputFiles[0].name }; - diagnostics.removeKey(proj); + diagnostics.delete(resolvedPath); projectStatus.set(resolvedPath, status); projectCompilerOptions = baseCompilerOptions; return BuildResultFlags.DeclarationOutputUnchanged; @@ -1465,8 +1466,8 @@ namespace ts { function reportAndStoreErrors(proj: ResolvedConfigFilePath, errors: ReadonlyArray) { reportErrors(errors); - projectErrorsReported.setValue(proj, true); - diagnostics.setValue(proj, errors); + projectErrorsReported.set(proj, true); + diagnostics.set(proj, errors); } function reportErrors(errors: ReadonlyArray) { From 11b21fbba6aa383c04917c0379b0f7c04756a899 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 17:56:50 -0700 Subject: [PATCH 015/111] builderPrograms as ConfigFileMap --- src/compiler/tsbuild.ts | 67 +++++------------------------------------ 1 file changed, 8 insertions(+), 59 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index d8d55c346f9..54094fb62de 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -356,7 +356,7 @@ namespace ts { const buildInfoChecked = createMap() as ConfigFileMap; // Watch state - const builderPrograms = createFileMap(); + const builderPrograms = createMap() as ConfigFileMap; const diagnostics = createMap() as ConfigFileMap>; const projectPendingBuild = createMap() as ConfigFileMap; const projectErrorsReported = createMap() as ConfigFileMap; @@ -396,57 +396,6 @@ namespace ts { return resolvedPath; } - - // TODO remove this and use normal map so we arent transforming paths constantly - function createFileMap(): { - setValue(fileName: ResolvedConfigFileName, value: T): void; - getValue(fileName: ResolvedConfigFileName): T | undefined; - hasKey(fileName: ResolvedConfigFileName): boolean; - removeKey(fileName: ResolvedConfigFileName): void; - forEach(action: (value: T, key: ResolvedConfigFilePath) => void): void; - getSize(): number; - clear(): void; - } { - const lookup = createMap(); - return { - setValue, - getValue, - removeKey, - forEach, - hasKey, - getSize, - clear - }; - - function forEach(action: (value: T, key: ResolvedConfigFilePath) => void) { - lookup.forEach(action); - } - - function hasKey(fileName: ResolvedConfigFileName) { - return lookup.has(toResolvedConfigFilePath(fileName)); - } - - function removeKey(fileName: ResolvedConfigFileName) { - lookup.delete(toResolvedConfigFilePath(fileName)); - } - - function setValue(fileName: ResolvedConfigFileName, value: T) { - lookup.set(toResolvedConfigFilePath(fileName), value); - } - - function getValue(fileName: ResolvedConfigFileName): T | undefined { - return lookup.get(toResolvedConfigFilePath(fileName)); - } - - function getSize() { - return lookup.size; - } - - function clear() { - lookup.clear(); - } - } - function resetBuildContext(opts = defaultOptions) { options = opts; baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); @@ -1116,7 +1065,7 @@ namespace ts { configFile.fileNames, configFile.options, compilerHost, - getOldProgram(proj, configFile), + getOldProgram(resolvedPath, configFile), configFile.errors, configFile.projectReferences ); @@ -1194,7 +1143,7 @@ namespace ts { }; diagnostics.delete(resolvedPath); projectStatus.set(resolvedPath, status); - afterProgramCreate(proj, program); + afterProgramCreate(resolvedPath, program); projectCompilerOptions = baseCompilerOptions; return resultFlags; @@ -1204,7 +1153,7 @@ namespace ts { // List files if any other build error using program (emit errors already report files) if (writeFileName) listFiles(program, writeFileName); projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); - afterProgramCreate(proj, program); + afterProgramCreate(resolvedPath, program); projectCompilerOptions = baseCompilerOptions; return resultFlags; } @@ -1216,19 +1165,19 @@ namespace ts { } } - function afterProgramCreate(proj: ResolvedConfigFileName, program: T) { + function afterProgramCreate(proj: ResolvedConfigFilePath, program: T) { if (host.afterProgramEmitAndDiagnostics) { host.afterProgramEmitAndDiagnostics(program); } if (options.watch) { program.releaseProgram(); - builderPrograms.setValue(proj, program); + builderPrograms.set(proj, program); } } - function getOldProgram(proj: ResolvedConfigFileName, parsed: ParsedCommandLine) { + function getOldProgram(proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { if (options.force) return undefined; - const value = builderPrograms.getValue(proj); + const value = builderPrograms.get(proj); if (value) return value; return readBuilderProgram(parsed.options, readFileWithCache) as any as T; } From 65ed413d8ddf80d418bb9df059f2e2201adccbfb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 18 Apr 2019 18:01:03 -0700 Subject: [PATCH 016/111] Remove resolveProjectName as api --- src/compiler/tsbuild.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 54094fb62de..0042abfcb9f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -266,8 +266,6 @@ namespace ts { // Testing only // TODO:: All the below ones should technically only be in watch mode. but thats for later time - /*@internal*/ resolveProjectName(name: string): ResolvedConfigFileName; - /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; /*@internal*/ buildInvalidatedProject(): void; @@ -378,8 +376,6 @@ namespace ts { invalidateProject, buildInvalidatedProject, - resolveProjectName, - startWatching }; From ddee617e845c33cbb45fddb914c8914b54a131d7 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 19 Apr 2019 10:11:44 -0700 Subject: [PATCH 017/111] Make SolutionBuilder and SolutionBuilderWithWatch separate --- src/compiler/tsbuild.ts | 95 ++++++++++++-------- src/testRunner/unittests/tsbuild/sample.ts | 55 ++++++++++++ src/testRunner/unittests/tsbuildWatchMode.ts | 13 ++- src/tsc/tsc.ts | 32 ++++--- 4 files changed, 133 insertions(+), 62 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 0042abfcb9f..9ec1b3f99c4 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -236,7 +236,6 @@ namespace ts { export interface SolutionBuilderHostBase extends ProgramHost { getModifiedTime(fileName: string): Date | undefined; setModifiedTime(fileName: string, date: Date): void; - deleteFile(fileName: string): void; 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; @@ -246,10 +245,11 @@ namespace ts { afterProgramEmitAndDiagnostics?(program: T): void; // For testing - now?(): Date; + /*@internal*/ now?(): Date; } export interface SolutionBuilderHost extends SolutionBuilderHostBase { + deleteFile(fileName: string): void; reportErrorSummary?: ReportEmitErrorSummary; } @@ -262,17 +262,16 @@ namespace ts { // Currently used for testing but can be made public if needed: /*@internal*/ getBuildOrder(): ReadonlyArray; + /*@internal*/ resetBuildContext(opts?: BuildOptions): void; // Testing only - - // TODO:: All the below ones should technically only be in watch mode. but thats for later time + /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; /*@internal*/ buildInvalidatedProject(): void; - - /*@internal*/ resetBuildContext(opts?: BuildOptions): void; } - export interface SolutionBuilderWithWatch extends SolutionBuilder { + export interface SolutionBuilderWithWatch { + buildAllProjects(): ExitStatus; /*@internal*/ startWatching(): void; } @@ -291,7 +290,6 @@ namespace ts { 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; @@ -299,6 +297,7 @@ namespace ts { export function createSolutionBuilderHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost; + host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop; host.reportErrorSummary = reportErrorSummary; return host; } @@ -318,16 +317,23 @@ namespace ts { return result; } + export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { + return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); + } + + export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch { + return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions); + } + /** * 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 - * TODO: use SolutionBuilderWithWatchHost => watchedSolution - * use SolutionBuilderHost => Solution */ - export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch; - export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch { - const hostWithWatch = host as SolutionBuilderWithWatchHost; + function createSolutionBuilderWorker(watch: false, host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch; + function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder | SolutionBuilderWithWatch { + const host = hostOrHostWithWatch as SolutionBuilderHost; + const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost; const currentDirectory = host.getCurrentDirectory(); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host); @@ -360,24 +366,27 @@ namespace ts { const projectErrorsReported = createMap() as ConfigFileMap; let timerToBuildInvalidatedProject: any; let reportFileChangeDetected = false; - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); + const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); // Watches for the solution const allWatchedWildcardDirectories = createMap() as ConfigFileMap>; const allWatchedInputFiles = createMap() as ConfigFileMap>; const allWatchedConfigFiles = createMap() as ConfigFileMap; - return { - buildAllProjects, - cleanAllProjects, - resetBuildContext, - getBuildOrder, - - invalidateProject, - buildInvalidatedProject, - - startWatching - }; + return watch ? + { + buildAllProjects, + startWatching + } : + { + buildAllProjects, + cleanAllProjects, + getBuildOrder, + resetBuildContext, + getUpToDateStatusOfProject, + invalidateProject, + buildInvalidatedProject, + }; function toPath(fileName: string) { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); @@ -392,8 +401,8 @@ namespace ts { return resolvedPath; } - function resetBuildContext(opts = defaultOptions) { - options = opts; + function resetBuildContext(opts?: BuildOptions) { + options = opts || defaultOptions; baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); resolvedConfigFilePaths.clear(); configFileCache.clear(); @@ -462,7 +471,7 @@ namespace ts { } function watchConfigFile(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { - if (options.watch && !allWatchedConfigFiles.has(resolvedPath)) { + if (watch && !allWatchedConfigFiles.has(resolvedPath)) { allWatchedConfigFiles.set(resolvedPath, watchFile( hostWithWatch, resolved, @@ -477,7 +486,7 @@ namespace ts { } function watchWildCardDirectories(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (!options.watch) return; + if (!watch) return; updateWatchingWildcardDirectories( getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolvedPath), createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), @@ -508,7 +517,7 @@ namespace ts { } function watchInputFiles(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (!options.watch) return; + if (!watch) return; mutateMap( getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolvedPath), arrayToMap(parsed.fileNames, toPath), @@ -565,8 +574,14 @@ namespace ts { scheduleBuildInvalidatedProject(); } + function getUpToDateStatusOfProject(project: string): UpToDateStatus { + const configFileName = resolveProjectName(project); + const configFilePath = toResolvedConfigFilePath(configFileName); + return getUpToDateStatus(parseConfigFile(configFileName, configFilePath), configFilePath); + } + function getBuildOrder() { - return buildOrder || (buildOrder = createBuildOrder(rootNames.map(resolveProjectName))); + return buildOrder || (buildOrder = createBuildOrder(resolveProjectNames(rootNames))); } function getUpToDateStatus(project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { @@ -851,7 +866,7 @@ namespace ts { if (buildProject) { buildSingleInvalidatedProject(buildProject.project, buildProject.reloadLevel); if (hasPendingInvalidatedProjects()) { - if (options.watch && !timerToBuildInvalidatedProject) { + if (watch && !timerToBuildInvalidatedProject) { scheduleBuildInvalidatedProject(); } } @@ -862,7 +877,7 @@ namespace ts { } function reportErrorSummary() { - if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) { + if (watch || host.reportErrorSummary) { // Report errors from the other projects getBuildOrder().forEach(project => { const projectPath = toResolvedConfigFilePath(project); @@ -872,11 +887,11 @@ namespace ts { }); let totalErrors = 0; diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); - if (options.watch) { + if (watch) { reportWatchStatus(getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); } else { - (host as SolutionBuilderHost).reportErrorSummary!(totalErrors); + host.reportErrorSummary!(totalErrors); } } } @@ -1165,7 +1180,7 @@ namespace ts { if (host.afterProgramEmitAndDiagnostics) { host.afterProgramEmitAndDiagnostics(program); } - if (options.watch) { + if (watch) { program.releaseProgram(); builderPrograms.set(proj, program); } @@ -1312,8 +1327,12 @@ namespace ts { return resolveConfigFileProjectName(resolvePath(host.getCurrentDirectory(), name)); } + function resolveProjectNames(configFileNames: ReadonlyArray): ResolvedConfigFileName[] { + return configFileNames.map(resolveProjectName); + } + function buildAllProjects(): ExitStatus { - if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } + if (watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } // TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api // Override readFile for json files and output .d.ts to cache the text const savedReadFileWithCache = readFileWithCache; diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 2621bd303ea..c004f53d38e 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -337,6 +337,61 @@ namespace ts { }); }); + describe("project invalidation", () => { + it("invalidates projects correctly", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); + + builder.buildAllProjects(); + host.assertDiagnosticMessages(/*empty*/); + + // Update a timestamp in the middle project + tick(); + appendText(fs, "/src/logic/index.ts", "function foo() {}"); + const originalWriteFile = fs.writeFileSync; + const writtenFiles = createMap(); + fs.writeFileSync = (path, data, encoding) => { + writtenFiles.set(path, true); + originalWriteFile.call(fs, path, data, encoding); + }; + // Because we haven't reset the build context, the builder should assume there's nothing to do right now + const status = builder.getUpToDateStatusOfProject("/src/logic"); + assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); + verifyInvalidation(/*expectedToWriteTests*/ false); + + // Rebuild this project + fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")} +export class cNew {}`); + verifyInvalidation(/*expectedToWriteTests*/ true); + + function verifyInvalidation(expectedToWriteTests: boolean) { + // Rebuild this project + tick(); + builder.invalidateProject("/src/logic"); + builder.buildInvalidatedProject(); + // The file should be updated + assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); + assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); + assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt"); + assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); + writtenFiles.clear(); + + // Build downstream projects should update 'tests', but not 'core' + tick(); + builder.buildInvalidatedProject(); + if (expectedToWriteTests) { + assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); + } + else { + assert.equal(writtenFiles.size, 0, "Should not write any new files"); + } + assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp"); + assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + } + }); + }); + describe("lists files", () => { it("listFiles", () => { const fs = projFs.shadow(); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 005956a98ad..ef87aa1f9cb 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -17,12 +17,13 @@ namespace ts.tscWatch { } export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { - const host = createSolutionBuilderWithWatchHost(system); - return ts.createSolutionBuilder(host, rootNames, defaultOptions || { watch: true }); + const host = createSolutionBuilderHost(system); + return ts.createSolutionBuilder(host, rootNames, defaultOptions || {}); } - function createSolutionBuilderWithWatch(host: TsBuildWatchSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { - const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions); + function createSolutionBuilderWithWatch(system: TsBuildWatchSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { + const host = createSolutionBuilderWithWatchHost(system); + const solutionBuilder = ts.createSolutionBuilderWithWatch(host, rootNames, defaultOptions || { watch: true }); solutionBuilder.buildAllProjects(); solutionBuilder.startWatching(); return solutionBuilder; @@ -1140,9 +1141,7 @@ export function gfoo() { it("incremental updates in verbose mode", () => { const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); - const solutionBuilder = createSolutionBuilder(host, [`${project}/${SubProject.tests}`], { verbose: true, watch: true }); - solutionBuilder.buildAllProjects(); - solutionBuilder.startWatching(); + createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], { verbose: true, watch: true }); checkOutputErrorsInitial(host, emptyArray, /*disableConsoleClears*/ undefined, [ `Projects in this build: \r\n * sample1/core/tsconfig.json\r\n * sample1/logic/tsconfig.json\r\n * sample1/tests/tsconfig.json\n\n`, `Project 'sample1/core/tsconfig.json' is out of date because output file 'sample1/core/anotherModule.js' does not exist\n\n`, diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 500480463bd..35bd29ea774 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -183,6 +183,9 @@ namespace ts { function performBuild(args: string[]) { const { buildOptions, projects, errors } = parseBuildCommand(args); + // Update to pretty if host supports it + updateReportDiagnostic(buildOptions); + if (errors.length > 0) { errors.forEach(reportDiagnostic); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); @@ -194,8 +197,6 @@ namespace ts { return sys.exit(ExitStatus.Success); } - // Update to pretty if host supports it - updateReportDiagnostic(buildOptions); if (projects.length === 0) { printVersion(); printHelp(buildOpts, "--build "); @@ -210,24 +211,21 @@ namespace ts { reportWatchModeWithoutSysSupport(); } - // Use default createProgram - const buildHost = buildOptions.watch ? - createSolutionBuilderWithWatchHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createWatchStatusReporter(buildOptions)) : - createSolutionBuilderHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createReportErrorSummary(buildOptions)); - updateCreateProgram(buildHost); - buildHost.afterProgramEmitAndDiagnostics = (program: BuilderProgram) => reportStatistics(program.getProgram()); - - const builder = createSolutionBuilder(buildHost, projects, buildOptions); - if (buildOptions.clean) { - return sys.exit(builder.cleanAllProjects()); - } - if (buildOptions.watch) { + const buildHost = createSolutionBuilderWithWatchHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createWatchStatusReporter(buildOptions)); + updateCreateProgram(buildHost); + buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); + const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions); builder.buildAllProjects(); - return (builder as SolutionBuilderWithWatch).startWatching(); + return builder.startWatching(); + } + else { + const buildHost = createSolutionBuilderHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createReportErrorSummary(buildOptions)); + updateCreateProgram(buildHost); + buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); + const builder = createSolutionBuilder(buildHost, projects, buildOptions); + return sys.exit(buildOptions.clean ? builder.cleanAllProjects() : builder.buildAllProjects()); } - - return sys.exit(builder.buildAllProjects()); } function createReportErrorSummary(options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined { From 4b572bea3703cc12b9086a6f4bbfcda4fca58897 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 19 Apr 2019 14:16:29 -0700 Subject: [PATCH 018/111] Instead of having two separate paths for building projects, (build all or invalidated use single path) --- src/compiler/tsbuild.ts | 210 +++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 108 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 9ec1b3f99c4..5690851e0b8 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -195,7 +195,6 @@ namespace ts { } type ResolvedConfigFilePath = ResolvedConfigFileName & Path; - interface FileMap extends Map { get(key: U): T | undefined; has(key: U): boolean; @@ -208,7 +207,6 @@ namespace ts { delete(key: U): boolean; clear(): void; } - type ConfigFileMap = FileMap; function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFilePath, createT: () => T): T { @@ -811,10 +809,13 @@ namespace ts { configFileCache.delete(resolved); buildOrder = undefined; } + clearProjectStatus(resolved); + addProjToQueue(resolved, reloadLevel); + } + + function clearProjectStatus(resolved: ResolvedConfigFilePath) { projectStatus.delete(resolved); diagnostics.delete(resolved); - - addProjToQueue(resolved, reloadLevel); } /** @@ -896,87 +897,121 @@ namespace ts { } } - function buildSingleInvalidatedProject(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { - const resolvedPath = toResolvedConfigFilePath(resolved); - const proj = parseConfigFile(resolved, resolvedPath); - if (!proj) { - reportParseConfigFileDiagnostic(resolvedPath); + function buildSingleInvalidatedProject(project: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { + const projectPath = toResolvedConfigFilePath(project); + const config = parseConfigFile(project, projectPath); + if (!config) { + reportParseConfigFileDiagnostic(projectPath); return; } if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - watchConfigFile(resolved, resolvedPath); - watchWildCardDirectories(resolved, resolvedPath, proj); - watchInputFiles(resolved, resolvedPath, proj); + watchConfigFile(project, projectPath); + watchWildCardDirectories(project, projectPath, config); + watchInputFiles(project, projectPath, config); } else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { // Update file names - const result = getFileNamesFromConfigSpecs(proj.configFileSpecs!, getDirectoryPath(resolved), proj.options, parseConfigFileHost); - updateErrorForNoInputFiles(result, resolved, proj.configFileSpecs!, proj.errors, canJsonReportNoInutFiles(proj.raw)); - proj.fileNames = result.fileNames; - watchInputFiles(resolved, resolvedPath, proj); + const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, parseConfigFileHost); + updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw)); + config.fileNames = result.fileNames; + watchInputFiles(project, projectPath, config); } - const status = getUpToDateStatus(proj, resolvedPath); - verboseReportProjectStatus(resolved, status); + const status = getUpToDateStatus(config, projectPath); + verboseReportProjectStatus(project, status); + if (status.type === UpToDateStatusType.UpToDate && !options.force) { + reportAndStoreErrors(projectPath, config.errors); + // Up to date, skip + if (options.dry) { + // In a dry build, inform the user of this fact + reportStatus(Diagnostics.Project_0_is_up_to_date, project); + } + return; + } + + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) { + reportAndStoreErrors(projectPath, config.errors); + // Fake that files have been built by updating output file stamps + updateOutputTimestamps(config, projectPath); + return; + } if (status.type === UpToDateStatusType.UpstreamBlocked) { - if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, resolved, status.upstreamProjectName); + reportAndStoreErrors(projectPath, config.errors); + if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName); return; } - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { - // Fake that files have been built by updating output file stamps - updateOutputTimestamps(proj, resolvedPath); + if (status.type === UpToDateStatusType.ContainerOnly) { + reportAndStoreErrors(projectPath, config.errors); + // Do nothing return; } - const buildResult = needsBuild(status, proj) ? - buildSingleProject(resolved, resolvedPath) : // Actual build - updateBundle(resolved, resolvedPath); // Fake that files have been built by manipulating prepend and existing output - if (buildResult & BuildResultFlags.AnyErrors) return; - + const buildResult = needsBuild(status, config) ? + buildSingleProject(project, projectPath) : // Actual build + updateBundle(project, projectPath); // Fake that files have been built by manipulating prepend and existing output // Only composite projects can be referenced by other projects - if (!proj.options.composite) return; - const buildOrder = getBuildOrder(); + if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { + queueReferencingProjects(project, projectPath, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); + } + } + function queueReferencingProjects(project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, declarationOutputChanged: boolean) { // Always use build order to queue projects - for (let index = buildOrder.indexOf(resolved) + 1; index < buildOrder.length; index++) { - const project = buildOrder[index]; - const projectPath = toResolvedConfigFilePath(project); - if (projectPendingBuild.has(projectPath)) continue; + const buildOrder = getBuildOrder(); + for (let index = buildOrder.indexOf(project) + 1; index < buildOrder.length; index++) { + const nextProject = buildOrder[index]; + const nextProjectPath = toResolvedConfigFilePath(nextProject); + if (projectPendingBuild.has(nextProjectPath)) continue; - const config = parseConfigFile(project, projectPath); - if (!config || !config.projectReferences) continue; - for (const ref of config.projectReferences) { + const nextProjectConfig = parseConfigFile(nextProject, nextProjectPath); + if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; + for (const ref of nextProjectConfig.projectReferences) { const resolvedRefPath = resolveProjectName(ref.path); - if (toResolvedConfigFilePath(resolvedRefPath) !== resolvedPath) continue; + if (toResolvedConfigFilePath(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 = projectStatus.get(projectPath); - if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { - if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.OutOfDateWithPrepend)) { - projectStatus.set(projectPath, { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, - newerProjectName: resolved - }); + const status = projectStatus.get(nextProjectPath); + if (status) { + switch (status.type) { + case UpToDateStatusType.UpToDate: + if (!declarationOutputChanged) { + if (ref.prepend) { + 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 (declarationOutputChanged) { + projectStatus.set(nextProjectPath, { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, + newerProjectName: project + }); + } + break; + + case UpToDateStatusType.UpstreamBlocked: + if (toResolvedConfigFilePath(resolveProjectName(status.upstreamProjectName)) === projectPath) { + clearProjectStatus(nextProjectPath); + } + break; } } - else if (status && status.type === UpToDateStatusType.UpToDate) { - if (ref.prepend) { - projectStatus.set(projectPath, { - type: UpToDateStatusType.OutOfDateWithPrepend, - outOfDateOutputFileName: status.oldestOutputFileName, - newerProjectName: resolved - }); - } - else { - status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; - } - } - addProjToQueue(projectPath, ConfigFileProgramReloadLevel.None); + addProjToQueue(nextProjectPath, ConfigFileProgramReloadLevel.None); break; } } @@ -1354,55 +1389,12 @@ namespace ts { const buildOrder = getBuildOrder(); reportBuildQueue(buildOrder); - let anyFailed = false; - for (const next of buildOrder) { - const resolvedPath = toResolvedConfigFilePath(next); - const proj = parseConfigFile(next, resolvedPath); - if (proj === undefined) { - reportParseConfigFileDiagnostic(resolvedPath); - anyFailed = true; - break; - } + buildOrder.forEach(configFileName => + projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None)); - // report errors early when using continue or break statements - const errors = proj.errors; - const status = getUpToDateStatus(proj, resolvedPath); - verboseReportProjectStatus(next, status); - - if (status.type === UpToDateStatusType.UpToDate && !options.force) { - reportAndStoreErrors(resolvedPath, errors); - // Up to date, skip - if (defaultOptions.dry) { - // In a dry build, inform the user of this fact - reportStatus(Diagnostics.Project_0_is_up_to_date, next); - } - continue; - } - - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) { - reportAndStoreErrors(resolvedPath, errors); - // Fake build - updateOutputTimestamps(proj, resolvedPath); - continue; - } - - if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportAndStoreErrors(resolvedPath, errors); - if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, next, status.upstreamProjectName); - continue; - } - - if (status.type === UpToDateStatusType.ContainerOnly) { - reportAndStoreErrors(resolvedPath, errors); - // Do nothing - continue; - } - - const buildResult = needsBuild(status, proj) ? - buildSingleProject(next, resolvedPath) : // Actual build - updateBundle(next, resolvedPath); // Fake that files have been built by manipulating prepend and existing output - - anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors); + while (hasPendingInvalidatedProjects()) { + const buildProject = getNextInvalidatedProject()!; + buildSingleInvalidatedProject(buildProject.project, buildProject.reloadLevel); } reportErrorSummary(); host.readFile = originalReadFile; @@ -1414,7 +1406,7 @@ namespace ts { readFileWithCache = savedReadFileWithCache; compilerHost.resolveModuleNames = originalResolveModuleNames; moduleResolutionCache = undefined; - return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; + return diagnostics.size ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; } function needsBuild(status: UpToDateStatus, config: ParsedCommandLine) { @@ -1431,7 +1423,9 @@ namespace ts { function reportAndStoreErrors(proj: ResolvedConfigFilePath, errors: ReadonlyArray) { reportErrors(errors); projectErrorsReported.set(proj, true); - diagnostics.set(proj, errors); + if (errors.length) { + diagnostics.set(proj, errors); + } } function reportErrors(errors: ReadonlyArray) { From 04a972b9f384738769e90e133e88340d64cbc043 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 19 Apr 2019 15:13:55 -0700 Subject: [PATCH 019/111] More refactoring --- src/compiler/tsbuild.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 5690851e0b8..1b298f248f2 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -832,14 +832,15 @@ namespace ts { } function getNextInvalidatedProject() { - for (const project of getBuildOrder()) { + Debug.assert(hasPendingInvalidatedProjects()); + return forEach(getBuildOrder(), (project, projectIndex) => { const projectPath = toResolvedConfigFilePath(project); const reloadLevel = projectPendingBuild.get(projectPath); if (reloadLevel !== undefined) { projectPendingBuild.delete(projectPath); - return { project, reloadLevel }; + return { project, projectPath, reloadLevel, projectIndex }; } - } + }); } function hasPendingInvalidatedProjects() { @@ -863,9 +864,8 @@ namespace ts { projectErrorsReported.clear(); reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation); } - const buildProject = getNextInvalidatedProject(); - if (buildProject) { - buildSingleInvalidatedProject(buildProject.project, buildProject.reloadLevel); + if (hasPendingInvalidatedProjects()) { + buildNextInvalidatedProject(); if (hasPendingInvalidatedProjects()) { if (watch && !timerToBuildInvalidatedProject) { scheduleBuildInvalidatedProject(); @@ -897,8 +897,8 @@ namespace ts { } } - function buildSingleInvalidatedProject(project: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { - const projectPath = toResolvedConfigFilePath(project); + function buildNextInvalidatedProject() { + const { project, projectPath, reloadLevel, projectIndex } = getNextInvalidatedProject()!; const config = parseConfigFile(project, projectPath); if (!config) { reportParseConfigFileDiagnostic(projectPath); @@ -954,14 +954,14 @@ namespace ts { updateBundle(project, projectPath); // Fake that files have been built by manipulating prepend and existing output // Only composite projects can be referenced by other projects if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { - queueReferencingProjects(project, projectPath, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); + queueReferencingProjects(project, projectPath, projectIndex, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); } } - function queueReferencingProjects(project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, declarationOutputChanged: boolean) { + function queueReferencingProjects(project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, projectIndex: number, declarationOutputChanged: boolean) { // Always use build order to queue projects const buildOrder = getBuildOrder(); - for (let index = buildOrder.indexOf(project) + 1; index < buildOrder.length; index++) { + for (let index = projectIndex + 1; index < buildOrder.length; index++) { const nextProject = buildOrder[index]; const nextProjectPath = toResolvedConfigFilePath(nextProject); if (projectPendingBuild.has(nextProjectPath)) continue; @@ -1393,8 +1393,7 @@ namespace ts { projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None)); while (hasPendingInvalidatedProjects()) { - const buildProject = getNextInvalidatedProject()!; - buildSingleInvalidatedProject(buildProject.project, buildProject.reloadLevel); + buildNextInvalidatedProject(); } reportErrorSummary(); host.readFile = originalReadFile; From 1a75c62ceb077454b3d2e5bd652690da136f54e6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 19 Apr 2019 15:37:23 -0700 Subject: [PATCH 020/111] Remove resetBuildContext --- src/compiler/tsbuild.ts | 29 ++----------------- src/testRunner/unittests/tsbuild/outFile.ts | 14 ++++----- .../unittests/tsbuild/resolveJsonModule.ts | 12 ++++---- src/testRunner/unittests/tsbuild/sample.ts | 9 +++--- 4 files changed, 19 insertions(+), 45 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 1b298f248f2..5d36be72314 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -260,7 +260,6 @@ namespace ts { // Currently used for testing but can be made public if needed: /*@internal*/ getBuildOrder(): ReadonlyArray; - /*@internal*/ resetBuildContext(opts?: BuildOptions): void; // Testing only /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; @@ -337,8 +336,8 @@ namespace ts { const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host); // State of the solution - let options = defaultOptions; - let baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); + const options = defaultOptions; + const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); const resolvedConfigFilePaths = createMap(); type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; const configFileCache = createMap() as ConfigFileMap; @@ -380,7 +379,6 @@ namespace ts { buildAllProjects, cleanAllProjects, getBuildOrder, - resetBuildContext, getUpToDateStatusOfProject, invalidateProject, buildInvalidatedProject, @@ -399,29 +397,6 @@ namespace ts { return resolvedPath; } - function resetBuildContext(opts?: BuildOptions) { - options = opts || defaultOptions; - baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); - resolvedConfigFilePaths.clear(); - configFileCache.clear(); - projectStatus.clear(); - buildOrder = undefined; - buildInfoChecked.clear(); - - diagnostics.clear(); - projectPendingBuild.clear(); - projectErrorsReported.clear(); - if (timerToBuildInvalidatedProject) { - clearTimeout(timerToBuildInvalidatedProject); - timerToBuildInvalidatedProject = undefined; - } - reportFileChangeDetected = false; - clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf)); - clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher)); - clearMap(allWatchedConfigFiles, closeFileWatcher); - builderPrograms.clear(); - } - function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { return !!(entry as ParsedCommandLine).options; } diff --git a/src/testRunner/unittests/tsbuild/outFile.ts b/src/testRunner/unittests/tsbuild/outFile.ts index 48beeef4f61..20061c86a7b 100644 --- a/src/testRunner/unittests/tsbuild/outFile.ts +++ b/src/testRunner/unittests/tsbuild/outFile.ts @@ -388,7 +388,7 @@ namespace ts { ...outputFiles[project.third] ]; const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host); + let builder = createSolutionBuilder(host); builder.buildAllProjects(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist @@ -398,7 +398,7 @@ namespace ts { // Delete bundle info host.clearDiagnostics(); host.deleteFile(outputFiles[project.first][ext.buildinfo]); - builder.resetBuildContext(); + builder = createSolutionBuilder(host); builder.buildAllProjects(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), @@ -428,11 +428,11 @@ namespace ts { it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { const fs = outFileFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host); + let builder = createSolutionBuilder(host); builder.buildAllProjects(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); host.clearDiagnostics(); - builder.resetBuildContext(); + builder = createSolutionBuilder(host); changeCompilerVersion(host); builder.buildAllProjects(); host.assertDiagnosticMessages( @@ -453,7 +453,7 @@ namespace ts { // Build with command line incremental const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, { incremental: true }); + let builder = createSolutionBuilder(host, { incremental: true }); builder.buildAllProjects(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); host.clearDiagnostics(); @@ -461,7 +461,7 @@ namespace ts { // Make non incremental build with change in file that doesnt affect dts appendText(fs, relSources[project.first][source.ts][part.one], "console.log(s);"); - builder.resetBuildContext({ verbose: true }); + builder = createSolutionBuilder(host, { verbose: true }); builder.buildAllProjects(); host.assertDiagnosticMessages(getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.first][source.config], relOutputFiles[project.first][ext.js], relSources[project.first][source.ts][part.one]], @@ -475,7 +475,7 @@ namespace ts { // Make incremental build with change in file that doesnt affect dts appendText(fs, relSources[project.first][source.ts][part.one], "console.log(s);"); - builder.resetBuildContext({ verbose: true, incremental: true }); + builder = createSolutionBuilder(host, { verbose: true, incremental: true }); builder.buildAllProjects(); // Builds completely because tsbuildinfo is old. host.assertDiagnosticMessages( diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index 3fc50d72ea5..d2555a730a2 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -64,7 +64,7 @@ export default hello.hello`); const configFile = "src/tsconfig_withFiles.json"; replaceText(fs, configFile, `"composite": true,`, `"composite": true, "sourceMap": true,`); const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, [configFile], { verbose: true }); + let builder = createSolutionBuilder(host, [configFile], { verbose: true }); builder.buildAllProjects(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(configFile), @@ -75,7 +75,7 @@ export default hello.hello`); assert(fs.existsSync(output), `Expect file ${output} to exist`); } host.clearDiagnostics(); - builder.resetBuildContext(); + builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); builder.buildAllProjects(); host.assertDiagnosticMessages( @@ -89,7 +89,7 @@ export default hello.hello`); const configFile = "src/tsconfig_withFiles.json"; replaceText(fs, configFile, `"outDir": "dist",`, ""); const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, [configFile], { verbose: true }); + let builder = createSolutionBuilder(host, [configFile], { verbose: true }); builder.buildAllProjects(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(configFile), @@ -100,7 +100,7 @@ export default hello.hello`); assert(fs.existsSync(output), `Expect file ${output} to exist`); } host.clearDiagnostics(); - builder.resetBuildContext(); + builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); builder.buildAllProjects(); host.assertDiagnosticMessages( @@ -128,7 +128,7 @@ export default hello.hello`); const stringsConfigFile = "src/strings/tsconfig.json"; const mainConfigFile = "src/main/tsconfig.json"; const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, [configFile], { verbose: true }); + let builder = createSolutionBuilder(host, [configFile], { verbose: true }); builder.buildAllProjects(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(stringsConfigFile, mainConfigFile, configFile), @@ -139,7 +139,7 @@ export default hello.hello`); ); assert(fs.existsSync(expectedOutput), `Expect file ${expectedOutput} to exist`); host.clearDiagnostics(); - builder.resetBuildContext(); + builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); builder.buildAllProjects(); host.assertDiagnosticMessages( diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index c004f53d38e..0682284e1d7 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -171,11 +171,11 @@ namespace ts { function initializeWithBuild(opts?: BuildOptions) { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); builder.buildAllProjects(); host.clearDiagnostics(); tick(); - builder.resetBuildContext(opts ? { ...opts, verbose: true } : undefined); + builder = createSolutionBuilder(host, ["/src/tests"], { ...(opts || {}), verbose: true }); return { fs, host, builder }; } @@ -183,7 +183,6 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.resetBuildContext(); builder.buildAllProjects(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), @@ -288,7 +287,7 @@ namespace ts { fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } })); replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`); const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); builder.buildAllProjects(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), @@ -301,7 +300,7 @@ namespace ts { ); host.clearDiagnostics(); tick(); - builder.resetBuildContext(); + builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} })); builder.buildAllProjects(); host.assertDiagnosticMessages( From 5b361c8497d2cc25d0e25f97c11e5c2394429a2a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 May 2019 13:27:36 -0700 Subject: [PATCH 021/111] Make API to build project and wire cancellation token --- src/compiler/tsbuild.ts | 153 ++++++++++++------ src/compiler/types.ts | 3 + src/compiler/watch.ts | 29 ++-- .../unittests/tsbuild/emptyFiles.ts | 4 +- src/testRunner/unittests/tsbuild/helpers.ts | 2 +- .../unittests/tsbuild/missingExtendedFile.ts | 2 +- src/testRunner/unittests/tsbuild/outFile.ts | 37 +++-- .../tsbuild/referencesWithRootDirInParent.ts | 8 +- .../unittests/tsbuild/resolveJsonModule.ts | 14 +- src/testRunner/unittests/tsbuild/sample.ts | 80 +++++---- .../unittests/tsbuild/transitiveReferences.ts | 2 +- src/testRunner/unittests/tsbuildWatchMode.ts | 12 +- .../unittests/tsserver/projectReferences.ts | 2 +- src/tsc/tsc.ts | 4 +- 14 files changed, 234 insertions(+), 118 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index dd9b62edd7a..4fb2edf6801 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -255,7 +255,7 @@ namespace ts { } export interface SolutionBuilder { - buildAllProjects(): ExitStatus; + build(project?: string, cancellationToken?: CancellationToken): ExitStatus; cleanAllProjects(): ExitStatus; // Currently used for testing but can be made public if needed: @@ -264,14 +264,21 @@ namespace ts { // Testing only /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; - /*@internal*/ buildInvalidatedProject(): void; + /*@internal*/ buildNextInvalidatedProject(): void; } export interface SolutionBuilderWithWatch { - buildAllProjects(): ExitStatus; + build(project?: string, cancellationToken?: CancellationToken): ExitStatus; /*@internal*/ startWatching(): void; } + interface InvalidatedProject { + project: ResolvedConfigFileName; + projectPath: ResolvedConfigFilePath; + reloadLevel: ConfigFileProgramReloadLevel; + projectIndex: number; + } + /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic */ @@ -386,18 +393,22 @@ namespace ts { const allWatchedInputFiles = createMap() as ConfigFileMap>; const allWatchedConfigFiles = createMap() as ConfigFileMap; + let allProjectBuildPending = true; + let needsSummary = true; + // let watchAllProjectsPending = watch; + return watch ? { - buildAllProjects, + build, startWatching } : { - buildAllProjects, + build, cleanAllProjects, getBuildOrder, getUpToDateStatusOfProject, invalidateProject, - buildInvalidatedProject, + buildNextInvalidatedProject, }; function toPath(fileName: string) { @@ -800,6 +811,7 @@ namespace ts { configFileCache.delete(resolved); buildOrder = undefined; } + needsSummary = true; clearProjectStatus(resolved); addProjToQueue(resolved, reloadLevel); enableCache(); @@ -823,16 +835,16 @@ namespace ts { } } - function getNextInvalidatedProject() { - Debug.assert(hasPendingInvalidatedProjects()); - return forEach(getBuildOrder(), (project, projectIndex) => { - const projectPath = toResolvedConfigFilePath(project); - const reloadLevel = projectPendingBuild.get(projectPath); - if (reloadLevel !== undefined) { - projectPendingBuild.delete(projectPath); - return { project, projectPath, reloadLevel, projectIndex }; - } - }); + function getNextInvalidatedProject(buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { + return hasPendingInvalidatedProjects() ? + forEach(buildOrder, (project, projectIndex) => { + const projectPath = toResolvedConfigFilePath(project); + const reloadLevel = projectPendingBuild.get(projectPath); + if (reloadLevel !== undefined) { + return { project, projectPath, reloadLevel, projectIndex }; + } + }) : + undefined; } function hasPendingInvalidatedProjects() { @@ -846,18 +858,19 @@ namespace ts { if (timerToBuildInvalidatedProject) { hostWithWatch.clearTimeout(timerToBuildInvalidatedProject); } - timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildInvalidatedProject, 250); + timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250); } - function buildInvalidatedProject() { + function buildNextInvalidatedProject() { timerToBuildInvalidatedProject = undefined; if (reportFileChangeDetected) { reportFileChangeDetected = false; projectErrorsReported.clear(); reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation); } - if (hasPendingInvalidatedProjects()) { - buildNextInvalidatedProject(); + const invalidatedProject = getNextInvalidatedProject(getBuildOrder()); + if (invalidatedProject) { + buildInvalidatedProject(invalidatedProject); if (hasPendingInvalidatedProjects()) { if (watch && !timerToBuildInvalidatedProject) { scheduleBuildInvalidatedProject(); @@ -872,6 +885,7 @@ namespace ts { function reportErrorSummary() { if (watch || host.reportErrorSummary) { + needsSummary = false; // Report errors from the other projects getBuildOrder().forEach(project => { const projectPath = toResolvedConfigFilePath(project); @@ -890,11 +904,11 @@ namespace ts { } } - function buildNextInvalidatedProject() { - const { project, projectPath, reloadLevel, projectIndex } = getNextInvalidatedProject()!; + function buildInvalidatedProject({ project, projectPath, reloadLevel, projectIndex }: InvalidatedProject, cancellationToken?: CancellationToken) { const config = parseConfigFile(project, projectPath); if (!config) { reportParseConfigFileDiagnostic(projectPath); + projectPendingBuild.delete(projectPath); return; } @@ -920,6 +934,7 @@ namespace ts { // In a dry build, inform the user of this fact reportStatus(Diagnostics.Project_0_is_up_to_date, project); } + projectPendingBuild.delete(projectPath); return; } @@ -927,24 +942,28 @@ namespace ts { reportAndStoreErrors(projectPath, config.errors); // Fake that files have been built by updating output file stamps updateOutputTimestamps(config, projectPath); + projectPendingBuild.delete(projectPath); return; } if (status.type === UpToDateStatusType.UpstreamBlocked) { reportAndStoreErrors(projectPath, config.errors); if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName); + projectPendingBuild.delete(projectPath); return; } if (status.type === UpToDateStatusType.ContainerOnly) { reportAndStoreErrors(projectPath, config.errors); // Do nothing + projectPendingBuild.delete(projectPath); return; } const buildResult = needsBuild(status, config) ? - buildSingleProject(project, projectPath) : // Actual build - updateBundle(project, projectPath); // Fake that files have been built by manipulating prepend and existing output + buildSingleProject(project, projectPath, cancellationToken) : // Actual build + updateBundle(project, projectPath, cancellationToken); // Fake that files have been built by manipulating prepend and existing output + projectPendingBuild.delete(projectPath); // Only composite projects can be referenced by other projects if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { queueReferencingProjects(project, projectPath, projectIndex, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); @@ -1050,7 +1069,7 @@ namespace ts { } } - function buildSingleProject(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath): BuildResultFlags { + function buildSingleProject(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { if (options.dry) { reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj); return BuildResultFlags.Success; @@ -1112,15 +1131,15 @@ namespace ts { // Don't emit anything in the presence of syntactic errors or options diagnostics const syntaxDiagnostics = [ ...program.getConfigFileParsingDiagnostics(), - ...program.getOptionsDiagnostics(), - ...program.getGlobalDiagnostics(), - ...program.getSyntacticDiagnostics()]; + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)]; if (syntaxDiagnostics.length) { return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic"); } // Same as above but now for semantic diagnostics - const semanticDiagnostics = program.getSemanticDiagnostics(); + const semanticDiagnostics = program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken); if (semanticDiagnostics.length) { return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic"); } @@ -1132,7 +1151,14 @@ namespace ts { let declDiagnostics: Diagnostic[] | undefined; const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); const outputFiles: OutputFile[] = []; - emitFilesAndReportErrors(program, reportDeclarationDiagnostics, /*writeFileName*/ undefined, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark })); + emitFilesAndReportErrors( + program, + reportDeclarationDiagnostics, + /*writeFileName*/ undefined, + /*reportSummary*/ undefined, + (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), + cancellationToken + ); // Don't emit .d.ts if there are decl file errors if (declDiagnostics) { program.restoreState(); @@ -1221,7 +1247,7 @@ namespace ts { return readBuilderProgram(parsed.options, readFileWithCache) as any as T; } - function updateBundle(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath): BuildResultFlags { + function updateBundle(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { if (options.dry) { reportStatus(Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); return BuildResultFlags.Success; @@ -1241,7 +1267,7 @@ namespace ts { }); if (isString(outputFiles)) { reportStatus(Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(outputFiles)); - return buildSingleProject(proj, resolvedPath); + return buildSingleProject(proj, resolvedPath, cancellationToken); } // Actual Emit @@ -1403,21 +1429,58 @@ namespace ts { cacheState = undefined; } - function buildAllProjects(): ExitStatus { - if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } - enableCache(); + function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { + // Set initial build if not already built + if (allProjectBuildPending) { + allProjectBuildPending = false; + if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } + enableCache(); + const buildOrder = getBuildOrder(); + reportBuildQueue(buildOrder); + buildOrder.forEach(configFileName => + projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None)); - const buildOrder = getBuildOrder(); - reportBuildQueue(buildOrder); - buildOrder.forEach(configFileName => - projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None)); - - while (hasPendingInvalidatedProjects()) { - buildNextInvalidatedProject(); + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); + } } - reportErrorSummary(); - disableCache(); - return diagnostics.size ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; + + let successfulProjects = 0; + let errorProjects = 0; + const resolvedProject = project && resolveProjectName(project); + if (resolvedProject) { + const projectPath = toResolvedConfigFilePath(resolvedProject); + const projectIndex = findIndex( + getBuildOrder(), + configFileName => toResolvedConfigFilePath(configFileName) === projectPath + ); + if (projectIndex === -1) return ExitStatus.InvalidProject_OutputsSkipped; + } + const buildOrder = resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder(); + while (true) { + const invalidatedProject = getNextInvalidatedProject(buildOrder); + if (!invalidatedProject) { + if (needsSummary) { + disableCache(); + reportErrorSummary(); + } + break; + } + + buildInvalidatedProject(invalidatedProject, cancellationToken); + if (diagnostics.has(invalidatedProject.projectPath)) { + errorProjects++; + } + else { + successfulProjects++; + } + } + + return errorProjects ? + successfulProjects ? + ExitStatus.DiagnosticsPresent_OutputsGenerated : + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success; } function needsBuild(status: UpToDateStatus, config: ParsedCommandLine) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5e2d6aba474..791b26ab572 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3053,6 +3053,9 @@ namespace ts { // Diagnostics were produced and outputs were generated in spite of them. DiagnosticsPresent_OutputsGenerated = 2, + + // When build skipped because passed in project is invalid + InvalidProject_OutputsSkipped = 3, } export interface EmitResult { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index f9bf2a8468d..d6a856cfe9f 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -113,12 +113,12 @@ namespace ts { getCurrentDirectory(): string; getCompilerOptions(): CompilerOptions; getSourceFiles(): ReadonlyArray; - getSyntacticDiagnostics(): ReadonlyArray; - getOptionsDiagnostics(): ReadonlyArray; - getGlobalDiagnostics(): ReadonlyArray; - getSemanticDiagnostics(): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; getConfigFileParsingDiagnostics(): ReadonlyArray; - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; } export function listFiles(program: ProgramToEmitFilesAndReportErrors, writeFileName: (s: string) => void) { @@ -132,25 +132,32 @@ namespace ts { /** * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options */ - export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) { + export function emitFilesAndReportErrors( + program: ProgramToEmitFilesAndReportErrors, + reportDiagnostic: DiagnosticReporter, + writeFileName?: (s: string) => void, + reportSummary?: ReportEmitErrorSummary, + writeFile?: WriteFileCallback, + cancellationToken?: CancellationToken + ) { // First get and report any syntactic errors. const diagnostics = program.getConfigFileParsingDiagnostics().slice(); const configFileParsingDiagnosticsLength = diagnostics.length; - addRange(diagnostics, program.getSyntacticDiagnostics()); + addRange(diagnostics, program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)); // If we didn't have any syntactic errors, then also try getting the global and // semantic errors. if (diagnostics.length === configFileParsingDiagnosticsLength) { - addRange(diagnostics, program.getOptionsDiagnostics()); - addRange(diagnostics, program.getGlobalDiagnostics()); + addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); + addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); if (diagnostics.length === configFileParsingDiagnosticsLength) { - addRange(diagnostics, program.getSemanticDiagnostics()); + addRange(diagnostics, program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken)); } } // Emit and report any errors we ran into. - const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile); + const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken); addRange(diagnostics, emitDiagnostics); sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); diff --git a/src/testRunner/unittests/tsbuild/emptyFiles.ts b/src/testRunner/unittests/tsbuild/emptyFiles.ts index 17c6644ccc6..badad8f3377 100644 --- a/src/testRunner/unittests/tsbuild/emptyFiles.ts +++ b/src/testRunner/unittests/tsbuild/emptyFiles.ts @@ -14,7 +14,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/no-references"], { dry: false, force: false, verbose: false }); host.clearDiagnostics(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages([Diagnostics.The_files_list_in_config_file_0_is_empty, "/src/no-references/tsconfig.json"]); // Check for outputs to not be written. @@ -29,7 +29,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/with-references"], { dry: false, force: false, verbose: false }); host.clearDiagnostics(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(/*empty*/); // Check for outputs to be written. diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 579573a876e..33a7afb181a 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -172,7 +172,7 @@ declare const console: { log(msg: any): void; };`; } return originalReadFile.call(host, path); }; - builder.buildAllProjects(); + builder.build(); generateSourceMapBaselineFiles(fs, expectedMapFileNames); generateBuildInfoSectionBaselineFiles(fs, expectedBuildInfoFilesForSectionBaselines || emptyArray); fs.makeReadonly(); diff --git a/src/testRunner/unittests/tsbuild/missingExtendedFile.ts b/src/testRunner/unittests/tsbuild/missingExtendedFile.ts index 00e8bfec1e3..881f3fbc030 100644 --- a/src/testRunner/unittests/tsbuild/missingExtendedFile.ts +++ b/src/testRunner/unittests/tsbuild/missingExtendedFile.ts @@ -5,7 +5,7 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], {}); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( [Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"], [Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.first.json", "[\"**/*\"]", "[]"], diff --git a/src/testRunner/unittests/tsbuild/outFile.ts b/src/testRunner/unittests/tsbuild/outFile.ts index 20061c86a7b..ea56c7f511d 100644 --- a/src/testRunner/unittests/tsbuild/outFile.ts +++ b/src/testRunner/unittests/tsbuild/outFile.ts @@ -363,7 +363,7 @@ namespace ts { ]; const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist for (const output of expectedOutputs) { @@ -389,7 +389,7 @@ namespace ts { ]; const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist for (const output of expectedOutputs) { @@ -399,7 +399,7 @@ namespace ts { host.clearDiagnostics(); host.deleteFile(outputFiles[project.first][ext.buildinfo]); builder = createSolutionBuilder(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[project.first][source.config], relOutputFiles[project.first][ext.buildinfo]], @@ -416,7 +416,7 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); replaceText(fs, sources[project.third][source.config], `"composite": true,`, ""); const builder = createSolutionBuilder(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist - without tsbuildinfo for third project for (const output of expectedOutputFiles.slice(0, expectedOutputFiles.length - 2)) { @@ -429,12 +429,12 @@ namespace ts { const fs = outFileFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); host.clearDiagnostics(); builder = createSolutionBuilder(host); changeCompilerVersion(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.first][source.config], fakes.version, version], @@ -454,7 +454,7 @@ namespace ts { // Build with command line incremental const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, { incremental: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); host.clearDiagnostics(); tick(); @@ -462,7 +462,7 @@ namespace ts { // Make non incremental build with change in file that doesnt affect dts appendText(fs, relSources[project.first][source.ts][part.one], "console.log(s);"); builder = createSolutionBuilder(host, { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), [Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.first][source.config], relOutputFiles[project.first][ext.js], relSources[project.first][source.ts][part.one]], [Diagnostics.Building_project_0, sources[project.first][source.config]], @@ -476,7 +476,7 @@ namespace ts { // Make incremental build with change in file that doesnt affect dts appendText(fs, relSources[project.first][source.ts][part.one], "console.log(s);"); builder = createSolutionBuilder(host, { verbose: true, incremental: true }); - builder.buildAllProjects(); + builder.build(); // Builds completely because tsbuildinfo is old. host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), @@ -489,6 +489,23 @@ namespace ts { host.clearDiagnostics(); }); + it("builds till project specified", () => { + const fs = outFileFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, { verbose: false }); + const result = builder.build(sources[project.second][source.config]); + host.assertDiagnosticMessages(/*empty*/); + // First and Third is not built + for (const output of [...outputFiles[project.first], ...outputFiles[project.third]]) { + assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); + } + // second is built + for (const output of outputFiles[project.second]) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); + } + assert.equal(result, ExitStatus.Success); + }); + describe("Prepend output with .tsbuildinfo", () => { // Prologues describe("Prologues", () => { @@ -904,7 +921,7 @@ ${internal} enum internalEnum { a, b, c }`); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[project.first][source.config], "src/first/first_PART1.js"], diff --git a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts index 630cd8575ec..c965eb73067 100644 --- a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts +++ b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts @@ -19,7 +19,7 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/src/main", "/src/src/other"], {}); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(/*empty*/); for (const output of allExpectedOutputs) { assert(fs.existsSync(output), `Expect file ${output} to exist`); @@ -39,7 +39,7 @@ namespace ts { replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, ""); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/src/main"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/src/other/tsconfig.json", "src/src/main/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/other/tsconfig.json", "src/dist/other.js"], @@ -75,7 +75,7 @@ namespace ts { })); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/src/main"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/src/other/tsconfig.json", "src/src/main/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/other/tsconfig.json", "src/dist/other.js"], @@ -112,7 +112,7 @@ namespace ts { })); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/src/main/tsconfig.main.json"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/src/other/tsconfig.other.json", "src/src/main/tsconfig.main.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/other/tsconfig.other.json", "src/dist/other.js"], diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index d2555a730a2..f1a40c050f9 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -19,7 +19,7 @@ namespace ts { function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: ReadonlyArray, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) { const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...expectedDiagnosticMessages); if (!expectedDiagnosticMessages.length) { // Check for outputs. Not an exhaustive list @@ -65,7 +65,7 @@ export default hello.hello`); replaceText(fs, configFile, `"composite": true,`, `"composite": true, "sourceMap": true,`); const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, [configFile], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(configFile), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFile, "src/dist/src/index.js"], @@ -77,7 +77,7 @@ export default hello.hello`); host.clearDiagnostics(); builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(configFile), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFile, "src/src/index.ts", "src/dist/src/index.js"] @@ -90,7 +90,7 @@ export default hello.hello`); replaceText(fs, configFile, `"outDir": "dist",`, ""); const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, [configFile], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(configFile), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFile, "src/src/index.js"], @@ -102,7 +102,7 @@ export default hello.hello`); host.clearDiagnostics(); builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(configFile), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFile, "src/src/index.ts", "src/src/index.js"] @@ -129,7 +129,7 @@ export default hello.hello`); const mainConfigFile = "src/main/tsconfig.json"; const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, [configFile], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(stringsConfigFile, mainConfigFile, configFile), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, stringsConfigFile, "src/strings/tsconfig.tsbuildinfo"], @@ -141,7 +141,7 @@ export default hello.hello`); host.clearDiagnostics(); builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild(stringsConfigFile, mainConfigFile, configFile), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, stringsConfigFile, "src/strings/foo.json", "src/strings/tsconfig.tsbuildinfo"], diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 9119fd2fb96..3ad2b2e3eec 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -21,7 +21,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); host.clearDiagnostics(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(/*empty*/); // Check for outputs. Not an exhaustive list @@ -39,7 +39,7 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], {}); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(/*empty*/); const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/", "/logic/outDir/")); // Check for outputs. Not an exhaustive list @@ -57,7 +57,7 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], {}); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(/*empty*/); const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/index.d.ts", "/logic/out/decls/index.d.ts")); // Check for outputs. Not an exhaustive list @@ -71,7 +71,7 @@ namespace ts { replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, ""); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/core"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], @@ -88,7 +88,7 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( [Diagnostics.A_non_dry_build_would_build_project_0, "/src/core/tsconfig.json"], [Diagnostics.A_non_dry_build_would_build_project_0, "/src/logic/tsconfig.json"], @@ -106,12 +106,12 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); - builder.buildAllProjects(); + builder.build(); tick(); host.clearDiagnostics(); builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( [Diagnostics.Project_0_is_up_to_date, "/src/core/tsconfig.json"], [Diagnostics.Project_0_is_up_to_date, "/src/logic/tsconfig.json"], @@ -126,7 +126,7 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); - builder.buildAllProjects(); + builder.build(); // Verify they exist for (const output of allExpectedOutputs) { assert(fs.existsSync(output), `Expect file ${output} to exist`); @@ -146,15 +146,16 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); - const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false }); - builder.buildAllProjects(); + let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false }); + builder.build(); let currentTime = time(); checkOutputTimestamps(currentTime); tick(); Debug.assert(time() !== currentTime, "Time moves on"); currentTime = time(); - builder.buildAllProjects(); + builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false }); + builder.build(); checkOutputTimestamps(currentTime); function checkOutputTimestamps(expected: number) { @@ -172,7 +173,7 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.clearDiagnostics(); tick(); builder = createSolutionBuilder(host, ["/src/tests"], { ...(opts || {}), verbose: true }); @@ -183,7 +184,7 @@ namespace ts { const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], @@ -198,7 +199,7 @@ namespace ts { // All three projects are up to date it("Detects that all projects are up to date", () => { const { host, builder } = initializeWithBuild(); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], @@ -211,7 +212,7 @@ namespace ts { it("Only builds the leaf node project", () => { const { fs, host, builder } = initializeWithBuild(); fs.writeFileSync("/src/tests/index.ts", "const m = 10;"); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], @@ -225,7 +226,7 @@ namespace ts { it("Detects type-only changes in upstream projects", () => { const { fs, host, builder } = initializeWithBuild(); replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), @@ -242,7 +243,7 @@ namespace ts { it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { const { host, builder } = initializeWithBuild(); changeCompilerVersion(host); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/core/tsconfig.json", fakes.version, version], @@ -256,7 +257,7 @@ namespace ts { it("rebuilds from start if --f is passed", () => { const { host, builder } = initializeWithBuild({ force: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], @@ -271,7 +272,7 @@ namespace ts { it("rebuilds when tsconfig changes", () => { const { fs, host, builder } = initializeWithBuild(); replaceText(fs, "/src/tests/tsconfig.json", `"composite": true`, `"composite": true, "target": "es3"`); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], @@ -287,7 +288,7 @@ namespace ts { replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`); const host = new fakes.SolutionBuilderHost(fs); let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], @@ -301,7 +302,7 @@ namespace ts { tick(); builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: {} })); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], @@ -310,6 +311,31 @@ namespace ts { [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"] ); }); + + it("builds till project specified", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + const result = builder.build("/src/logic"); + host.assertDiagnosticMessages(/*empty*/); + assert.isFalse(fs.existsSync(allExpectedOutputs[0]), `Expect file ${allExpectedOutputs[0]} to not exist`); + for (const output of allExpectedOutputs.slice(1)) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); + } + assert.equal(result, ExitStatus.Success); + }); + + it("building project in not build order doesnt throw error", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + const result = builder.build("/src/logic2"); + host.assertDiagnosticMessages(/*empty*/); + for (const output of allExpectedOutputs) { + assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); + } + assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); + }); }); describe("downstream-blocked compilations", () => { @@ -320,7 +346,7 @@ namespace ts { // Induce an error in the middle project replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages( getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], @@ -340,7 +366,7 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(/*empty*/); // Update a timestamp in the middle project @@ -366,7 +392,7 @@ export class cNew {}`); // Rebuild this project tick(); builder.invalidateProject("/src/logic"); - builder.buildInvalidatedProject(); + builder.buildNextInvalidatedProject(); // The file should be updated assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); @@ -376,7 +402,7 @@ export class cNew {}`); // Build downstream projects should update 'tests', but not 'core' tick(); - builder.buildInvalidatedProject(); + builder.buildNextInvalidatedProject(); if (expectedToWriteTests) { assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt"); } @@ -394,7 +420,7 @@ export class cNew {}`); const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { listFiles: true }); - builder.buildAllProjects(); + builder.build(); assert.deepEqual(host.traces, [ "/lib/lib.d.ts", "/src/core/anotherModule.ts", @@ -421,7 +447,7 @@ export class cNew {}`); const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], { listEmittedFiles: true }); - builder.buildAllProjects(); + builder.build(); assert.deepEqual(host.traces, [ "TSFILE: /src/core/anotherModule.js", "TSFILE: /src/core/anotherModule.d.ts.map", diff --git a/src/testRunner/unittests/tsbuild/transitiveReferences.ts b/src/testRunner/unittests/tsbuild/transitiveReferences.ts index 30e28de8cde..94f364ddec0 100644 --- a/src/testRunner/unittests/tsbuild/transitiveReferences.ts +++ b/src/testRunner/unittests/tsbuild/transitiveReferences.ts @@ -30,7 +30,7 @@ namespace ts { const host = new fakes.SolutionBuilderHost(fs); modifyDiskLayout(fs); const builder = createSolutionBuilder(host, ["/src/tsconfig.c.json"], { listFiles: true }); - builder.buildAllProjects(); + builder.build(); host.assertDiagnosticMessages(...expectedDiagnostics); for (const output of allExpectedOutputs) { assert(fs.existsSync(output), `Expect file ${output} to exist`); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index ef87aa1f9cb..ad00145db52 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -24,7 +24,7 @@ namespace ts.tscWatch { function createSolutionBuilderWithWatch(system: TsBuildWatchSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { const host = createSolutionBuilderWithWatchHost(system); const solutionBuilder = ts.createSolutionBuilderWithWatch(host, rootNames, defaultOptions || { watch: true }); - solutionBuilder.buildAllProjects(); + solutionBuilder.build(); solutionBuilder.startWatching(); return solutionBuilder; } @@ -608,7 +608,7 @@ let x: string = 10;`); // Build the composite project const host = createTsBuildWatchSystem(allFiles, { currentDirectory }); const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {}); - solutionBuilder.buildAllProjects(); + solutionBuilder.build(); const outputFileStamps = getOutputFileStamps(host); for (const stamp of outputFileStamps) { assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); @@ -723,7 +723,7 @@ let x: string = 10;`); function foo() { }`); solutionBuilder.invalidateProject(`${project}/${SubProject.logic}`); - solutionBuilder.buildInvalidatedProject(); + solutionBuilder.buildNextInvalidatedProject(); // not ideal, but currently because of d.ts but no new file is written // There will be timeout queued even though file contents are same @@ -736,7 +736,7 @@ function foo() { export function gfoo() { }`); solutionBuilder.invalidateProject(logic[0].path); - solutionBuilder.buildInvalidatedProject(); + solutionBuilder.buildNextInvalidatedProject(); }, expectedProgramFiles); }); @@ -747,7 +747,7 @@ export function gfoo() { references: [{ path: "../core" }] })); solutionBuilder.invalidateProject(logic[0].path, ConfigFileProgramReloadLevel.Full); - solutionBuilder.buildInvalidatedProject(); + solutionBuilder.buildNextInvalidatedProject(); }, [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]); }); }); @@ -967,7 +967,7 @@ export function gfoo() { export function gfoo() { }`); solutionBuilder.invalidateProject(bTsconfig.path); - solutionBuilder.buildInvalidatedProject(); + solutionBuilder.buildNextInvalidatedProject(); }, emptyArray, expectedProgramFiles, diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index e39c510216d..f07774cd43c 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -5,7 +5,7 @@ namespace ts.projectSystem { // ts build should succeed const solutionBuilder = tscWatch.createSolutionBuilder(host, rootNames, {}); - solutionBuilder.buildAllProjects(); + solutionBuilder.build(); assert.equal(host.getOutput().length, 0); return host; diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 35bd29ea774..fc5559be023 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -216,7 +216,7 @@ namespace ts { updateCreateProgram(buildHost); buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions); - builder.buildAllProjects(); + builder.build(); return builder.startWatching(); } else { @@ -224,7 +224,7 @@ namespace ts { updateCreateProgram(buildHost); buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); const builder = createSolutionBuilder(buildHost, projects, buildOptions); - return sys.exit(buildOptions.clean ? builder.cleanAllProjects() : builder.buildAllProjects()); + return sys.exit(buildOptions.clean ? builder.cleanAllProjects() : builder.build()); } } From e8074f7fdc3aac4fd8b6e23f0067800685420d1f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 May 2019 14:30:59 -0700 Subject: [PATCH 022/111] Rename cleanAll to clean and take optional project as input --- src/compiler/tsbuild.ts | 59 ++++++++------- .../unittests/tsbuild/emptyFiles.ts | 8 +-- src/testRunner/unittests/tsbuild/helpers.ts | 12 ++++ src/testRunner/unittests/tsbuild/outFile.ts | 48 ++++++------- .../tsbuild/referencesWithRootDirInParent.ts | 24 ++----- .../unittests/tsbuild/resolveJsonModule.ts | 14 ++-- src/testRunner/unittests/tsbuild/sample.ts | 72 +++++++++++-------- .../unittests/tsbuild/transitiveReferences.ts | 4 +- src/tsc/tsc.ts | 2 +- 9 files changed, 124 insertions(+), 119 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 4fb2edf6801..c075e1f799c 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -256,7 +256,7 @@ namespace ts { export interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; - cleanAllProjects(): ExitStatus; + clean(project?: string): ExitStatus; // Currently used for testing but can be made public if needed: /*@internal*/ getBuildOrder(): ReadonlyArray; @@ -404,7 +404,7 @@ namespace ts { } : { build, - cleanAllProjects, + clean, getBuildOrder, getUpToDateStatusOfProject, invalidateProject, @@ -1342,10 +1342,12 @@ namespace ts { return priorNewestUpdateTime; } - function getFilesToClean(): string[] { - // Get the same graph for cleaning we'd use for building - const filesToDelete: string[] = []; - for (const proj of getBuildOrder()) { + function clean(project?: string) { + const buildOrder = getBuildOrderFor(project); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + const filesToDelete = options.dry ? [] as string[] : undefined; + for (const proj of buildOrder) { const resolvedPath = toResolvedConfigFilePath(proj); const parsed = parseConfigFile(proj, resolvedPath); if (parsed === undefined) { @@ -1356,22 +1358,19 @@ namespace ts { const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); for (const output of outputs) { if (host.fileExists(output)) { - filesToDelete.push(output); + if (filesToDelete) { + filesToDelete.push(output); + } + else { + host.deleteFile(output); + invalidateResolvedProject(resolvedPath, ConfigFileProgramReloadLevel.None); + } } } } - return filesToDelete; - } - function cleanAllProjects() { - const filesToDelete = getFilesToClean(); - if (options.dry) { + if (filesToDelete) { reportStatus(Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); - return ExitStatus.Success; - } - - for (const output of filesToDelete) { - host.deleteFile(output); } return ExitStatus.Success; @@ -1429,7 +1428,23 @@ namespace ts { cacheState = undefined; } + function getBuildOrderFor(project: string | undefined) { + const resolvedProject = project && resolveProjectName(project); + if (resolvedProject) { + const projectPath = toResolvedConfigFilePath(resolvedProject); + const projectIndex = findIndex( + getBuildOrder(), + configFileName => toResolvedConfigFilePath(configFileName) === projectPath + ); + if (projectIndex === -1) return undefined; + } + return resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder(); + } + function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { + const buildOrder = getBuildOrderFor(project); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + // Set initial build if not already built if (allProjectBuildPending) { allProjectBuildPending = false; @@ -1447,16 +1462,6 @@ namespace ts { let successfulProjects = 0; let errorProjects = 0; - const resolvedProject = project && resolveProjectName(project); - if (resolvedProject) { - const projectPath = toResolvedConfigFilePath(resolvedProject); - const projectIndex = findIndex( - getBuildOrder(), - configFileName => toResolvedConfigFilePath(configFileName) === projectPath - ); - if (projectIndex === -1) return ExitStatus.InvalidProject_OutputsSkipped; - } - const buildOrder = resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder(); while (true) { const invalidatedProject = getNextInvalidatedProject(buildOrder); if (!invalidatedProject) { diff --git a/src/testRunner/unittests/tsbuild/emptyFiles.ts b/src/testRunner/unittests/tsbuild/emptyFiles.ts index badad8f3377..38b8b4b5379 100644 --- a/src/testRunner/unittests/tsbuild/emptyFiles.ts +++ b/src/testRunner/unittests/tsbuild/emptyFiles.ts @@ -18,9 +18,7 @@ namespace ts { host.assertDiagnosticMessages([Diagnostics.The_files_list_in_config_file_0_is_empty, "/src/no-references/tsconfig.json"]); // Check for outputs to not be written. - for (const output of allExpectedOutputs) { - assert(!fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsAbsent(fs, allExpectedOutputs); }); it("does not have empty files diagnostic when files is empty and references are provided", () => { @@ -33,9 +31,7 @@ namespace ts { host.assertDiagnosticMessages(/*empty*/); // Check for outputs to be written. - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); }); }); } diff --git a/src/testRunner/unittests/tsbuild/helpers.ts b/src/testRunner/unittests/tsbuild/helpers.ts index 33a7afb181a..7aeaba36199 100644 --- a/src/testRunner/unittests/tsbuild/helpers.ts +++ b/src/testRunner/unittests/tsbuild/helpers.ts @@ -82,6 +82,18 @@ declare const console: { log(msg: any): void; };`; return fs; } + export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) { + for (const output of outputs) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); + } + } + + export function verifyOutputsAbsent(fs: vfs.FileSystem, outputs: readonly string[]) { + for (const output of outputs) { + assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); + } + } + function generateSourceMapBaselineFiles(fs: vfs.FileSystem, mapFileNames: ReadonlyArray) { for (const mapFile of mapFileNames) { if (!fs.existsSync(mapFile)) continue; diff --git a/src/testRunner/unittests/tsbuild/outFile.ts b/src/testRunner/unittests/tsbuild/outFile.ts index ea56c7f511d..f562d4dc26b 100644 --- a/src/testRunner/unittests/tsbuild/outFile.ts +++ b/src/testRunner/unittests/tsbuild/outFile.ts @@ -366,18 +366,14 @@ namespace ts { builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist - for (const output of expectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, expectedOutputs); host.clearDiagnostics(); - builder.cleanAllProjects(); + builder.clean(); host.assertDiagnosticMessages(/*none*/); // Verify they are gone - for (const output of expectedOutputs) { - assert(!fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsAbsent(fs, expectedOutputs); // Subsequent clean shouldn't throw / etc - builder.cleanAllProjects(); + builder.clean(); }); it("verify buildInfo absence results in new build", () => { @@ -392,9 +388,7 @@ namespace ts { builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist - for (const output of expectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, expectedOutputs); // Delete bundle info host.clearDiagnostics(); host.deleteFile(outputFiles[project.first][ext.buildinfo]); @@ -419,10 +413,8 @@ namespace ts { builder.build(); host.assertDiagnosticMessages(...initialExpectedDiagnostics); // Verify they exist - without tsbuildinfo for third project - for (const output of expectedOutputFiles.slice(0, expectedOutputFiles.length - 2)) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } - assert.isFalse(fs.existsSync(outputFiles[project.third][ext.buildinfo]), `Expect file ${outputFiles[project.third][ext.buildinfo]} to not exist`); + verifyOutputsPresent(fs, expectedOutputFiles.slice(0, expectedOutputFiles.length - 2)); + verifyOutputsAbsent(fs, [outputFiles[project.third][ext.buildinfo]]); }); it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => { @@ -496,13 +488,23 @@ namespace ts { const result = builder.build(sources[project.second][source.config]); host.assertDiagnosticMessages(/*empty*/); // First and Third is not built - for (const output of [...outputFiles[project.first], ...outputFiles[project.third]]) { - assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsAbsent(fs, [...outputFiles[project.first], ...outputFiles[project.third]]); // second is built - for (const output of outputFiles[project.second]) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, outputFiles[project.second]); + assert.equal(result, ExitStatus.Success); + }); + + it("cleans till project specified", () => { + const fs = outFileFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, { verbose: false }); + builder.build(); + const result = builder.clean(sources[project.second][source.config]); + host.assertDiagnosticMessages(/*empty*/); + // First and Third output for present + verifyOutputsPresent(fs, [...outputFiles[project.first], ...outputFiles[project.third]]); + // second is cleaned + verifyOutputsAbsent(fs, outputFiles[project.second]); assert.equal(result, ExitStatus.Success); }); @@ -940,9 +942,7 @@ ${internal} enum internalEnum { a, b, c }`); removeFileExtension(f) + Extension.Dts + ".map", ]) ]); - for (const output of expectedOutputFiles) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, expectedOutputFiles); }); }); } diff --git a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts index c965eb73067..186a43b3c3e 100644 --- a/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts +++ b/src/testRunner/unittests/tsbuild/referencesWithRootDirInParent.ts @@ -21,9 +21,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/src/main", "/src/src/other"], {}); builder.build(); host.assertDiagnosticMessages(/*empty*/); - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); }); it("verify that it reports error for same .tsbuildinfo file because no rootDir in the base", () => { @@ -48,12 +46,8 @@ namespace ts { [Diagnostics.Building_project_0, "/src/src/main/tsconfig.json"], [Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, "/src/dist/tsconfig.tsbuildinfo", "/src/src/other"] ); - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } - for (const output of missingOutputs) { - assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); + verifyOutputsAbsent(fs, missingOutputs); }); it("verify that it reports error for same .tsbuildinfo file", () => { @@ -84,12 +78,8 @@ namespace ts { [Diagnostics.Building_project_0, "/src/src/main/tsconfig.json"], [Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, "/src/dist/tsconfig.tsbuildinfo", "/src/src/other"] ); - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } - for (const output of missingOutputs) { - assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); + verifyOutputsAbsent(fs, missingOutputs); }); it("verify that it reports no error when .tsbuildinfo differ", () => { @@ -120,9 +110,7 @@ namespace ts { [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/main/tsconfig.main.json", "src/dist/a.js"], [Diagnostics.Building_project_0, "/src/src/main/tsconfig.main.json"] ); - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); }); }); } diff --git a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts index f1a40c050f9..66bb0dc6e95 100644 --- a/src/testRunner/unittests/tsbuild/resolveJsonModule.ts +++ b/src/testRunner/unittests/tsbuild/resolveJsonModule.ts @@ -23,9 +23,7 @@ namespace ts { host.assertDiagnosticMessages(...expectedDiagnosticMessages); if (!expectedDiagnosticMessages.length) { // Check for outputs. Not an exhaustive list - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); } } @@ -71,9 +69,7 @@ export default hello.hello`); [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFile, "src/dist/src/index.js"], [Diagnostics.Building_project_0, `/${configFile}`] ); - for (const output of [...allExpectedOutputs, "/src/dist/src/index.js.map"]) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, [...allExpectedOutputs, "/src/dist/src/index.js.map"]); host.clearDiagnostics(); builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); @@ -96,9 +92,7 @@ export default hello.hello`); [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFile, "src/src/index.js"], [Diagnostics.Building_project_0, `/${configFile}`] ); - for (const output of ["/src/src/index.js", "/src/src/index.d.ts"]) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, ["/src/src/index.js", "/src/src/index.d.ts"]); host.clearDiagnostics(); builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); @@ -137,7 +131,7 @@ export default hello.hello`); [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, mainConfigFile, "src/main/index.js"], [Diagnostics.Building_project_0, `/${mainConfigFile}`], ); - assert(fs.existsSync(expectedOutput), `Expect file ${expectedOutput} to exist`); + verifyOutputsPresent(fs, [expectedOutput]); host.clearDiagnostics(); builder = createSolutionBuilder(host, [configFile], { verbose: true }); tick(); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 3ad2b2e3eec..65530e59705 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -25,9 +25,7 @@ namespace ts { host.assertDiagnosticMessages(/*empty*/); // Check for outputs. Not an exhaustive list - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); }); it("builds correctly when outDir is specified", () => { @@ -43,9 +41,7 @@ namespace ts { host.assertDiagnosticMessages(/*empty*/); const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/", "/logic/outDir/")); // Check for outputs. Not an exhaustive list - for (const output of expectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, expectedOutputs); }); it("builds correctly when declarationDir is specified", () => { @@ -61,9 +57,7 @@ namespace ts { host.assertDiagnosticMessages(/*empty*/); const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/index.d.ts", "/logic/out/decls/index.d.ts")); // Check for outputs. Not an exhaustive list - for (const output of expectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, expectedOutputs); }); it("builds correctly when project is not composite or doesnt have any references", () => { @@ -77,9 +71,7 @@ namespace ts { [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], [Diagnostics.Building_project_0, "/src/core/tsconfig.json"] ); - for (const output of ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map"]) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map"]); }); }); @@ -96,9 +88,7 @@ namespace ts { ); // Check for outputs to not be written. Not an exhaustive list - for (const output of allExpectedOutputs) { - assert(!fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsAbsent(fs, allExpectedOutputs); }); it("indicates that it would skip builds during a dry build", () => { @@ -128,16 +118,42 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false }); builder.build(); // Verify they exist - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } - builder.cleanAllProjects(); + verifyOutputsPresent(fs, allExpectedOutputs); + + builder.clean(); // Verify they are gone - for (const output of allExpectedOutputs) { - assert(!fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsAbsent(fs, allExpectedOutputs); + // Subsequent clean shouldn't throw / etc - builder.cleanAllProjects(); + builder.clean(); + verifyOutputsAbsent(fs, allExpectedOutputs); + + builder.build(); + // Verify they exist + verifyOutputsPresent(fs, allExpectedOutputs); + }); + + it("cleans till project specified", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + builder.build(); + const result = builder.clean("/src/logic"); + host.assertDiagnosticMessages(/*empty*/); + verifyOutputsPresent(fs, [allExpectedOutputs[0]]); + verifyOutputsAbsent(fs, allExpectedOutputs.slice(1)); + assert.equal(result, ExitStatus.Success); + }); + + it("cleaning project in not build order doesnt throw error", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + builder.build(); + const result = builder.clean("/src/logic2"); + host.assertDiagnosticMessages(/*empty*/); + verifyOutputsPresent(fs, allExpectedOutputs); + assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); }); }); @@ -318,10 +334,8 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tests"], {}); const result = builder.build("/src/logic"); host.assertDiagnosticMessages(/*empty*/); - assert.isFalse(fs.existsSync(allExpectedOutputs[0]), `Expect file ${allExpectedOutputs[0]} to not exist`); - for (const output of allExpectedOutputs.slice(1)) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsAbsent(fs, [allExpectedOutputs[0]]); + verifyOutputsPresent(fs, allExpectedOutputs.slice(1)); assert.equal(result, ExitStatus.Success); }); @@ -331,9 +345,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tests"], {}); const result = builder.build("/src/logic2"); host.assertDiagnosticMessages(/*empty*/); - for (const output of allExpectedOutputs) { - assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`); - } + verifyOutputsAbsent(fs, allExpectedOutputs); assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); }); }); diff --git a/src/testRunner/unittests/tsbuild/transitiveReferences.ts b/src/testRunner/unittests/tsbuild/transitiveReferences.ts index 94f364ddec0..614796b2a85 100644 --- a/src/testRunner/unittests/tsbuild/transitiveReferences.ts +++ b/src/testRunner/unittests/tsbuild/transitiveReferences.ts @@ -32,9 +32,7 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tsconfig.c.json"], { listFiles: true }); builder.build(); host.assertDiagnosticMessages(...expectedDiagnostics); - for (const output of allExpectedOutputs) { - assert(fs.existsSync(output), `Expect file ${output} to exist`); - } + verifyOutputsPresent(fs, allExpectedOutputs); assert.deepEqual(host.traces, expectedFileTraces); } diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index fc5559be023..3514236beda 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -224,7 +224,7 @@ namespace ts { updateCreateProgram(buildHost); buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); const builder = createSolutionBuilder(buildHost, projects, buildOptions); - return sys.exit(buildOptions.clean ? builder.cleanAllProjects() : builder.build()); + return sys.exit(buildOptions.clean ? builder.clean() : builder.build()); } } From 3da47963d51d85a1af3c482344d56c947b71c552 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 May 2019 15:17:53 -0700 Subject: [PATCH 023/111] Remove startWatching as explicit method from api --- src/compiler/tsbuild.ts | 42 +++++++++----------- src/testRunner/unittests/tsbuildWatchMode.ts | 1 - src/tsc/tsc.ts | 19 ++++----- 3 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index c075e1f799c..1f7a90cabd6 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -234,6 +234,7 @@ namespace ts { export interface SolutionBuilderHostBase extends ProgramHost { getModifiedTime(fileName: string): Date | undefined; setModifiedTime(fileName: string, date: Date): void; + deleteFile(fileName: string): void; 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; @@ -247,7 +248,6 @@ namespace ts { } export interface SolutionBuilderHost extends SolutionBuilderHostBase { - deleteFile(fileName: string): void; reportErrorSummary?: ReportEmitErrorSummary; } @@ -267,11 +267,6 @@ namespace ts { /*@internal*/ buildNextInvalidatedProject(): void; } - export interface SolutionBuilderWithWatch { - build(project?: string, cancellationToken?: CancellationToken): ExitStatus; - /*@internal*/ startWatching(): void; - } - interface InvalidatedProject { project: ResolvedConfigFileName; projectPath: ResolvedConfigFilePath; @@ -294,6 +289,7 @@ namespace ts { 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; @@ -301,7 +297,6 @@ namespace ts { export function createSolutionBuilderHost(system = sys, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost; - host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop; host.reportErrorSummary = reportErrorSummary; return host; } @@ -325,7 +320,7 @@ namespace ts { return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); } - export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch { + export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions); } @@ -334,8 +329,8 @@ namespace ts { * can dynamically add/remove other projects based on changes on the rootNames' references */ function createSolutionBuilderWorker(watch: false, host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch; - function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder | SolutionBuilderWithWatch { + function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { const host = hostOrHostWithWatch as SolutionBuilderHost; const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost; const currentDirectory = host.getCurrentDirectory(); @@ -395,21 +390,16 @@ namespace ts { let allProjectBuildPending = true; let needsSummary = true; - // let watchAllProjectsPending = watch; + let watchAllProjectsPending = watch; - return watch ? - { - build, - startWatching - } : - { - build, - clean, - getBuildOrder, - getUpToDateStatusOfProject, - invalidateProject, - buildNextInvalidatedProject, - }; + return { + build, + clean, + getBuildOrder, + getUpToDateStatusOfProject, + invalidateProject, + buildNextInvalidatedProject, + }; function toPath(fileName: string) { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); @@ -1469,6 +1459,10 @@ namespace ts { disableCache(); reportErrorSummary(); } + if (watchAllProjectsPending) { + watchAllProjectsPending = false; + startWatching(); + } break; } diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index ad00145db52..70fe602c8a2 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -25,7 +25,6 @@ namespace ts.tscWatch { const host = createSolutionBuilderWithWatchHost(system); const solutionBuilder = ts.createSolutionBuilderWithWatch(host, rootNames, defaultOptions || { watch: true }); solutionBuilder.build(); - solutionBuilder.startWatching(); return solutionBuilder; } diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 3514236beda..47814a1f68b 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -207,25 +207,22 @@ namespace ts { reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--build")); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } - if (buildOptions.watch) { - reportWatchModeWithoutSysSupport(); - } if (buildOptions.watch) { + reportWatchModeWithoutSysSupport(); const buildHost = createSolutionBuilderWithWatchHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createWatchStatusReporter(buildOptions)); updateCreateProgram(buildHost); buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions); builder.build(); - return builder.startWatching(); - } - else { - const buildHost = createSolutionBuilderHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createReportErrorSummary(buildOptions)); - updateCreateProgram(buildHost); - buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); - const builder = createSolutionBuilder(buildHost, projects, buildOptions); - return sys.exit(buildOptions.clean ? builder.clean() : builder.build()); + return; } + + const buildHost = createSolutionBuilderHost(sys, /*createProgram*/ undefined, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty(buildOptions)), createReportErrorSummary(buildOptions)); + updateCreateProgram(buildHost); + buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(program.getProgram()); + const builder = createSolutionBuilder(buildHost, projects, buildOptions); + return sys.exit(buildOptions.clean ? builder.clean() : builder.build()); } function createReportErrorSummary(options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined { From 5c18513e966dc9718358778cc53261aa2c845cc2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 May 2019 15:34:13 -0700 Subject: [PATCH 024/111] Make SolutionBuilder as Public API --- src/compiler/commandLineParser.ts | 3 +- src/compiler/tsbuild.ts | 122 +++++++++--------- src/compiler/watch.ts | 2 - .../reference/api/tsserverlibrary.d.ts | 43 +++++- tests/baselines/reference/api/typescript.d.ts | 43 +++++- 5 files changed, 145 insertions(+), 68 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 104f3576e3a..b6ae404a295 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1022,8 +1022,7 @@ namespace ts { } } - /* @internal */ - export interface OptionsBase { + interface OptionsBase { [option: string]: CompilerOptionsValue | undefined; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 1f7a90cabd6..673f712689e 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1,58 +1,5 @@ -// Currently we do not want to expose API for build, we should work out the API, and then expose it just like we did for builder/watch /*@internal*/ namespace ts { - const minimumDate = new Date(-8640000000000000); - const maximumDate = new Date(8640000000000000); - - export interface BuildHost { - verbose(diag: DiagnosticMessage, ...args: string[]): void; - error(diag: DiagnosticMessage, ...args: string[]): void; - errorDiagnostic(diag: Diagnostic): void; - message(diag: DiagnosticMessage, ...args: string[]): void; - } - - export interface BuildOptions extends OptionsBase { - dry?: boolean; - force?: boolean; - verbose?: boolean; - - /*@internal*/ clean?: boolean; - /*@internal*/ watch?: boolean; - /*@internal*/ help?: boolean; - - preserveWatchOutput?: boolean; - listEmittedFiles?: boolean; - listFiles?: boolean; - pretty?: boolean; - incremental?: boolean; - - traceResolution?: boolean; - /* @internal */ diagnostics?: boolean; - /* @internal */ extendedDiagnostics?: boolean; - } - - 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 - } - export enum UpToDateStatusType { Unbuildable, UpToDate, @@ -194,6 +141,63 @@ namespace ts { } } + export function resolveConfigFileProjectName(project: string): ResolvedConfigFileName { + if (fileExtensionIs(project, Extension.Json)) { + return project as ResolvedConfigFileName; + } + + 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; + + preserveWatchOutput?: boolean; + listEmittedFiles?: boolean; + listFiles?: boolean; + pretty?: boolean; + incremental?: boolean; + + traceResolution?: boolean; + /* @internal */ diagnostics?: boolean; + /* @internal */ extendedDiagnostics?: boolean; + + [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 + } + type ResolvedConfigFilePath = ResolvedConfigFileName & Path; interface FileMap extends Map { get(key: U): T | undefined; @@ -231,6 +235,8 @@ namespace ts { return fileExtensionIs(fileName, Extension.Dts); } + export type ReportEmitErrorSummary = (errorCount: number) => void; + export interface SolutionBuilderHostBase extends ProgramHost { getModifiedTime(fileName: string): Date | undefined; setModifiedTime(fileName: string, date: Date): void; @@ -1527,15 +1533,7 @@ namespace ts { } } - export function resolveConfigFileProjectName(project: string): ResolvedConfigFileName { - if (fileExtensionIs(project, Extension.Json)) { - return project as ResolvedConfigFileName; - } - - return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName; - } - - export function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { + function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { switch (status.type) { case UpToDateStatusType.OutOfDateWithSelf: return formatMessage(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index d6a856cfe9f..50853534bb0 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -88,8 +88,6 @@ namespace ts { return result; } - export type ReportEmitErrorSummary = (errorCount: number) => void; - export function getErrorCountForSummary(diagnostics: ReadonlyArray) { return countWhere(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index fb95102e791..08a1309d792 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1905,7 +1905,8 @@ declare namespace ts { enum ExitStatus { Success = 0, DiagnosticsPresent_OutputsSkipped = 1, - DiagnosticsPresent_OutputsGenerated = 2 + DiagnosticsPresent_OutputsGenerated = 2, + InvalidProject_OutputsSkipped = 3 } interface EmitResult { emitSkipped: boolean; @@ -4555,6 +4556,46 @@ declare namespace ts { */ function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; } +declare namespace ts { + interface BuildOptions { + dry?: boolean; + force?: boolean; + verbose?: boolean; + preserveWatchOutput?: boolean; + listEmittedFiles?: boolean; + listFiles?: boolean; + pretty?: boolean; + incremental?: boolean; + traceResolution?: boolean; + [option: string]: CompilerOptionsValue | undefined; + } + type ReportEmitErrorSummary = (errorCount: number) => void; + interface SolutionBuilderHostBase extends ProgramHost { + getModifiedTime(fileName: string): Date | undefined; + setModifiedTime(fileName: string, date: Date): void; + deleteFile(fileName: string): void; + reportDiagnostic: DiagnosticReporter; + reportSolutionBuilderStatus: DiagnosticReporter; + afterProgramEmitAndDiagnostics?(program: T): void; + } + interface SolutionBuilderHost extends SolutionBuilderHostBase { + reportErrorSummary?: ReportEmitErrorSummary; + } + interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { + } + interface SolutionBuilder { + build(project?: string, cancellationToken?: CancellationToken): ExitStatus; + clean(project?: string): ExitStatus; + } + /** + * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic + */ + function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter; + function createSolutionBuilderHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost; + function createSolutionBuilderWithWatchHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost; + function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; +} declare namespace ts.server { type ActionSet = "action::set"; type ActionInvalidate = "action::invalidate"; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 170cb00b9e8..1c8129c20b0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1905,7 +1905,8 @@ declare namespace ts { enum ExitStatus { Success = 0, DiagnosticsPresent_OutputsSkipped = 1, - DiagnosticsPresent_OutputsGenerated = 2 + DiagnosticsPresent_OutputsGenerated = 2, + InvalidProject_OutputsSkipped = 3 } interface EmitResult { emitSkipped: boolean; @@ -4555,6 +4556,46 @@ declare namespace ts { */ function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; } +declare namespace ts { + interface BuildOptions { + dry?: boolean; + force?: boolean; + verbose?: boolean; + preserveWatchOutput?: boolean; + listEmittedFiles?: boolean; + listFiles?: boolean; + pretty?: boolean; + incremental?: boolean; + traceResolution?: boolean; + [option: string]: CompilerOptionsValue | undefined; + } + type ReportEmitErrorSummary = (errorCount: number) => void; + interface SolutionBuilderHostBase extends ProgramHost { + getModifiedTime(fileName: string): Date | undefined; + setModifiedTime(fileName: string, date: Date): void; + deleteFile(fileName: string): void; + reportDiagnostic: DiagnosticReporter; + reportSolutionBuilderStatus: DiagnosticReporter; + afterProgramEmitAndDiagnostics?(program: T): void; + } + interface SolutionBuilderHost extends SolutionBuilderHostBase { + reportErrorSummary?: ReportEmitErrorSummary; + } + interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { + } + interface SolutionBuilder { + build(project?: string, cancellationToken?: CancellationToken): ExitStatus; + clean(project?: string): ExitStatus; + } + /** + * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic + */ + function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter; + function createSolutionBuilderHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost; + function createSolutionBuilderWithWatchHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost; + function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; +} declare namespace ts.server { type ActionSet = "action::set"; type ActionInvalidate = "action::invalidate"; From 0a255249f6bf04c3c15e21fadb393c31fb9a3b15 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 May 2019 15:42:08 -0700 Subject: [PATCH 025/111] Enable apis to create incremental program --- src/compiler/watch.ts | 75 ++++++++++--------- .../reference/api/tsserverlibrary.d.ts | 11 +++ tests/baselines/reference/api/typescript.d.ts | 11 +++ 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 50853534bb0..4f974add57d 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -380,43 +380,6 @@ namespace ts { return host; } - export function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined) { - if (compilerOptions.out || compilerOptions.outFile) return undefined; - const buildInfoPath = getOutputPathForBuildInfo(compilerOptions); - if (!buildInfoPath) return undefined; - const content = 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); - } - - 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; - } - - interface IncrementalProgramOptions { - rootNames: ReadonlyArray; - options: CompilerOptions; - configFileParsingDiagnostics?: ReadonlyArray; - projectReferences?: ReadonlyArray; - host?: CompilerHost; - createProgram?: CreateProgram; - } - 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, path => host!.readFile(path)) as any as T; - return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); - } - export interface IncrementalCompilationOptions { rootNames: ReadonlyArray; options: CompilerOptions; @@ -444,6 +407,44 @@ namespace ts { } namespace ts { + export function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined) { + if (compilerOptions.out || compilerOptions.outFile) return undefined; + const buildInfoPath = getOutputPathForBuildInfo(compilerOptions); + if (!buildInfoPath) return undefined; + const content = 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); + } + + 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: ReadonlyArray; + options: CompilerOptions; + configFileParsingDiagnostics?: ReadonlyArray; + projectReferences?: ReadonlyArray; + 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, path => host!.readFile(path)) as any as T; + return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); + } + export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ export type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray | undefined) => T; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 08a1309d792..7b45703dcfd 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4443,6 +4443,17 @@ declare namespace ts { function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; } declare namespace ts { + function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined): (EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram) | undefined; + function createIncrementalCompilerHost(options: CompilerOptions, system?: System): CompilerHost; + interface IncrementalProgramOptions { + rootNames: ReadonlyArray; + options: CompilerOptions; + configFileParsingDiagnostics?: ReadonlyArray; + projectReferences?: ReadonlyArray; + host?: CompilerHost; + createProgram?: CreateProgram; + } + function createIncrementalProgram({ rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram }: IncrementalProgramOptions): T; type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray | undefined) => T; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 1c8129c20b0..acbe1b1ac0d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4443,6 +4443,17 @@ declare namespace ts { function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; } declare namespace ts { + function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined): (EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram) | undefined; + function createIncrementalCompilerHost(options: CompilerOptions, system?: System): CompilerHost; + interface IncrementalProgramOptions { + rootNames: ReadonlyArray; + options: CompilerOptions; + configFileParsingDiagnostics?: ReadonlyArray; + projectReferences?: ReadonlyArray; + host?: CompilerHost; + createProgram?: CreateProgram; + } + function createIncrementalProgram({ rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram }: IncrementalProgramOptions): T; type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray | undefined) => T; From 71b190af61ba2b074c54bb85d2a8c817a7df7bf9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 2 May 2019 16:12:29 -0700 Subject: [PATCH 026/111] Create api for buildNextProject --- src/compiler/tsbuild.ts | 33 +++++++++++-- src/testRunner/unittests/tsbuild/sample.ts | 48 ++++++++++++++++--- .../reference/api/tsserverlibrary.d.ts | 5 ++ tests/baselines/reference/api/typescript.d.ts | 5 ++ 4 files changed, 80 insertions(+), 11 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 673f712689e..9e625041287 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -260,9 +260,15 @@ namespace ts { export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } + export interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } + export interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; // Currently used for testing but can be made public if needed: /*@internal*/ getBuildOrder(): ReadonlyArray; @@ -401,6 +407,7 @@ namespace ts { return { build, clean, + buildNextProject, getBuildOrder, getUpToDateStatusOfProject, invalidateProject, @@ -1437,10 +1444,7 @@ namespace ts { return resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder(); } - function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { - const buildOrder = getBuildOrderFor(project); - if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; - + function setupInitialBuild(cancellationToken: CancellationToken | undefined) { // Set initial build if not already built if (allProjectBuildPending) { allProjectBuildPending = false; @@ -1455,6 +1459,27 @@ namespace ts { cancellationToken.throwIfCancellationRequested(); } } + } + + function buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined { + setupInitialBuild(cancellationToken); + const invalidatedProject = getNextInvalidatedProject(getBuildOrder()); + if (!invalidatedProject) return undefined; + + buildInvalidatedProject(invalidatedProject, cancellationToken); + return { + project: invalidatedProject.project, + result: diagnostics.has(invalidatedProject.projectPath) ? + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success + }; + } + + function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { + const buildOrder = getBuildOrderFor(project); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + setupInitialBuild(cancellationToken); let successfulProjects = 0; let errorProjects = 0; diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 65530e59705..7f9f2260d6d 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -2,9 +2,10 @@ namespace ts { describe("unittests:: tsbuild:: on 'sample1' project", () => { let projFs: vfs.FileSystem; const { time, tick } = getTime(); - const allExpectedOutputs = ["/src/tests/index.js", - "/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", - "/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"]; + const testsOutputs = ["/src/tests/index.js"]; + const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"]; + const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map"]; + const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs]; before(() => { projFs = loadProjectFromDisk("tests/projects/sample1", time); @@ -140,8 +141,8 @@ namespace ts { builder.build(); const result = builder.clean("/src/logic"); host.assertDiagnosticMessages(/*empty*/); - verifyOutputsPresent(fs, [allExpectedOutputs[0]]); - verifyOutputsAbsent(fs, allExpectedOutputs.slice(1)); + verifyOutputsPresent(fs, testsOutputs); + verifyOutputsAbsent(fs, [...logicOutputs, ...coreOutputs]); assert.equal(result, ExitStatus.Success); }); @@ -334,8 +335,8 @@ namespace ts { const builder = createSolutionBuilder(host, ["/src/tests"], {}); const result = builder.build("/src/logic"); host.assertDiagnosticMessages(/*empty*/); - verifyOutputsAbsent(fs, [allExpectedOutputs[0]]); - verifyOutputsPresent(fs, allExpectedOutputs.slice(1)); + verifyOutputsAbsent(fs, testsOutputs); + verifyOutputsPresent(fs, [...logicOutputs, ...coreOutputs]); assert.equal(result, ExitStatus.Success); }); @@ -348,6 +349,39 @@ namespace ts { verifyOutputsAbsent(fs, allExpectedOutputs); assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); }); + + it("building using buildNextProject", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], {}); + verifyBuildNextResult({ + project: "/src/core/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, coreOutputs, [...logicOutputs, ...testsOutputs]); + + verifyBuildNextResult({ + project: "/src/logic/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, [...coreOutputs, ...logicOutputs], testsOutputs); + + verifyBuildNextResult({ + project: "/src/tests/tsconfig.json" as ResolvedConfigFileName, + result: ExitStatus.Success + }, allExpectedOutputs, emptyArray); + + verifyBuildNextResult(/*expected*/ undefined, allExpectedOutputs, emptyArray); + + function verifyBuildNextResult( + expected: SolutionBuilderResult | undefined, + presentOutputs: readonly string[], + absentOutputs: readonly string[] + ) { + const result = builder.buildNextProject(); + assert.deepEqual(result, expected); + verifyOutputsPresent(fs, presentOutputs); + verifyOutputsAbsent(fs, absentOutputs); + } + }); }); describe("downstream-blocked compilations", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 7b45703dcfd..f13d0ab3f51 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4594,9 +4594,14 @@ declare namespace ts { } interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } + interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; } /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index acbe1b1ac0d..e5e5046794a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4594,9 +4594,14 @@ declare namespace ts { } interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } + interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; + buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; } /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic From f01743385796a2d8a07d1dc5d3feaad526166d7e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 3 May 2019 13:32:47 -0700 Subject: [PATCH 027/111] Move everything into state so we can pass it around --- src/compiler/tsbuild.ts | 2649 +++++++++--------- src/testRunner/unittests/tsbuild/sample.ts | 2 +- src/testRunner/unittests/tsbuildWatchMode.ts | 8 +- 3 files changed, 1406 insertions(+), 1253 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 9e625041287..22bc8ba8238 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -198,7 +198,8 @@ namespace ts { AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors } - type ResolvedConfigFilePath = ResolvedConfigFileName & Path; + /*@internal*/ + export type ResolvedConfigFilePath = ResolvedConfigFileName & Path; interface FileMap extends Map { get(key: U): T | undefined; has(key: U): boolean; @@ -212,6 +213,9 @@ namespace ts { 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); @@ -275,15 +279,16 @@ namespace ts { // Testing only /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; - /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; + /*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void; /*@internal*/ buildNextInvalidatedProject(): void; } interface InvalidatedProject { - project: ResolvedConfigFileName; - projectPath: ResolvedConfigFilePath; - reloadLevel: ConfigFileProgramReloadLevel; - projectIndex: number; + readonly project: ResolvedConfigFileName; + readonly projectPath: ResolvedConfigFilePath; + readonly reloadLevel: ConfigFileProgramReloadLevel; + readonly projectIndex: number; + readonly buildOrder: readonly ResolvedConfigFileName[]; } /** @@ -336,1279 +341,1418 @@ namespace ts { 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: ReadonlyArray; + + 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: readonly ResolvedConfigFileName[] | undefined; + readFileWithCache: (f: string) => string | undefined; + projectCompilerOptions: CompilerOptions; + cache: SolutionBuilderStateCache | undefined; + allProjectBuildPending: boolean; + needsSummary: boolean; + watchAllProjectsPending: boolean; + + // 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: ReadonlyArray, 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, + + // 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 } = state; + parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; + const 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[]): readonly ResolvedConfigFileName[] { + const temporaryMarks = createMap() as ConfigFileMap; + const permanentMarks = createMap() as ConfigFileMap; + const circularityReportStack: string[] = []; + let buildOrder: ResolvedConfigFileName[] | undefined; + for (const root of roots) { + visit(root); + } + + return 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) { + // TODO:: Do we report this as error? + reportStatus(state, 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 || + (state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)))); + } + + function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined) { + const resolvedProject = project && resolveProjectName(state, project); + if (resolvedProject) { + const projectPath = toResolvedConfigFilePath(state, resolvedProject); + const projectIndex = findIndex( + getBuildOrder(state), + configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath + ); + if (projectIndex === -1) return undefined; + } + return resolvedProject ? createBuildOrder(state, [resolvedProject]) : getBuildOrder(state); + } + + 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 = getBuildOrder(state); + reportBuildQueue(state, buildOrder); + buildOrder.forEach(configFileName => + state.projectPendingBuild.set( + toResolvedConfigFilePath(state, configFileName), + ConfigFileProgramReloadLevel.None + ) + ); + + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); + } + } + + function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { + return state.projectPendingBuild.size ? + forEach(buildOrder, (project, projectIndex) => { + const projectPath = toResolvedConfigFilePath(state, project); + const reloadLevel = state.projectPendingBuild.get(projectPath); + if (reloadLevel !== undefined) { + return { project, projectPath, reloadLevel, projectIndex, buildOrder }; + } + }) : + undefined; + } + + function listEmittedFile({ writeFileName }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { + if (writeFileName && proj.options.listEmittedFiles) { + writeFileName(`TSFILE: ${file}`); + } + } + + function getOldProgram({ options, builderPrograms, readFileWithCache }: SolutionBuilderState, proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (options.force) return undefined; + const value = builderPrograms.get(proj); + if (value) return value; + return readBuilderProgram(parsed.options, readFileWithCache) 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: ReadonlyArray, + 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 buildSingleProject(state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { + if (state.options.dry) { + reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, proj); + return BuildResultFlags.Success; + } + + if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, proj); + + const { host, projectStatus, diagnostics, compilerHost, moduleResolutionCache, } = state; + const configFile = parseConfigFile(state, proj, resolvedPath); + if (!configFile) { + // Failed to read the config file + reportParseConfigFileDiagnostic(state, resolvedPath); + projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); + return BuildResultFlags.ConfigFileErrors; + } + + if (configFile.fileNames.length === 0) { + reportAndStoreErrors(state, resolvedPath, configFile.errors); + // Nothing to build - must be a solution file, basically + return BuildResultFlags.None; + } + + state.projectCompilerOptions = configFile.options; + // Update module resolution cache if needed + if (moduleResolutionCache) { + 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: configFile.options.configFile!, + commandLine: configFile + }; + moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); + moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + } + moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(configFile.options); + moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(configFile.options); + } + + // Create program + const program = host.createProgram( + configFile.fileNames, + configFile.options, + compilerHost, + getOldProgram(state, resolvedPath, configFile), + configFile.errors, + configFile.projectReferences + ); + + // Don't emit anything in the presence of syntactic errors or options diagnostics + const syntaxDiagnostics = [ + ...program.getConfigFileParsingDiagnostics(), + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)]; + if (syntaxDiagnostics.length) { + return buildErrors( + state, + resolvedPath, + program, + syntaxDiagnostics, + BuildResultFlags.SyntaxErrors, + "Syntactic" + ); + } + + // Same as above but now for semantic diagnostics + const semanticDiagnostics = program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken); + if (semanticDiagnostics.length) { + return buildErrors( + state, + resolvedPath, + program, + semanticDiagnostics, + BuildResultFlags.TypeErrors, + "Semantic" + ); + } + + // 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[] = []; + emitFilesAndReportErrors( + program, + reportDeclarationDiagnostics, + /*writeFileName*/ undefined, + /*reportSummary*/ undefined, + (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), + cancellationToken + ); + // Don't emit .d.ts if there are decl file errors + if (declDiagnostics) { + program.restoreState(); + return buildErrors( + state, + resolvedPath, + program, + declDiagnostics, + BuildResultFlags.DeclarationEmitErrors, + "Declaration file" + ); + } + + // Actual Emit + 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(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); + if (priorChangeTime !== undefined) { + newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); + } + }); + + const emitDiagnostics = emitterDiagnostics.getDiagnostics(); + if (emitDiagnostics.length) { + return buildErrors( + state, + resolvedPath, + program, + emitDiagnostics, + BuildResultFlags.EmitErrors, + "Emit" + ); + } + + if (state.writeFileName) { + emittedOutputs.forEach(name => listEmittedFile(state, configFile, name)); + listFiles(program, state.writeFileName); + } + + // Update time stamps for rest of the outputs + newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + diagnostics.delete(resolvedPath); + projectStatus.set(resolvedPath, { + type: UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, + oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) + }); + afterProgramCreate(state, resolvedPath, program); + state.projectCompilerOptions = state.baseCompilerOptions; + return resultFlags; + } + + function updateBundle(state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { + if (state.options.dry) { + reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); + return BuildResultFlags.Success; + } + + if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, proj); + + // Update js, and source map + const { projectStatus, diagnostics, compilerHost } = state; + const config = Debug.assertDefined(parseConfigFile(state, proj, resolvedPath)); + state.projectCompilerOptions = config.options; + const outputFiles = emitUsingBuildInfo( + config, + compilerHost, + ref => { + const refName = resolveProjectName(state, ref.path); + return parseConfigFile(state, refName, toResolvedConfigFilePath(state, refName)); + }); + if (isString(outputFiles)) { + reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(state, outputFiles)); + return buildSingleProject(state, proj, resolvedPath, cancellationToken); + } + + // 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(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); + }); + const emitDiagnostics = emitterDiagnostics.getDiagnostics(); + if (emitDiagnostics.length) { + return buildErrors( + state, + resolvedPath, + /*program*/ undefined, + emitDiagnostics, + BuildResultFlags.EmitErrors, + "Emit" + ); + } + + if (state.writeFileName) { + emittedOutputs.forEach(name => listEmittedFile(state, config, name)); + } + + // Update timestamps for dts + const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, minimumDate, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + diagnostics.delete(resolvedPath); + projectStatus.set(resolvedPath, { + type: UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime, + oldestOutputFileName: outputFiles[0].name + }); + state.projectCompilerOptions = state.baseCompilerOptions; + return BuildResultFlags.DeclarationOutputUnchanged; + } + + 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) { + continue; + } + + // An upstream project is blocked + if (refStatus.type === UpToDateStatusType.Unbuildable) { + return { + type: UpToDateStatusType.UpstreamBlocked, + upstreamProjectName: ref.path + }; + } + + // 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 = getOutputPathForBuildInfo(project.options); + if (buildInfoPath) { + const value = state.readFileWithCache(buildInfoPath); + const buildInfo = value && getBuildInfo(value); + if (buildInfo && 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 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 buildInvalidatedProject(state: SolutionBuilderState, { project, projectPath, reloadLevel, projectIndex, buildOrder }: InvalidatedProject, cancellationToken?: CancellationToken) { + const { options, projectPendingBuild } = state; + const config = parseConfigFile(state, project, projectPath); + if (!config) { + reportParseConfigFileDiagnostic(state, projectPath); + projectPendingBuild.delete(projectPath); + return; + } + + 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); + } + return; + } + + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + // Fake that files have been built by updating output file stamps + updateOutputTimestamps(state, config, projectPath); + return; + } + } + + if (status.type === UpToDateStatusType.UpstreamBlocked) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + if (options.verbose) reportStatus(state, Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName); + return; + } + + if (status.type === UpToDateStatusType.ContainerOnly) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + // Do nothing + return; + } + + const buildResult = needsBuild(state, status, config) ? + buildSingleProject(state, project, projectPath, cancellationToken) : // Actual build + updateBundle(state, project, projectPath, cancellationToken); // Fake that files have been built by manipulating prepend and existing output + projectPendingBuild.delete(projectPath); + // Only composite projects can be referenced by other projects + if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { + queueReferencingProjects(state, project, projectPath, projectIndex, buildOrder, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); + } + } + + function queueReferencingProjects( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + buildOrder: readonly ResolvedConfigFileName[], + declarationOutputChanged: boolean + ) { + // 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 (!declarationOutputChanged) { + 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 (declarationOutputChanged) { + 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 buildNextProject(state: SolutionBuilderState, cancellationToken?: CancellationToken): SolutionBuilderResult | undefined { + setupInitialBuild(state, cancellationToken); + const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); + if (!invalidatedProject) return undefined; + + buildInvalidatedProject(state, invalidatedProject, cancellationToken); + return { + project: invalidatedProject.project, + result: state.diagnostics.has(invalidatedProject.projectPath) ? + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success + }; + } + + function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken): ExitStatus { + const buildOrder = getBuildOrderFor(state, project); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + setupInitialBuild(state, cancellationToken); + + let successfulProjects = 0; + let errorProjects = 0; + while (true) { + const invalidatedProject = getNextInvalidatedProject(state, buildOrder); + if (!invalidatedProject) break; + buildInvalidatedProject(state, invalidatedProject, cancellationToken); + if (state.diagnostics.has(invalidatedProject.projectPath)) { + errorProjects++; + } + else { + successfulProjects++; + } + } + + disableCache(state); + reportErrorSummary(state); + startWatching(state); + + return errorProjects ? + successfulProjects ? + ExitStatus.DiagnosticsPresent_OutputsGenerated : + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success; + } + + function clean(state: SolutionBuilderState, project?: string) { + const buildOrder = getBuildOrderFor(state, project); + if (!buildOrder) return ExitStatus.InvalidProject_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 (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 invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); + if (invalidatedProject) { + buildInvalidatedProject(state, invalidatedProject); + if (state.projectPendingBuild.size) { + // Schedule next project for build + if (state.watch && !state.timerToBuildInvalidatedProject) { + scheduleBuildInvalidatedProject(state); + } + } + else { + disableCache(state); + reportErrorSummary(state); + } + } + } + + 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) { + if (!state.watchAllProjectsPending) return; + state.watchAllProjectsPending = false; + for (const resolved of getBuildOrder(state)) { + 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: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { - const host = hostOrHostWithWatch as SolutionBuilderHost; - const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost; - const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host); - - // State of the solution - const options = defaultOptions; - const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); - const resolvedConfigFilePaths = createMap(); - type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; - const configFileCache = createMap() as ConfigFileMap; - /** Map from config file name to up-to-date status */ - const projectStatus = createMap() as ConfigFileMap; - let buildOrder: readonly ResolvedConfigFileName[] | undefined; - const writeFileName = host.trace ? (s: string) => host.trace!(s) : undefined; - let readFileWithCache = (f: string) => host.readFile(f); - let projectCompilerOptions = baseCompilerOptions; - const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions); - setGetSourceFileAsHashVersioned(compilerHost, host); - compilerHost.getParsedCommandLine = fileName => parseConfigFile(fileName as ResolvedConfigFileName, toResolvedConfigFilePath(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, projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; - compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => - loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); - } - let cacheState: { - originalReadFile: CompilerHost["readFile"]; - originalFileExists: CompilerHost["fileExists"]; - originalDirectoryExists: CompilerHost["directoryExists"]; - originalCreateDirectory: CompilerHost["createDirectory"]; - originalWriteFile: CompilerHost["writeFile"] | undefined; - originalReadFileWithCache: CompilerHost["readFile"]; - originalGetSourceFile: CompilerHost["getSourceFile"]; - } | undefined; - - const buildInfoChecked = createMap() as ConfigFileMap; - const extendedConfigCache = createMap(); - - // Watch state - const builderPrograms = createMap() as ConfigFileMap; - const diagnostics = createMap() as ConfigFileMap>; - const projectPendingBuild = createMap() as ConfigFileMap; - const projectErrorsReported = createMap() as ConfigFileMap; - let timerToBuildInvalidatedProject: any; - let reportFileChangeDetected = false; - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); - - // Watches for the solution - const allWatchedWildcardDirectories = createMap() as ConfigFileMap>; - const allWatchedInputFiles = createMap() as ConfigFileMap>; - const allWatchedConfigFiles = createMap() as ConfigFileMap; - - let allProjectBuildPending = true; - let needsSummary = true; - let watchAllProjectsPending = watch; - + function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, options: BuildOptions): SolutionBuilder { + const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options); return { - build, - clean, - buildNextProject, - getBuildOrder, - getUpToDateStatusOfProject, - invalidateProject, - buildNextInvalidatedProject, + build: (project, cancellationToken) => build(state, project, cancellationToken), + clean: project => clean(state, project), + buildNextProject: cancellationToken => buildNextProject(state, cancellationToken), + 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), }; + } - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); - } + function relName(state: SolutionBuilderState, path: string): string { + return convertToRelativePath(path, state.currentDirectory, f => state.getCanonicalFileName(f)); + } - function toResolvedConfigFilePath(fileName: ResolvedConfigFileName): ResolvedConfigFilePath { - const path = resolvedConfigFilePaths.get(fileName); - if (path !== undefined) return path; + function reportStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: string[]) { + state.host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); + } - const resolvedPath = toPath(fileName) as ResolvedConfigFilePath; - resolvedConfigFilePaths.set(fileName, resolvedPath); - return resolvedPath; - } - - function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { - return !!(entry as ParsedCommandLine).options; - } - - function parseConfigFile(configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { - const value = configFileCache.get(configFilePath); - if (value) { - return isParsedCommandLine(value) ? value : undefined; - } - - let diagnostic: Diagnostic | undefined; - parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; - const parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache); - parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; - configFileCache.set(configFilePath, parsed || diagnostic!); - return parsed; - } - - function reportStatus(message: DiagnosticMessage, ...args: string[]) { - host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); - } - - function reportWatchStatus(message: DiagnosticMessage, ...args: (string | number | undefined)[]) { - if (hostWithWatch.onWatchStatusChange) { - hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), host.getNewLine(), baseCompilerOptions); - } - } - - function startWatching() { - for (const resolved of getBuildOrder()) { - const resolvedPath = toResolvedConfigFilePath(resolved); - // Watch this file - watchConfigFile(resolved, resolvedPath); - - const cfg = parseConfigFile(resolved, resolvedPath); - if (cfg) { - // Update watchers for wildcard directories - watchWildCardDirectories(resolved, resolvedPath, cfg); - - // Watch input files - watchInputFiles(resolved, resolvedPath, cfg); - } - } - - } - - function watchConfigFile(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { - if (watch && !allWatchedConfigFiles.has(resolvedPath)) { - allWatchedConfigFiles.set(resolvedPath, watchFile( - hostWithWatch, - resolved, - () => { - invalidateProjectAndScheduleBuilds(resolvedPath, ConfigFileProgramReloadLevel.Full); - }, - PollingInterval.High, - WatchType.ConfigFile, - resolved - )); - } - } - - function watchWildCardDirectories(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (!watch) return; - updateWatchingWildcardDirectories( - getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolvedPath), - createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), - (dir, flags) => { - return watchDirectory( - hostWithWatch, - dir, - fileOrDirectory => { - const fileOrDirectoryPath = toPath(fileOrDirectory); - if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { - writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`); - return; - } - - if (isOutputFile(fileOrDirectory, parsed)) { - writeLog(`${fileOrDirectory} is output file`); - return; - } - - invalidateProjectAndScheduleBuilds(resolvedPath, ConfigFileProgramReloadLevel.Partial); - }, - flags, - WatchType.WildcardDirectory, - resolved - ); - } - ); - } - - function watchInputFiles(resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (!watch) return; - mutateMap( - getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolvedPath), - arrayToMap(parsed.fileNames, toPath), - { - createNewValue: (path, input) => watchFilePath( - hostWithWatch, - input, - () => invalidateProjectAndScheduleBuilds(resolvedPath, ConfigFileProgramReloadLevel.None), - PollingInterval.Low, - path as Path, - WatchType.SourceFile, - resolved - ), - onDeleteValue: closeFileWatcher, - } - ); - } - - function isOutputFile(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(fileName, out) || isSameFile(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, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } - - // If --outDir, check if file is in that directory - if (configFile.options.outDir && containsPath(configFile.options.outDir, fileName, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } - - return !forEach(configFile.fileNames, inputFile => isSameFile(fileName, inputFile)); - } - - function isSameFile(file1: string, file2: string) { - return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; - } - - function invalidateProjectAndScheduleBuilds(resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { - reportFileChangeDetected = true; - invalidateResolvedProject(resolvedPath, reloadLevel); - scheduleBuildInvalidatedProject(); - } - - function getUpToDateStatusOfProject(project: string): UpToDateStatus { - const configFileName = resolveProjectName(project); - const configFilePath = toResolvedConfigFilePath(configFileName); - return getUpToDateStatus(parseConfigFile(configFileName, configFilePath), configFilePath); - } - - function getBuildOrder() { - return buildOrder || (buildOrder = createBuildOrder(resolveProjectNames(rootNames))); - } - - function getUpToDateStatus(project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { - if (project === undefined) { - return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; - } - - const prior = projectStatus.get(resolvedPath); - if (prior !== undefined) { - return prior; - } - - const actual = getUpToDateStatusWorker(project, resolvedPath); - projectStatus.set(resolvedPath, actual); - return actual; - } - - function getUpToDateStatusWorker(project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { - let newestInputFileName: string = undefined!; - let newestInputFileTime = minimumDate; - // 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) { - projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); - for (const ref of project.projectReferences) { - usesPrepend = usesPrepend || !!(ref.prepend); - const resolvedRef = resolveProjectReferencePath(ref); - const resolvedRefPath = toResolvedConfigFilePath(resolvedRef); - const refStatus = getUpToDateStatus(parseConfigFile(resolvedRef, resolvedRefPath), resolvedRefPath); - - // Its a circular reference ignore the status of this project - if (refStatus.type === UpToDateStatusType.ComputingUpstream) { - continue; - } - - // An upstream project is blocked - if (refStatus.type === UpToDateStatusType.Unbuildable) { - return { - type: UpToDateStatusType.UpstreamBlocked, - upstreamProjectName: ref.path - }; - } - - // 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(project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName); - if (configStatus) return configStatus; - - // Check extended config time - const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(configFile, oldestOutputFileTime, oldestOutputFileName)); - if (extendedConfigStatus) return extendedConfigStatus; - } - - if (!buildInfoChecked.has(resolvedPath)) { - buildInfoChecked.set(resolvedPath, true); - const buildInfoPath = getOutputPathForBuildInfo(project.options); - if (buildInfoPath) { - const value = readFileWithCache(buildInfoPath); - const buildInfo = value && getBuildInfo(value); - if (buildInfo && 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 checkConfigFileUpToDateStatus(configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { - // Check tsconfig time - const tsconfigTime = host.getModifiedTime(configFile) || missingFileModifiedTime; - if (oldestOutputFileTime < tsconfigTime) { - return { - type: UpToDateStatusType.OutOfDateWithSelf, - outOfDateOutputFileName: oldestOutputFileName, - newerInputFileName: configFile - }; - } - } - - function invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel) { - invalidateResolvedProject(toResolvedConfigFilePath(resolveProjectName(configFileName)), reloadLevel || ConfigFileProgramReloadLevel.None); - } - - function invalidateResolvedProject(resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { - if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - configFileCache.delete(resolved); - buildOrder = undefined; - } - needsSummary = true; - clearProjectStatus(resolved); - addProjToQueue(resolved, reloadLevel); - enableCache(); - } - - function clearProjectStatus(resolved: ResolvedConfigFilePath) { - projectStatus.delete(resolved); - diagnostics.delete(resolved); - } - - /** - * return true if new addition - */ - function addProjToQueue(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 getNextInvalidatedProject(buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { - return hasPendingInvalidatedProjects() ? - forEach(buildOrder, (project, projectIndex) => { - const projectPath = toResolvedConfigFilePath(project); - const reloadLevel = projectPendingBuild.get(projectPath); - if (reloadLevel !== undefined) { - return { project, projectPath, reloadLevel, projectIndex }; - } - }) : - undefined; - } - - function hasPendingInvalidatedProjects() { - return !!projectPendingBuild.size; - } - - function scheduleBuildInvalidatedProject() { - if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { - return; - } - if (timerToBuildInvalidatedProject) { - hostWithWatch.clearTimeout(timerToBuildInvalidatedProject); - } - timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250); - } - - function buildNextInvalidatedProject() { - timerToBuildInvalidatedProject = undefined; - if (reportFileChangeDetected) { - reportFileChangeDetected = false; - projectErrorsReported.clear(); - reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation); - } - const invalidatedProject = getNextInvalidatedProject(getBuildOrder()); - if (invalidatedProject) { - buildInvalidatedProject(invalidatedProject); - if (hasPendingInvalidatedProjects()) { - if (watch && !timerToBuildInvalidatedProject) { - scheduleBuildInvalidatedProject(); - } - } - else { - disableCache(); - reportErrorSummary(); - } - } - } - - function reportErrorSummary() { - if (watch || host.reportErrorSummary) { - needsSummary = false; - // Report errors from the other projects - getBuildOrder().forEach(project => { - const projectPath = toResolvedConfigFilePath(project); - if (!projectErrorsReported.has(projectPath)) { - reportErrors(diagnostics.get(projectPath) || emptyArray); - } - }); - let totalErrors = 0; - diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); - if (watch) { - reportWatchStatus(getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); - } - else { - host.reportErrorSummary!(totalErrors); - } - } - } - - function buildInvalidatedProject({ project, projectPath, reloadLevel, projectIndex }: InvalidatedProject, cancellationToken?: CancellationToken) { - const config = parseConfigFile(project, projectPath); - if (!config) { - reportParseConfigFileDiagnostic(projectPath); - projectPendingBuild.delete(projectPath); - return; - } - - if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - watchConfigFile(project, projectPath); - watchWildCardDirectories(project, projectPath, config); - watchInputFiles(project, projectPath, config); - } - else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { - // Update file names - const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, parseConfigFileHost); - updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw)); - config.fileNames = result.fileNames; - watchInputFiles(project, projectPath, config); - } - - const status = getUpToDateStatus(config, projectPath); - verboseReportProjectStatus(project, status); - if (status.type === UpToDateStatusType.UpToDate && !options.force) { - reportAndStoreErrors(projectPath, config.errors); - // Up to date, skip - if (options.dry) { - // In a dry build, inform the user of this fact - reportStatus(Diagnostics.Project_0_is_up_to_date, project); - } - projectPendingBuild.delete(projectPath); - return; - } - - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) { - reportAndStoreErrors(projectPath, config.errors); - // Fake that files have been built by updating output file stamps - updateOutputTimestamps(config, projectPath); - projectPendingBuild.delete(projectPath); - return; - } - - if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportAndStoreErrors(projectPath, config.errors); - if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName); - projectPendingBuild.delete(projectPath); - return; - } - - if (status.type === UpToDateStatusType.ContainerOnly) { - reportAndStoreErrors(projectPath, config.errors); - // Do nothing - projectPendingBuild.delete(projectPath); - return; - } - - const buildResult = needsBuild(status, config) ? - buildSingleProject(project, projectPath, cancellationToken) : // Actual build - updateBundle(project, projectPath, cancellationToken); // Fake that files have been built by manipulating prepend and existing output - projectPendingBuild.delete(projectPath); - // Only composite projects can be referenced by other projects - if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { - queueReferencingProjects(project, projectPath, projectIndex, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); - } - } - - function queueReferencingProjects(project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, projectIndex: number, declarationOutputChanged: boolean) { - // Always use build order to queue projects - const buildOrder = getBuildOrder(); - for (let index = projectIndex + 1; index < buildOrder.length; index++) { - const nextProject = buildOrder[index]; - const nextProjectPath = toResolvedConfigFilePath(nextProject); - if (projectPendingBuild.has(nextProjectPath)) continue; - - const nextProjectConfig = parseConfigFile(nextProject, nextProjectPath); - if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; - for (const ref of nextProjectConfig.projectReferences) { - const resolvedRefPath = resolveProjectName(ref.path); - if (toResolvedConfigFilePath(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 = projectStatus.get(nextProjectPath); - if (status) { - switch (status.type) { - case UpToDateStatusType.UpToDate: - if (!declarationOutputChanged) { - if (ref.prepend) { - 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 (declarationOutputChanged) { - projectStatus.set(nextProjectPath, { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, - newerProjectName: project - }); - } - break; - - case UpToDateStatusType.UpstreamBlocked: - if (toResolvedConfigFilePath(resolveProjectName(status.upstreamProjectName)) === projectPath) { - clearProjectStatus(nextProjectPath); - } - break; - } - } - addProjToQueue(nextProjectPath, ConfigFileProgramReloadLevel.None); - break; - } - } - } - - function createBuildOrder(roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { - const temporaryMarks = createMap() as ConfigFileMap; - const permanentMarks = createMap() as ConfigFileMap; - const circularityReportStack: string[] = []; - let buildOrder: ResolvedConfigFileName[] | undefined; - for (const root of roots) { - visit(root); - } - - return buildOrder || emptyArray; - - function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { - const projPath = toResolvedConfigFilePath(configFileName); - // Already visited - if (permanentMarks.has(projPath)) return; - // Circular - if (temporaryMarks.has(projPath)) { - if (!inCircularContext) { - // TODO:: Do we report this as error? - reportStatus(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(configFileName, projPath); - if (parsed && parsed.projectReferences) { - for (const ref of parsed.projectReferences) { - const resolvedRefPath = resolveProjectName(ref.path); - visit(resolvedRefPath, inCircularContext || ref.circular); - } - } - - circularityReportStack.pop(); - permanentMarks.set(projPath, true); - (buildOrder || (buildOrder = [])).push(configFileName); - } - } - - function buildSingleProject(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { - if (options.dry) { - reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj); - return BuildResultFlags.Success; - } - - if (options.verbose) reportStatus(Diagnostics.Building_project_0, proj); - - let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; - - const configFile = parseConfigFile(proj, resolvedPath); - if (!configFile) { - // Failed to read the config file - resultFlags |= BuildResultFlags.ConfigFileErrors; - reportParseConfigFileDiagnostic(resolvedPath); - projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); - return resultFlags; - } - if (configFile.fileNames.length === 0) { - reportAndStoreErrors(resolvedPath, configFile.errors); - // Nothing to build - must be a solution file, basically - return BuildResultFlags.None; - } - - // TODO: handle resolve module name to cache result in project reference redirect - projectCompilerOptions = configFile.options; - // Update module resolution cache if needed - if (moduleResolutionCache) { - const projPath = toPath(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: projectCompilerOptions.configFile!, - commandLine: configFile - }; - moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); - } - moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(projectCompilerOptions); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(projectCompilerOptions); - } - - const program = host.createProgram( - configFile.fileNames, - configFile.options, - compilerHost, - getOldProgram(resolvedPath, configFile), - configFile.errors, - configFile.projectReferences - ); - - // Don't emit anything in the presence of syntactic errors or options diagnostics - const syntaxDiagnostics = [ - ...program.getConfigFileParsingDiagnostics(), - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)]; - if (syntaxDiagnostics.length) { - return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic"); - } - - // Same as above but now for semantic diagnostics - const semanticDiagnostics = program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken); - if (semanticDiagnostics.length) { - return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic"); - } - - // 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 newestDeclarationFileContentChangedTime = minimumDate; - let anyDtsChanged = false; - let declDiagnostics: Diagnostic[] | undefined; - const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); - const outputFiles: OutputFile[] = []; - emitFilesAndReportErrors( - program, - reportDeclarationDiagnostics, - /*writeFileName*/ undefined, - /*reportSummary*/ undefined, - (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), - cancellationToken - ); - // Don't emit .d.ts if there are decl file errors - if (declDiagnostics) { - program.restoreState(); - return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file"); - } - - // Actual Emit - 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) && readFileWithCache(name) === text) { - priorChangeTime = host.getModifiedTime(name); - } - else { - resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged; - anyDtsChanged = true; - } - } - - emittedOutputs.set(toPath(name), name); - writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); - if (priorChangeTime !== undefined) { - newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); - } - }); - - const emitDiagnostics = emitterDiagnostics.getDiagnostics(); - if (emitDiagnostics.length) { - return buildErrors(emitDiagnostics, BuildResultFlags.EmitErrors, "Emit"); - } - - if (writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(configFile, name)); - listFiles(program, writeFileName); - } - - // Update time stamps for rest of the outputs - newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - - const status: Status.UpToDate = { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) - }; - diagnostics.delete(resolvedPath); - projectStatus.set(resolvedPath, status); - afterProgramCreate(resolvedPath, program); - projectCompilerOptions = baseCompilerOptions; - return resultFlags; - - function buildErrors(diagnostics: ReadonlyArray, errorFlags: BuildResultFlags, errorType: string) { - resultFlags |= errorFlags; - reportAndStoreErrors(resolvedPath, diagnostics); - // List files if any other build error using program (emit errors already report files) - if (writeFileName) listFiles(program, writeFileName); - projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); - afterProgramCreate(resolvedPath, program); - projectCompilerOptions = baseCompilerOptions; - return resultFlags; - } - } - - function listEmittedFile(proj: ParsedCommandLine, file: string) { - if (writeFileName && proj.options.listEmittedFiles) { - writeFileName(`TSFILE: ${file}`); - } - } - - function afterProgramCreate(proj: ResolvedConfigFilePath, program: T) { - if (host.afterProgramEmitAndDiagnostics) { - host.afterProgramEmitAndDiagnostics(program); - } - if (watch) { - program.releaseProgram(); - builderPrograms.set(proj, program); - } - } - - function getOldProgram(proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { - if (options.force) return undefined; - const value = builderPrograms.get(proj); - if (value) return value; - return readBuilderProgram(parsed.options, readFileWithCache) as any as T; - } - - function updateBundle(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { - if (options.dry) { - reportStatus(Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); - return BuildResultFlags.Success; - } - - if (options.verbose) reportStatus(Diagnostics.Updating_output_of_project_0, proj); - - // Update js, and source map - const config = Debug.assertDefined(parseConfigFile(proj, resolvedPath)); - projectCompilerOptions = config.options; - const outputFiles = emitUsingBuildInfo( - config, - compilerHost, - ref => { - const refName = resolveProjectName(ref.path); - return parseConfigFile(refName, toResolvedConfigFilePath(refName)); - }); - if (isString(outputFiles)) { - reportStatus(Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(outputFiles)); - return buildSingleProject(proj, resolvedPath, cancellationToken); - } - - // Actual Emit - Debug.assert(!!outputFiles.length); - const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createMap() as FileMap; - outputFiles.forEach(({ name, text, writeByteOrderMark }) => { - emittedOutputs.set(toPath(name), name); - writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); - }); - const emitDiagnostics = emitterDiagnostics.getDiagnostics(); - if (emitDiagnostics.length) { - reportAndStoreErrors(resolvedPath, emitDiagnostics); - projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Emit errors" }); - projectCompilerOptions = baseCompilerOptions; - return BuildResultFlags.DeclarationOutputUnchanged | BuildResultFlags.EmitErrors; - } - - if (writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(config, name)); - } - - // Update timestamps for dts - const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(config, minimumDate, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - - const status: Status.UpToDate = { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles[0].name - }; - - diagnostics.delete(resolvedPath); - projectStatus.set(resolvedPath, status); - projectCompilerOptions = baseCompilerOptions; - return BuildResultFlags.DeclarationOutputUnchanged; - } - - function updateOutputTimestamps(proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { - if (options.dry) { - return reportStatus(Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); - } - const priorNewestUpdateTime = updateOutputTimestampsWorker(proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); - const status: Status.UpToDate = { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: priorNewestUpdateTime, - oldestOutputFileName: getFirstProjectOutput(proj, !host.useCaseSensitiveFileNames()) - }; - projectStatus.set(resolvedPath, status); - } - - function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { - const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); - if (!skipOutputs || outputs.length !== skipOutputs.size) { - if (options.verbose) { - reportStatus(verboseMessage, proj.options.configFilePath!); - } - const now = host.now ? host.now() : new Date(); - for (const file of outputs) { - if (skipOutputs && skipOutputs.has(toPath(file))) { - continue; - } - - if (isDeclarationFile(file)) { - priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); - } - - host.setModifiedTime(file, now); - listEmittedFile(proj, file); - } - } - - return priorNewestUpdateTime; - } - - function clean(project?: string) { - const buildOrder = getBuildOrderFor(project); - if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; - - const filesToDelete = options.dry ? [] as string[] : undefined; - for (const proj of buildOrder) { - const resolvedPath = toResolvedConfigFilePath(proj); - const parsed = parseConfigFile(proj, resolvedPath); - if (parsed === undefined) { - // File has gone missing; fine to ignore here - reportParseConfigFileDiagnostic(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); - invalidateResolvedProject(resolvedPath, ConfigFileProgramReloadLevel.None); - } - } - } - } - - if (filesToDelete) { - reportStatus(Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); - } - - return ExitStatus.Success; - } - - function resolveProjectName(name: string): ResolvedConfigFileName { - return resolveConfigFileProjectName(resolvePath(host.getCurrentDirectory(), name)); - } - - function resolveProjectNames(configFileNames: ReadonlyArray): ResolvedConfigFileName[] { - return configFileNames.map(resolveProjectName); - } - - function enableCache() { - if (cacheState) { - disableCache(); - } - - const originalReadFileWithCache = readFileWithCache; - const originalGetSourceFile = compilerHost.getSourceFile; - - const { originalReadFile, originalFileExists, originalDirectoryExists, - originalCreateDirectory, originalWriteFile, getSourceFileWithCache, - readFileWithCache: newReadFileWithCache - } = changeCompilerHostLikeToUseCache(host, toPath, (...args) => originalGetSourceFile.call(compilerHost, ...args)); - readFileWithCache = newReadFileWithCache; - compilerHost.getSourceFile = getSourceFileWithCache!; - - cacheState = { - originalReadFile, - originalFileExists, - originalDirectoryExists, - originalCreateDirectory, - originalWriteFile, - originalReadFileWithCache, - originalGetSourceFile, - }; - } - - function disableCache() { - if (!cacheState) return; - - host.readFile = cacheState.originalReadFile; - host.fileExists = cacheState.originalFileExists; - host.directoryExists = cacheState.originalDirectoryExists; - host.createDirectory = cacheState.originalCreateDirectory; - host.writeFile = cacheState.originalWriteFile; - compilerHost.getSourceFile = cacheState.originalGetSourceFile; - readFileWithCache = cacheState.originalReadFileWithCache; - extendedConfigCache.clear(); - if (moduleResolutionCache) { - moduleResolutionCache.directoryToModuleNameMap.clear(); - moduleResolutionCache.moduleNameToDirectoryMap.clear(); - } - cacheState = undefined; - } - - function getBuildOrderFor(project: string | undefined) { - const resolvedProject = project && resolveProjectName(project); - if (resolvedProject) { - const projectPath = toResolvedConfigFilePath(resolvedProject); - const projectIndex = findIndex( - getBuildOrder(), - configFileName => toResolvedConfigFilePath(configFileName) === projectPath - ); - if (projectIndex === -1) return undefined; - } - return resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder(); - } - - function setupInitialBuild(cancellationToken: CancellationToken | undefined) { - // Set initial build if not already built - if (allProjectBuildPending) { - allProjectBuildPending = false; - if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } - enableCache(); - const buildOrder = getBuildOrder(); - reportBuildQueue(buildOrder); - buildOrder.forEach(configFileName => - projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None)); - - if (cancellationToken) { - cancellationToken.throwIfCancellationRequested(); - } - } - } - - function buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined { - setupInitialBuild(cancellationToken); - const invalidatedProject = getNextInvalidatedProject(getBuildOrder()); - if (!invalidatedProject) return undefined; - - buildInvalidatedProject(invalidatedProject, cancellationToken); - return { - project: invalidatedProject.project, - result: diagnostics.has(invalidatedProject.projectPath) ? - ExitStatus.DiagnosticsPresent_OutputsSkipped : - ExitStatus.Success - }; - } - - function build(project?: string, cancellationToken?: CancellationToken): ExitStatus { - const buildOrder = getBuildOrderFor(project); - if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; - - setupInitialBuild(cancellationToken); - - let successfulProjects = 0; - let errorProjects = 0; - while (true) { - const invalidatedProject = getNextInvalidatedProject(buildOrder); - if (!invalidatedProject) { - if (needsSummary) { - disableCache(); - reportErrorSummary(); - } - if (watchAllProjectsPending) { - watchAllProjectsPending = false; - startWatching(); - } - break; - } - - buildInvalidatedProject(invalidatedProject, cancellationToken); - if (diagnostics.has(invalidatedProject.projectPath)) { - errorProjects++; - } - else { - successfulProjects++; - } - } - - return errorProjects ? - successfulProjects ? - ExitStatus.DiagnosticsPresent_OutputsGenerated : - ExitStatus.DiagnosticsPresent_OutputsSkipped : - ExitStatus.Success; - } - - function needsBuild(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 reportParseConfigFileDiagnostic(proj: ResolvedConfigFilePath) { - reportAndStoreErrors(proj, [configFileCache.get(proj) as Diagnostic]); - } - - function reportAndStoreErrors(proj: ResolvedConfigFilePath, errors: ReadonlyArray) { - reportErrors(errors); - projectErrorsReported.set(proj, true); - if (errors.length) { - diagnostics.set(proj, errors); - } - } - - function reportErrors(errors: ReadonlyArray) { - errors.forEach(err => host.reportDiagnostic(err)); - } - - /** - * Report the build ordering inferred from the current project graph if we're in verbose mode - */ - function reportBuildQueue(buildQueue: readonly ResolvedConfigFileName[]) { - if (options.verbose) { - reportStatus(Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(s)).join("")); - } - } - - function relName(path: string): string { - return convertToRelativePath(path, host.getCurrentDirectory(), f => compilerHost.getCanonicalFileName(f)); - } - - /** - * Report the up-to-date status of a project if we're in verbose mode - */ - function verboseReportProjectStatus(configFileName: string, status: UpToDateStatus) { - if (!options.verbose) return; - return formatUpToDateStatus(configFileName, status, relName, reportStatus); + 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 formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { + function reportErrors({ host }: SolutionBuilderState, errors: ReadonlyArray) { + errors.forEach(err => host.reportDiagnostic(err)); + } + + function reportAndStoreErrors(state: SolutionBuilderState, proj: ResolvedConfigFilePath, errors: ReadonlyArray) { + 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) { + if (!state.needsSummary || (!state.watch && !state.host.reportErrorSummary)) return; + state.needsSummary = false; + const { diagnostics } = state; + // Report errors from the other projects + getBuildOrder(state).forEach(project => { + const projectPath = toResolvedConfigFilePath(state, project); + if (!state.projectErrorsReported.has(projectPath)) { + reportErrors(state, diagnostics.get(projectPath) || emptyArray); + } + }); + let totalErrors = 0; + diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); + if (state.watch) { + reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); + } + else { + 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 formatMessage(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, - relName(configFileName), - relName(status.outOfDateOutputFileName), - relName(status.newerInputFileName)); + 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 formatMessage(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, - relName(configFileName), - relName(status.outOfDateOutputFileName), - relName(status.newerProjectName)); + 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 formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, - relName(configFileName), - relName(status.missingOutputFileName)); + 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 formatMessage(Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, - relName(configFileName), - relName(status.newestInputFileName || ""), - relName(status.oldestOutputFileName || "")); + 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 formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, - relName(configFileName), - relName(status.newerProjectName)); + 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 formatMessage(Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, - relName(configFileName)); + return reportStatus( + state, + Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, + relName(state, configFileName) + ); case UpToDateStatusType.UpstreamOutOfDate: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, - relName(configFileName), - relName(status.upstreamProjectName)); + 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 formatMessage(Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, - relName(configFileName), - relName(status.upstreamProjectName)); + return reportStatus( + state, + Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, + relName(state, configFileName), + relName(state, status.upstreamProjectName) + ); case UpToDateStatusType.Unbuildable: - return formatMessage(Diagnostics.Failed_to_parse_file_0_Colon_1, - relName(configFileName), - status.reason); + return reportStatus( + state, + Diagnostics.Failed_to_parse_file_0_Colon_1, + relName(state, configFileName), + status.reason + ); case UpToDateStatusType.TsVersionOutputOfDate: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, - relName(configFileName), + 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); + version + ); case UpToDateStatusType.ContainerOnly: - // Don't report status on "solution" projects + // Don't report status on "solution" projects case UpToDateStatusType.ComputingUpstream: // Should never leak from getUptoDateStatusWorker break; @@ -1616,4 +1760,13 @@ namespace ts { 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/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 7f9f2260d6d..056e894bf53 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -437,7 +437,7 @@ export class cNew {}`); function verifyInvalidation(expectedToWriteTests: boolean) { // Rebuild this project tick(); - builder.invalidateProject("/src/logic"); + builder.invalidateProject("/src/logic/tsconfig.json" as ResolvedConfigFilePath); builder.buildNextInvalidatedProject(); // The file should be updated assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt"); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 70fe602c8a2..f67b575e979 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -721,7 +721,7 @@ let x: string = 10;`); host.writeFile(logic[1].path, `${logic[1].content} function foo() { }`); - solutionBuilder.invalidateProject(`${project}/${SubProject.logic}`); + solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath); solutionBuilder.buildNextInvalidatedProject(); // not ideal, but currently because of d.ts but no new file is written @@ -734,7 +734,7 @@ function foo() { host.writeFile(logic[1].path, `${logic[1].content} export function gfoo() { }`); - solutionBuilder.invalidateProject(logic[0].path); + solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath); solutionBuilder.buildNextInvalidatedProject(); }, expectedProgramFiles); }); @@ -745,7 +745,7 @@ export function gfoo() { compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, references: [{ path: "../core" }] })); - solutionBuilder.invalidateProject(logic[0].path, ConfigFileProgramReloadLevel.Full); + solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full); solutionBuilder.buildNextInvalidatedProject(); }, [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]); }); @@ -965,7 +965,7 @@ export function gfoo() { host.writeFile(bTs.path, `${bTs.content} export function gfoo() { }`); - solutionBuilder.invalidateProject(bTsconfig.path); + solutionBuilder.invalidateProject(bTsconfig.path.toLowerCase() as ResolvedConfigFilePath); solutionBuilder.buildNextInvalidatedProject(); }, emptyArray, From 6227fabbed1d1c46fcef46ba6ef5bd4fcc86c1af Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 May 2019 13:05:24 -0700 Subject: [PATCH 028/111] Make invalidated project when only need to be built or updated --- src/compiler/tsbuild.ts | 295 ++++++++++++++++++++++++---------------- 1 file changed, 177 insertions(+), 118 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 22bc8ba8238..e015e8bb13f 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -283,14 +283,30 @@ namespace ts { /*@internal*/ buildNextInvalidatedProject(): void; } - interface InvalidatedProject { + const enum InvalidatedProjectKind { + BuildProject, + UpdateBundle, + UpdateOutputFileStamps + } + + interface UpdateOutputFileStampsProject { + readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; + readonly project: ResolvedConfigFileName; + readonly projectPath: ResolvedConfigFilePath; + readonly config: ParsedCommandLine; + } + + interface BuildOrUpdateBundleProject { + readonly kind: InvalidatedProjectKind.BuildProject | InvalidatedProjectKind.UpdateBundle; readonly project: ResolvedConfigFileName; readonly projectPath: ResolvedConfigFilePath; - readonly reloadLevel: ConfigFileProgramReloadLevel; readonly projectIndex: number; + readonly config: ParsedCommandLine; readonly buildOrder: readonly ResolvedConfigFileName[]; } + type InvalidatedProject = UpdateOutputFileStampsProject | BuildOrUpdateBundleProject; + /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic */ @@ -663,15 +679,90 @@ namespace ts { } function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { - return state.projectPendingBuild.size ? - forEach(buildOrder, (project, projectIndex) => { - const projectPath = toResolvedConfigFilePath(state, project); - const reloadLevel = state.projectPendingBuild.get(projectPath); - if (reloadLevel !== undefined) { - return { project, projectPath, reloadLevel, projectIndex, buildOrder }; + if (!state.projectPendingBuild.size) return 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; + + 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; } - }) : - undefined; + + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { + reportAndStoreErrors(state, projectPath, config.errors); + return { + kind: InvalidatedProjectKind.UpdateOutputFileStamps, + project, + projectPath, + config + }; + + continue; + } + } + + if (status.type === UpToDateStatusType.UpstreamBlocked) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + if (options.verbose) reportStatus(state, 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 { + kind: needsBuild(state, status, config) ? + InvalidatedProjectKind.BuildProject : + InvalidatedProjectKind.UpdateBundle, + project, + projectPath, + projectIndex, + config, + buildOrder + }; + } + + return undefined; } function listEmittedFile({ writeFileName }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { @@ -714,7 +805,44 @@ namespace ts { return errorFlags; } - function buildSingleProject(state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { + 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 buildSingleProject( + state: SolutionBuilderState, + proj: ResolvedConfigFileName, + resolvedPath: ResolvedConfigFilePath, + config: ParsedCommandLine, + cancellationToken: CancellationToken | undefined + ): BuildResultFlags { if (state.options.dry) { reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, proj); return BuildResultFlags.Success; @@ -722,54 +850,25 @@ namespace ts { if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, proj); - const { host, projectStatus, diagnostics, compilerHost, moduleResolutionCache, } = state; - const configFile = parseConfigFile(state, proj, resolvedPath); - if (!configFile) { - // Failed to read the config file - reportParseConfigFileDiagnostic(state, resolvedPath); - projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); - return BuildResultFlags.ConfigFileErrors; - } - - if (configFile.fileNames.length === 0) { - reportAndStoreErrors(state, resolvedPath, configFile.errors); + if (config.fileNames.length === 0) { + reportAndStoreErrors(state, resolvedPath, config.errors); // Nothing to build - must be a solution file, basically return BuildResultFlags.None; } - state.projectCompilerOptions = configFile.options; + const { host, projectStatus, diagnostics, compilerHost } = state; + state.projectCompilerOptions = config.options; // Update module resolution cache if needed - if (moduleResolutionCache) { - 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: configFile.options.configFile!, - commandLine: configFile - }; - moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); - } - moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(configFile.options); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(configFile.options); - } + updateModuleResolutionCache(state, proj, config); // Create program const program = host.createProgram( - configFile.fileNames, - configFile.options, + config.fileNames, + config.options, compilerHost, - getOldProgram(state, resolvedPath, configFile), - configFile.errors, - configFile.projectReferences + getOldProgram(state, resolvedPath, config), + config.errors, + config.projectReferences ); // Don't emit anything in the presence of syntactic errors or options diagnostics @@ -867,24 +966,30 @@ namespace ts { } if (state.writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(state, configFile, name)); + emittedOutputs.forEach(name => listEmittedFile(state, config, name)); listFiles(program, state.writeFileName); } // Update time stamps for rest of the outputs - newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); diagnostics.delete(resolvedPath); projectStatus.set(resolvedPath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) + oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()) }); afterProgramCreate(state, resolvedPath, program); state.projectCompilerOptions = state.baseCompilerOptions; return resultFlags; } - function updateBundle(state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags { + function updateBundle( + state: SolutionBuilderState, + proj: ResolvedConfigFileName, + resolvedPath: ResolvedConfigFilePath, + config: ParsedCommandLine, + cancellationToken: CancellationToken | undefined + ): BuildResultFlags { if (state.options.dry) { reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); return BuildResultFlags.Success; @@ -894,7 +999,6 @@ namespace ts { // Update js, and source map const { projectStatus, diagnostics, compilerHost } = state; - const config = Debug.assertDefined(parseConfigFile(state, proj, resolvedPath)); state.projectCompilerOptions = config.options; const outputFiles = emitUsingBuildInfo( config, @@ -905,7 +1009,7 @@ namespace ts { }); if (isString(outputFiles)) { reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(state, outputFiles)); - return buildSingleProject(state, proj, resolvedPath, cancellationToken); + return buildSingleProject(state, proj, resolvedPath, config, cancellationToken); } // Actual Emit @@ -1210,68 +1314,24 @@ namespace ts { !isIncrementalCompilation(config.options); } - function buildInvalidatedProject(state: SolutionBuilderState, { project, projectPath, reloadLevel, projectIndex, buildOrder }: InvalidatedProject, cancellationToken?: CancellationToken) { - const { options, projectPendingBuild } = state; - const config = parseConfigFile(state, project, projectPath); - if (!config) { - reportParseConfigFileDiagnostic(state, projectPath); + function buildInvalidatedProject( + state: SolutionBuilderState, + invalidatedProject: InvalidatedProject, + cancellationToken?: CancellationToken + ) { + const { projectPendingBuild } = state; + if (invalidatedProject.kind === InvalidatedProjectKind.UpdateOutputFileStamps) { + // Fake that files have been built by updating output file stamps + const { projectPath, config } = invalidatedProject; + updateOutputTimestamps(state, config, projectPath); projectPendingBuild.delete(projectPath); return; } - 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); - } - return; - } - - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { - reportAndStoreErrors(state, projectPath, config.errors); - projectPendingBuild.delete(projectPath); - // Fake that files have been built by updating output file stamps - updateOutputTimestamps(state, config, projectPath); - return; - } - } - - if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportAndStoreErrors(state, projectPath, config.errors); - projectPendingBuild.delete(projectPath); - if (options.verbose) reportStatus(state, Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName); - return; - } - - if (status.type === UpToDateStatusType.ContainerOnly) { - reportAndStoreErrors(state, projectPath, config.errors); - projectPendingBuild.delete(projectPath); - // Do nothing - return; - } - - const buildResult = needsBuild(state, status, config) ? - buildSingleProject(state, project, projectPath, cancellationToken) : // Actual build - updateBundle(state, project, projectPath, cancellationToken); // Fake that files have been built by manipulating prepend and existing output + const { kind, project, projectPath, projectIndex, config, buildOrder } = invalidatedProject; + const buildResult = kind === InvalidatedProjectKind.BuildProject ? + buildSingleProject(state, project, projectPath, config, cancellationToken) : // Actual build + updateBundle(state, project, projectPath, config, cancellationToken); // Fake that files have been built by manipulating prepend and existing output projectPendingBuild.delete(projectPath); // Only composite projects can be referenced by other projects if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { @@ -1467,12 +1527,11 @@ namespace ts { if (state.watch && !state.timerToBuildInvalidatedProject) { scheduleBuildInvalidatedProject(state); } - } - else { - disableCache(state); - reportErrorSummary(state); + return; } } + disableCache(state); + reportErrorSummary(state); } function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { From 5270b7e9b0da74d258bf96045a51878623acc5f7 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 May 2019 14:14:30 -0700 Subject: [PATCH 029/111] Make invalidated projects as api so we can expose it later --- src/compiler/tsbuild.ts | 210 +++++++++++++++++++++++++--------------- 1 file changed, 132 insertions(+), 78 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index e015e8bb13f..b99567e54a7 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -283,30 +283,6 @@ namespace ts { /*@internal*/ buildNextInvalidatedProject(): void; } - const enum InvalidatedProjectKind { - BuildProject, - UpdateBundle, - UpdateOutputFileStamps - } - - interface UpdateOutputFileStampsProject { - readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; - readonly project: ResolvedConfigFileName; - readonly projectPath: ResolvedConfigFilePath; - readonly config: ParsedCommandLine; - } - - interface BuildOrUpdateBundleProject { - readonly kind: InvalidatedProjectKind.BuildProject | InvalidatedProjectKind.UpdateBundle; - readonly project: ResolvedConfigFileName; - readonly projectPath: ResolvedConfigFilePath; - readonly projectIndex: number; - readonly config: ParsedCommandLine; - readonly buildOrder: readonly ResolvedConfigFileName[]; - } - - type InvalidatedProject = UpdateOutputFileStampsProject | BuildOrUpdateBundleProject; - /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic */ @@ -678,6 +654,121 @@ namespace ts { } } + const enum InvalidatedProjectKind { + Build, + UpdateBundle, + UpdateOutputFileStamps + } + + interface InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind; + readonly project: ResolvedConfigFileName; + readonly projectPath: ResolvedConfigFilePath; + /** + * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly + */ + done(cancellationToken?: CancellationToken): void; + } + + interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; + updateOutputFileStatmps(): void; + } + + interface BuildInvalidedProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.Build; + build(cancellationToken?: CancellationToken): BuildResultFlags; + } + + interface UpdateBundleProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateBundle; + updateBundle(cancellationToken?: CancellationToken): BuildResultFlags; + } + + type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; + + function createUpdateOutputFileStampsProject(state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, config: ParsedCommandLine): UpdateOutputFileStampsProject { + let updateOutputFileStampsPending = true; + return { + kind: InvalidatedProjectKind.UpdateOutputFileStamps, + project, + projectPath, + updateOutputFileStatmps: () => { + updateOutputTimestamps(state, config, projectPath); + updateOutputFileStampsPending = false; + }, + done: () => { + if (updateOutputFileStampsPending) { + updateOutputTimestamps(state, config, projectPath); + } + state.projectPendingBuild.delete(projectPath); + } + }; + } + + function createBuildInvalidedProject( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[] + ): BuildInvalidedProject { + let buildPending = true; + return { + kind: InvalidatedProjectKind.Build, + project, + projectPath, + build, + done: cancellationToken => { + if (buildPending) build(cancellationToken); + state.projectPendingBuild.delete(projectPath); + } + }; + + function build(cancellationToken?: CancellationToken) { + const buildResult = buildSingleProject(state, project, projectPath, config, cancellationToken); + queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, buildResult); + buildPending = false; + return buildResult; + } + } + + function createUpdateBundleProject( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[] + ): UpdateBundleProject { + let updatePending = true; + return { + kind: InvalidatedProjectKind.UpdateBundle, + project, + projectPath, + updateBundle: update, + done: cancellationToken => { + if (updatePending) update(cancellationToken); + state.projectPendingBuild.delete(projectPath); + } + }; + + function update(cancellationToken?: CancellationToken) { + const buildResult = updateBundle(state, project, projectPath, config, cancellationToken); + queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, buildResult); + updatePending = false; + return buildResult; + } + } + + 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: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { if (!state.projectPendingBuild.size) return undefined; @@ -724,14 +815,12 @@ namespace ts { if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { reportAndStoreErrors(state, projectPath, config.errors); - return { - kind: InvalidatedProjectKind.UpdateOutputFileStamps, + return createUpdateOutputFileStampsProject( + state, project, projectPath, config - }; - - continue; + ); } } @@ -749,17 +838,9 @@ namespace ts { continue; } - - return { - kind: needsBuild(state, status, config) ? - InvalidatedProjectKind.BuildProject : - InvalidatedProjectKind.UpdateBundle, - project, - projectPath, - projectIndex, - config, - buildOrder - }; + return needsBuild(state, status, config) ? + createBuildInvalidedProject(state, project, projectPath, projectIndex, config, buildOrder) : + createUpdateBundleProject(state, project, projectPath, projectIndex, config, buildOrder); } return undefined; @@ -1307,46 +1388,19 @@ namespace ts { }); } - 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 buildInvalidatedProject( - state: SolutionBuilderState, - invalidatedProject: InvalidatedProject, - cancellationToken?: CancellationToken - ) { - const { projectPendingBuild } = state; - if (invalidatedProject.kind === InvalidatedProjectKind.UpdateOutputFileStamps) { - // Fake that files have been built by updating output file stamps - const { projectPath, config } = invalidatedProject; - updateOutputTimestamps(state, config, projectPath); - projectPendingBuild.delete(projectPath); - return; - } - - const { kind, project, projectPath, projectIndex, config, buildOrder } = invalidatedProject; - const buildResult = kind === InvalidatedProjectKind.BuildProject ? - buildSingleProject(state, project, projectPath, config, cancellationToken) : // Actual build - updateBundle(state, project, projectPath, config, cancellationToken); // Fake that files have been built by manipulating prepend and existing output - projectPendingBuild.delete(projectPath); - // Only composite projects can be referenced by other projects - if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) { - queueReferencingProjects(state, project, projectPath, projectIndex, buildOrder, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)); - } - } - function queueReferencingProjects( state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, projectIndex: number, + config: ParsedCommandLine, buildOrder: readonly ResolvedConfigFileName[], - declarationOutputChanged: boolean + 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]; @@ -1365,7 +1419,7 @@ namespace ts { if (status) { switch (status.type) { case UpToDateStatusType.UpToDate: - if (!declarationOutputChanged) { + if (buildResult & BuildResultFlags.DeclarationOutputUnchanged) { if (ref.prepend) { state.projectStatus.set(nextProjectPath, { type: UpToDateStatusType.OutOfDateWithPrepend, @@ -1382,7 +1436,7 @@ namespace ts { // falls through case UpToDateStatusType.UpToDateWithUpstreamTypes: case UpToDateStatusType.OutOfDateWithPrepend: - if (declarationOutputChanged) { + if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { state.projectStatus.set(nextProjectPath, { type: UpToDateStatusType.OutOfDateWithUpstream, outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, @@ -1409,7 +1463,7 @@ namespace ts { const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); if (!invalidatedProject) return undefined; - buildInvalidatedProject(state, invalidatedProject, cancellationToken); + invalidatedProject.done(cancellationToken); return { project: invalidatedProject.project, result: state.diagnostics.has(invalidatedProject.projectPath) ? @@ -1429,7 +1483,7 @@ namespace ts { while (true) { const invalidatedProject = getNextInvalidatedProject(state, buildOrder); if (!invalidatedProject) break; - buildInvalidatedProject(state, invalidatedProject, cancellationToken); + invalidatedProject.done(cancellationToken); if (state.diagnostics.has(invalidatedProject.projectPath)) { errorProjects++; } @@ -1521,7 +1575,7 @@ namespace ts { } const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); if (invalidatedProject) { - buildInvalidatedProject(state, invalidatedProject); + invalidatedProject.done(); if (state.projectPendingBuild.size) { // Schedule next project for build if (state.watch && !state.timerToBuildInvalidatedProject) { From e4fe4acc17d3f7dbae6e911c49b1604e114c1660 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 May 2019 14:22:34 -0700 Subject: [PATCH 030/111] Make update bundle return invalidated project if it cant update the bundle --- src/compiler/tsbuild.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index b99567e54a7..05e6d0d2790 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -682,7 +682,7 @@ namespace ts { interface UpdateBundleProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.UpdateBundle; - updateBundle(cancellationToken?: CancellationToken): BuildResultFlags; + updateBundle(): BuildResultFlags | BuildInvalidedProject; } type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; @@ -749,13 +749,21 @@ namespace ts { projectPath, updateBundle: update, done: cancellationToken => { - if (updatePending) update(cancellationToken); + if (updatePending) { + const result = update(); + if ((result as BuildInvalidedProject).project) { + return (result as BuildInvalidedProject).done(cancellationToken); + } + } state.projectPendingBuild.delete(projectPath); } }; - function update(cancellationToken?: CancellationToken) { - const buildResult = updateBundle(state, project, projectPath, config, cancellationToken); + function update() { + const buildResult = updateBundle(state, project, projectPath, config); + if (isString(buildResult)) { + return createBuildInvalidedProject(state, project, projectPath, projectIndex, config, buildOrder); + } queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, buildResult); updatePending = false; return buildResult; @@ -1068,9 +1076,8 @@ namespace ts { state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, - config: ParsedCommandLine, - cancellationToken: CancellationToken | undefined - ): BuildResultFlags { + config: ParsedCommandLine + ): BuildResultFlags | string { if (state.options.dry) { reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); return BuildResultFlags.Success; @@ -1090,7 +1097,7 @@ namespace ts { }); if (isString(outputFiles)) { reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(state, outputFiles)); - return buildSingleProject(state, proj, resolvedPath, config, cancellationToken); + return outputFiles; } // Actual Emit From 9f9ae000cb395543650306c53b9995aed94efcb8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 May 2019 15:53:01 -0700 Subject: [PATCH 031/111] Enable getSemanticDiagnosticsOfNextAffectedFile for EmitAndSemanticDiagnosticsBuilder --- src/compiler/builder.ts | 22 ++++---- src/harness/fakes.ts | 52 ++++++++++--------- .../unittests/tscWatch/incremental.ts | 16 +++++- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 78058bf5a53..7190407dd86 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -796,6 +796,7 @@ namespace ts { (result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; } else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (result as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; (result as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; } else { @@ -913,6 +914,11 @@ namespace ts { ); } + // Add file to affected file pending emit to handle for later emit time + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + addToAffectedFilesPendingEmit(state, [(affected as SourceFile).path]); + } + // Get diagnostics for the affected file if its not ignored if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { // Get next affected file @@ -951,18 +957,8 @@ namespace ts { // When semantic builder asks for diagnostics of the whole program, // ensure that all the affected files are handled - let affected: SourceFile | Program | undefined; - let affectedFilesPendingEmit: Path[] | undefined; - while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) { - if (affected !== state.program && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { - (affectedFilesPendingEmit || (affectedFilesPendingEmit = [])).push((affected as SourceFile).path); - } - doneWithAffectedFile(state, affected); - } - - // In case of emit builder, cache the files to be emitted - if (affectedFilesPendingEmit) { - addToAffectedFilesPendingEmit(state, affectedFilesPendingEmit); + // tslint:disable-next-line no-empty + while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { } let diagnostics: Diagnostic[] | undefined; @@ -1181,7 +1177,7 @@ namespace ts { * 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 BuilderProgram { + 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 diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index 489a41dd677..c3a3bb621ce 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -388,6 +388,33 @@ namespace fakes { return ts.compareStringsCaseSensitive(ts.isString(a) ? a : a[0], ts.isString(b) ? b : b[0]); } + export function sanitizeBuildInfoProgram(buildInfo: ts.BuildInfo) { + if (buildInfo.program) { + // reference Map + if (buildInfo.program.referencedMap) { + const referencedMap: ts.MapLike = {}; + for (const path of ts.getOwnKeys(buildInfo.program.referencedMap).sort()) { + referencedMap[path] = buildInfo.program.referencedMap[path].sort(); + } + buildInfo.program.referencedMap = referencedMap; + } + + // exportedModulesMap + if (buildInfo.program.exportedModulesMap) { + const exportedModulesMap: ts.MapLike = {}; + for (const path of ts.getOwnKeys(buildInfo.program.exportedModulesMap).sort()) { + exportedModulesMap[path] = buildInfo.program.exportedModulesMap[path].sort(); + } + buildInfo.program.exportedModulesMap = exportedModulesMap; + } + + // semanticDiagnosticsPerFile + if (buildInfo.program.semanticDiagnosticsPerFile) { + buildInfo.program.semanticDiagnosticsPerFile.sort(compareProgramBuildInfoDiagnostic); + } + } + } + export const version = "FakeTSVersion"; export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { @@ -405,30 +432,7 @@ namespace fakes { public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { if (!ts.isBuildInfoFile(fileName)) return super.writeFile(fileName, content, writeByteOrderMark); const buildInfo = ts.getBuildInfo(content); - if (buildInfo.program) { - // reference Map - if (buildInfo.program.referencedMap) { - const referencedMap: ts.MapLike = {}; - for (const path of ts.getOwnKeys(buildInfo.program.referencedMap).sort()) { - referencedMap[path] = buildInfo.program.referencedMap[path].sort(); - } - buildInfo.program.referencedMap = referencedMap; - } - - // exportedModulesMap - if (buildInfo.program.exportedModulesMap) { - const exportedModulesMap: ts.MapLike = {}; - for (const path of ts.getOwnKeys(buildInfo.program.exportedModulesMap).sort()) { - exportedModulesMap[path] = buildInfo.program.exportedModulesMap[path].sort(); - } - buildInfo.program.exportedModulesMap = exportedModulesMap; - } - - // semanticDiagnosticsPerFile - if (buildInfo.program.semanticDiagnosticsPerFile) { - buildInfo.program.semanticDiagnosticsPerFile.sort(compareProgramBuildInfoDiagnostic); - } - } + sanitizeBuildInfoProgram(buildInfo); buildInfo.version = version; super.writeFile(fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark); } diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index 35ec5bff555..54109dec846 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -105,9 +105,23 @@ namespace ts.tscWatch { result.close(); } + function sanitizeBuildInfo(content: string) { + const buildInfo = getBuildInfo(content); + fakes.sanitizeBuildInfoProgram(buildInfo); + return getBuildInfoText(buildInfo); + } + function checkFileEmit(actual: Map, expected: ReadonlyArray) { assert.equal(actual.size, expected.length, `Actual: ${JSON.stringify(arrayFrom(actual.entries()), /*replacer*/ undefined, " ")}\nExpected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}`); - expected.forEach(file => assert.equal(actual.get(file.path), file.content, `Emit for ${file.path}`)); + expected.forEach(file => { + let expectedContent = file.content; + let actualContent = actual.get(file.path); + if (isBuildInfoFile(file.path)) { + actualContent = actualContent && sanitizeBuildInfo(actualContent); + expectedContent = sanitizeBuildInfo(expectedContent); + } + assert.equal(actualContent, expectedContent, `Emit for ${file.path}`); + }); } const libFileInfo: BuilderState.FileInfo = { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index f13d0ab3f51..df479429c17 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4417,7 +4417,7 @@ declare namespace ts { * 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 */ - interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram { + 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 diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e5e5046794a..4062969e8cd 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4417,7 +4417,7 @@ declare namespace ts { * 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 */ - interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram { + 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 From f0b7e08d2ca2d6380bea31f36d32f28d3b905a84 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 9 May 2019 13:13:50 -0700 Subject: [PATCH 032/111] Move towards BuildInvalidatedProject api where one can query program and perform its operations --- src/compiler/tsbuild.ts | 490 ++++++++++++++++++++++++++-------------- src/compiler/watch.ts | 40 +++- src/tsc/tsc.ts | 2 +- 3 files changed, 359 insertions(+), 173 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 05e6d0d2790..5498c710983 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -675,17 +675,44 @@ namespace ts { updateOutputFileStatmps(): void; } - interface BuildInvalidedProject extends InvalidatedProjectBase { + interface BuildInvalidedProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.Build; - build(cancellationToken?: CancellationToken): BuildResultFlags; + /* + * 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; + getCompilerOptions(): CompilerOptions; + getSourceFile(fileName: string): SourceFile | undefined; + getSourceFiles(): ReadonlyArray; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getConfigFileParsingDiagnostics(): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + 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; + getCurrentDirectory(): string; } - interface UpdateBundleProject extends InvalidatedProjectBase { + interface UpdateBundleProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.UpdateBundle; - updateBundle(): BuildResultFlags | BuildInvalidedProject; + updateBundle(): BuildResultFlags | BuildInvalidedProject; } - type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; + type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; function createUpdateOutputFileStampsProject(state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, config: ParsedCommandLine): UpdateOutputFileStampsProject { let updateOutputFileStampsPending = true; @@ -706,42 +733,318 @@ namespace ts { }; } - function createBuildInvalidedProject( - state: SolutionBuilderState, + function createBuildInvalidedProject( + state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, projectIndex: number, config: ParsedCommandLine, buildOrder: readonly ResolvedConfigFileName[] - ): BuildInvalidedProject { - let buildPending = true; + ): BuildInvalidedProject { + enum Step { + CreateProgram, + SyntaxDiagnostics, + SemanticDiagnostics, + Emit, + QueueReferencingProjects, + Done + } + + let step = Step.CreateProgram; + let program: T | undefined; + let buildResult: BuildResultFlags | undefined; + return { kind: InvalidatedProjectKind.Build, project, projectPath, - build, + getBuilderProgram: () => withProgramOrUndefined(identity), + getProgram: () => + withProgramOrUndefined( + program => program.getProgramOrUndefined() + ), + getCompilerOptions: () => config.options, + 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); + }, + getCurrentDirectory: () => state.currentDirectory, done: cancellationToken => { - if (buildPending) build(cancellationToken); + executeSteps(Step.Done, cancellationToken); state.projectPendingBuild.delete(projectPath); } }; - function build(cancellationToken?: CancellationToken) { - const buildResult = buildSingleProject(state, project, projectPath, config, cancellationToken); - queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, buildResult); - buildPending = false; - return buildResult; + function withProgramOrUndefined(action: (program: T) => U | undefined): U | undefined { + executeSteps(Step.CreateProgram); + return program && action(program); + } + + function withProgramOrEmptyArray(action: (program: T) => ReadonlyArray): ReadonlyArray { + 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: ReadonlyArray, 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, diagnostics, projectStatus } = 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); + } + }); + + const emitDiagnostics = emitterDiagnostics.getDiagnostics(); + if (emitDiagnostics.length) { + buildResult = buildErrors( + state, + projectPath, + program, + emitDiagnostics, + BuildResultFlags.EmitErrors, + "Emit" + ); + step = Step.QueueReferencingProjects; + return emitResult; + } + + if (state.writeFileName) { + emittedOutputs.forEach(name => listEmittedFile(state, config, name)); + listFiles(program!, state.writeFileName); + } + + // Update time stamps for rest of the outputs + newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + diagnostics.delete(projectPath); + projectStatus.set(projectPath, { + type: UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, + oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()) + }); + afterProgramCreate(state, projectPath, program!); + state.projectCompilerOptions = state.baseCompilerOptions; + step++; + buildResult = resultFlags; + return emitResult; + } + + function executeSteps(till: Step, cancellationToken?: CancellationToken) { + 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(/*writeFileCallback*/ undefined, cancellationToken); + 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 createUpdateBundleProject( - state: SolutionBuilderState, + function createUpdateBundleProject( + state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, projectIndex: number, config: ParsedCommandLine, buildOrder: readonly ResolvedConfigFileName[] - ): UpdateBundleProject { + ): UpdateBundleProject { let updatePending = true; return { kind: InvalidatedProjectKind.UpdateBundle, @@ -777,7 +1080,7 @@ namespace ts { !isIncrementalCompilation(config.options); } - function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { + function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { if (!state.projectPendingBuild.size) return undefined; const { options, projectPendingBuild } = state; @@ -925,153 +1228,6 @@ namespace ts { moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options); } - function buildSingleProject( - state: SolutionBuilderState, - proj: ResolvedConfigFileName, - resolvedPath: ResolvedConfigFilePath, - config: ParsedCommandLine, - cancellationToken: CancellationToken | undefined - ): BuildResultFlags { - if (state.options.dry) { - reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, proj); - return BuildResultFlags.Success; - } - - if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, proj); - - if (config.fileNames.length === 0) { - reportAndStoreErrors(state, resolvedPath, config.errors); - // Nothing to build - must be a solution file, basically - return BuildResultFlags.None; - } - - const { host, projectStatus, diagnostics, compilerHost } = state; - state.projectCompilerOptions = config.options; - // Update module resolution cache if needed - updateModuleResolutionCache(state, proj, config); - - // Create program - const program = host.createProgram( - config.fileNames, - config.options, - compilerHost, - getOldProgram(state, resolvedPath, config), - config.errors, - config.projectReferences - ); - - // Don't emit anything in the presence of syntactic errors or options diagnostics - const syntaxDiagnostics = [ - ...program.getConfigFileParsingDiagnostics(), - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)]; - if (syntaxDiagnostics.length) { - return buildErrors( - state, - resolvedPath, - program, - syntaxDiagnostics, - BuildResultFlags.SyntaxErrors, - "Syntactic" - ); - } - - // Same as above but now for semantic diagnostics - const semanticDiagnostics = program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken); - if (semanticDiagnostics.length) { - return buildErrors( - state, - resolvedPath, - program, - semanticDiagnostics, - BuildResultFlags.TypeErrors, - "Semantic" - ); - } - - // 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[] = []; - emitFilesAndReportErrors( - program, - reportDeclarationDiagnostics, - /*writeFileName*/ undefined, - /*reportSummary*/ undefined, - (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), - cancellationToken - ); - // Don't emit .d.ts if there are decl file errors - if (declDiagnostics) { - program.restoreState(); - return buildErrors( - state, - resolvedPath, - program, - declDiagnostics, - BuildResultFlags.DeclarationEmitErrors, - "Declaration file" - ); - } - - // Actual Emit - 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(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); - if (priorChangeTime !== undefined) { - newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); - } - }); - - const emitDiagnostics = emitterDiagnostics.getDiagnostics(); - if (emitDiagnostics.length) { - return buildErrors( - state, - resolvedPath, - program, - emitDiagnostics, - BuildResultFlags.EmitErrors, - "Emit" - ); - } - - if (state.writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(state, config, name)); - listFiles(program, state.writeFileName); - } - - // Update time stamps for rest of the outputs - newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - diagnostics.delete(resolvedPath); - projectStatus.set(resolvedPath, { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()) - }); - afterProgramCreate(state, resolvedPath, program); - state.projectCompilerOptions = state.baseCompilerOptions; - return resultFlags; - } - function updateBundle( state: SolutionBuilderState, proj: ResolvedConfigFileName, diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 4f974add57d..9378478664f 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -116,7 +116,7 @@ namespace ts { getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; getConfigFileParsingDiagnostics(): ReadonlyArray; - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; } export function listFiles(program: ProgramToEmitFilesAndReportErrors, writeFileName: (s: string) => void) { @@ -136,7 +136,9 @@ namespace ts { writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback, - cancellationToken?: CancellationToken + cancellationToken?: CancellationToken, + emitOnlyDtsFiles?: boolean, + customTransformers?: CustomTransformers ) { // First get and report any syntactic errors. const diagnostics = program.getConfigFileParsingDiagnostics().slice(); @@ -155,7 +157,8 @@ namespace ts { } // Emit and report any errors we ran into. - const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken); + const emitResult = program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + const { emittedFiles, diagnostics: emitDiagnostics } = emitResult; addRange(diagnostics, emitDiagnostics); sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); @@ -172,7 +175,34 @@ namespace ts { reportSummary(getErrorCountForSummary(diagnostics)); } - if (emitSkipped && diagnostics.length > 0) { + return { + emitResult, + diagnostics, + }; + } + + export function emitFilesAndReportErrorsAndGetExitStatus( + program: ProgramToEmitFilesAndReportErrors, + reportDiagnostic: DiagnosticReporter, + writeFileName?: (s: string) => void, + reportSummary?: ReportEmitErrorSummary, + writeFile?: WriteFileCallback, + cancellationToken?: CancellationToken, + emitOnlyDtsFiles?: boolean, + customTransformers?: CustomTransformers + ) { + const { emitResult, diagnostics } = emitFilesAndReportErrors( + program, + reportDiagnostic, + writeFileName, + reportSummary, + writeFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers + ); + + if (emitResult.emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. return ExitStatus.DiagnosticsPresent_OutputsSkipped; } @@ -395,7 +425,7 @@ namespace ts { const system = input.system || sys; const host = input.host || (input.host = createIncrementalCompilerHost(input.options, system)); const builderProgram = createIncrementalProgram(input); - const exitStatus = emitFilesAndReportErrors( + const exitStatus = emitFilesAndReportErrorsAndGetExitStatus( builderProgram, input.reportDiagnostic || createDiagnosticReporter(system), s => host.trace && host.trace(s), diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 47814a1f68b..6a340109238 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -246,7 +246,7 @@ namespace ts { configFileParsingDiagnostics }; const program = createProgram(programOptions); - const exitStatus = emitFilesAndReportErrors( + const exitStatus = emitFilesAndReportErrorsAndGetExitStatus( program, reportDiagnostic, s => sys.write(s + sys.newLine), From 8c489bfdf8bd141089c45d888c4ad4b02da53b4b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 9 May 2019 15:30:16 -0700 Subject: [PATCH 033/111] UpdateBundleProject to contain emit method --- src/compiler/emitter.ts | 9 +- src/compiler/tsbuild.ts | 378 +++++++++++++++++++++------------------- 2 files changed, 203 insertions(+), 184 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8f91e3d25c1..2b1e1309609 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -638,7 +638,12 @@ namespace ts { } /*@internal*/ - export function emitUsingBuildInfo(config: ParsedCommandLine, host: EmitUsingBuildInfoHost, getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined): EmitUsingBuildInfoResult { + export function emitUsingBuildInfo( + config: ParsedCommandLine, + host: EmitUsingBuildInfoHost, + getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined, + customTransformers?: CustomTransformers + ): EmitUsingBuildInfoResult { const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false); const buildInfoText = host.readFile(Debug.assertDefined(buildInfoPath)); if (!buildInfoText) return buildInfoPath!; @@ -723,7 +728,7 @@ namespace ts { useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), getProgramBuildInfo: returnUndefined }; - emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, /*emitOnlyDtsFiles*/ false, getTransformers(config.options)); + emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, /*emitOnlyDtsFiles*/ false, getTransformers(config.options, customTransformers)); return outputFiles; } diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 5498c710983..d66874f3a41 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -709,7 +709,7 @@ namespace ts { interface UpdateBundleProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.UpdateBundle; - updateBundle(): BuildResultFlags | BuildInvalidedProject; + emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; } type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; @@ -733,91 +733,109 @@ namespace ts { }; } - function createBuildInvalidedProject( + function createBuildOrUpdateInvalidedProject( + kind: InvalidatedProjectKind.Build | InvalidatedProjectKind.UpdateBundle, state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, projectIndex: number, config: ParsedCommandLine, - buildOrder: readonly ResolvedConfigFileName[] - ): BuildInvalidedProject { + buildOrder: readonly ResolvedConfigFileName[], + ): BuildInvalidedProject | UpdateBundleProject { enum Step { CreateProgram, SyntaxDiagnostics, SemanticDiagnostics, Emit, + EmitBundle, + BuildInvalidatedProjectOfBundle, QueueReferencingProjects, Done } - let step = Step.CreateProgram; + 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, - project, - projectPath, - getBuilderProgram: () => withProgramOrUndefined(identity), - getProgram: () => - withProgramOrUndefined( - program => program.getProgramOrUndefined() - ), - getCompilerOptions: () => config.options, - 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) - ); + return kind === InvalidatedProjectKind.Build ? + { + kind, + project, + projectPath, + getBuilderProgram: () => withProgramOrUndefined(identity), + getProgram: () => + withProgramOrUndefined( + program => program.getProgramOrUndefined() + ), + getCompilerOptions: () => config.options, + 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); + }, + getCurrentDirectory: () => state.currentDirectory, + done: cancellationToken => { + executeSteps(Step.Done, cancellationToken); + state.projectPendingBuild.delete(projectPath); } - executeSteps(Step.SemanticDiagnostics, cancellationToken); - if (step !== Step.Emit) return undefined; - return emit(writeFile, cancellationToken, customTransformers); - }, - getCurrentDirectory: () => state.currentDirectory, - done: cancellationToken => { - executeSteps(Step.Done, cancellationToken); - state.projectPendingBuild.delete(projectPath); - } - }; + } : + { + kind, + project, + projectPath, + emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => { + if (step !== Step.EmitBundle) return invalidatedProjectOfBundle; + return emitBundle(writeFile, customTransformers); + }, + done: cancellationToken => { + executeSteps(Step.Done, cancellationToken); + state.projectPendingBuild.delete(projectPath); + } + }; function withProgramOrUndefined(action: (program: T) => U | undefined): U | undefined { executeSteps(Step.CreateProgram); @@ -941,7 +959,7 @@ namespace ts { } // Actual Emit - const { host, compilerHost, diagnostics, projectStatus } = state; + const { host, compilerHost } = state; let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; let newestDeclarationFileContentChangedTime = minimumDate; let anyDtsChanged = false; @@ -967,6 +985,25 @@ namespace ts { } }); + 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( @@ -978,27 +1015,87 @@ namespace ts { "Emit" ); step = Step.QueueReferencingProjects; - return emitResult; + return emitDiagnostics; } if (state.writeFileName) { emittedOutputs.forEach(name => listEmittedFile(state, config, name)); - listFiles(program!, state.writeFileName); + if (program) listFiles(program, state.writeFileName); } // Update time stamps for rest of the outputs - newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - diagnostics.delete(projectPath); - projectStatus.set(projectPath, { + 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: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()) + newestDeclarationFileContentChangedTime: newestDeclarationFileContentChangedTimeIsMaximumDate ? + maximumDate : + newestDeclarationFileContentChangedTime, + oldestOutputFileName }); - afterProgramCreate(state, projectPath, program!); + if (program) afterProgramCreate(state, projectPath, program); state.projectCompilerOptions = state.baseCompilerOptions; - step++; + step = Step.QueueReferencingProjects; buildResult = resultFlags; - return emitResult; + 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) { @@ -1021,6 +1118,15 @@ namespace ts { emit(/*writeFileCallback*/ undefined, cancellationToken); break; + case Step.EmitBundle: + emitBundle(); + 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++; @@ -1037,42 +1143,6 @@ namespace ts { } } - function createUpdateBundleProject( - state: SolutionBuilderState, - project: ResolvedConfigFileName, - projectPath: ResolvedConfigFilePath, - projectIndex: number, - config: ParsedCommandLine, - buildOrder: readonly ResolvedConfigFileName[] - ): UpdateBundleProject { - let updatePending = true; - return { - kind: InvalidatedProjectKind.UpdateBundle, - project, - projectPath, - updateBundle: update, - done: cancellationToken => { - if (updatePending) { - const result = update(); - if ((result as BuildInvalidedProject).project) { - return (result as BuildInvalidedProject).done(cancellationToken); - } - } - state.projectPendingBuild.delete(projectPath); - } - }; - - function update() { - const buildResult = updateBundle(state, project, projectPath, config); - if (isString(buildResult)) { - return createBuildInvalidedProject(state, project, projectPath, projectIndex, config, buildOrder); - } - queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, buildResult); - updatePending = false; - return buildResult; - } - } - function needsBuild({ options }: SolutionBuilderState, status: UpToDateStatus, config: ParsedCommandLine) { if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; return config.fileNames.length === 0 || @@ -1149,9 +1219,17 @@ namespace ts { continue; } - return needsBuild(state, status, config) ? - createBuildInvalidedProject(state, project, projectPath, projectIndex, config, buildOrder) : - createUpdateBundleProject(state, project, projectPath, projectIndex, config, buildOrder); + return createBuildOrUpdateInvalidedProject( + needsBuild(state, status, config) ? + InvalidatedProjectKind.Build : + InvalidatedProjectKind.UpdateBundle, + state, + project, + projectPath, + projectIndex, + config, + buildOrder, + ); } return undefined; @@ -1228,70 +1306,6 @@ namespace ts { moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options); } - function updateBundle( - state: SolutionBuilderState, - proj: ResolvedConfigFileName, - resolvedPath: ResolvedConfigFilePath, - config: ParsedCommandLine - ): BuildResultFlags | string { - if (state.options.dry) { - reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); - return BuildResultFlags.Success; - } - - if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, proj); - - // Update js, and source map - const { projectStatus, diagnostics, 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)); - }); - if (isString(outputFiles)) { - reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(state, outputFiles)); - return outputFiles; - } - - // 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(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); - }); - const emitDiagnostics = emitterDiagnostics.getDiagnostics(); - if (emitDiagnostics.length) { - return buildErrors( - state, - resolvedPath, - /*program*/ undefined, - emitDiagnostics, - BuildResultFlags.EmitErrors, - "Emit" - ); - } - - if (state.writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(state, config, name)); - } - - // Update timestamps for dts - const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, minimumDate, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - diagnostics.delete(resolvedPath); - projectStatus.set(resolvedPath, { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles[0].name - }); - state.projectCompilerOptions = state.baseCompilerOptions; - return BuildResultFlags.DeclarationOutputUnchanged; - } - function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { // Check tsconfig time const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime; From 97fcea14d51f4187d9373dad235fb3aeee5bf08f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 9 May 2019 13:30:53 -0700 Subject: [PATCH 034/111] Api to get next invalidated project --- src/compiler/tsbuild.ts | 117 ++++++++++-------- src/testRunner/unittests/tsbuild/sample.ts | 12 +- src/testRunner/unittests/tsbuildWatchMode.ts | 4 +- .../reference/api/tsserverlibrary.d.ts | 51 ++++++-- tests/baselines/reference/api/typescript.d.ts | 51 ++++++-- 5 files changed, 164 insertions(+), 71 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index d66874f3a41..f30800c100d 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -264,15 +264,10 @@ namespace ts { export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } - export interface SolutionBuilderResult { - project: ResolvedConfigFileName; - result: T; - } - - export interface SolutionBuilder { + export interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; - buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; + getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; // Currently used for testing but can be made public if needed: /*@internal*/ getBuildOrder(): ReadonlyArray; @@ -325,11 +320,11 @@ namespace ts { return result; } - export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { + export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); } - export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { + export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions); } @@ -380,6 +375,7 @@ namespace ts { allProjectBuildPending: boolean; needsSummary: boolean; watchAllProjectsPending: boolean; + currentInvalidatedProject: InvalidatedProject | undefined; // Watch state readonly watch: boolean; @@ -452,6 +448,7 @@ namespace ts { allProjectBuildPending: true, needsSummary: true, watchAllProjectsPending: watch, + currentInvalidatedProject: undefined, // Watch state watch, @@ -654,28 +651,31 @@ namespace ts { } } - const enum InvalidatedProjectKind { + export enum InvalidatedProjectKind { Build, UpdateBundle, UpdateOutputFileStamps } - interface InvalidatedProjectBase { + export interface InvalidatedProjectBase { readonly kind: InvalidatedProjectKind; readonly project: ResolvedConfigFileName; - readonly projectPath: ResolvedConfigFilePath; + /*@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): void; + done(cancellationToken?: CancellationToken): ExitStatus; + getCompilerOptions(): CompilerOptions; + getCurrentDirectory(): string; } - interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { + export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; updateOutputFileStatmps(): void; } - interface BuildInvalidedProject extends InvalidatedProjectBase { + export interface BuildInvalidedProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.Build; /* * Emitting with this builder program without the api provided for this project @@ -683,7 +683,6 @@ namespace ts { */ getBuilderProgram(): T | undefined; getProgram(): Program | undefined; - getCompilerOptions(): CompilerOptions; getSourceFile(fileName: string): SourceFile | undefined; getSourceFiles(): ReadonlyArray; getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; @@ -704,22 +703,41 @@ namespace ts { 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; - getCurrentDirectory(): string; } - interface UpdateBundleProject extends InvalidatedProjectBase { + export interface UpdateBundleProject extends InvalidatedProjectBase { readonly kind: InvalidatedProjectKind.UpdateBundle; emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; } - type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; + export type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; - function createUpdateOutputFileStampsProject(state: SolutionBuilderState, project: ResolvedConfigFileName, projectPath: ResolvedConfigFilePath, config: ParsedCommandLine): UpdateOutputFileStampsProject { + 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; @@ -728,7 +746,7 @@ namespace ts { if (updateOutputFileStampsPending) { updateOutputTimestamps(state, config, projectPath); } - state.projectPendingBuild.delete(projectPath); + return doneInvalidatedProject(state, projectPath); } }; } @@ -763,12 +781,14 @@ namespace ts { kind, project, projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, getBuilderProgram: () => withProgramOrUndefined(identity), getProgram: () => withProgramOrUndefined( program => program.getProgramOrUndefined() ), - getCompilerOptions: () => config.options, getSourceFile: fileName => withProgramOrUndefined( program => program.getSourceFile(fileName) @@ -817,26 +837,27 @@ namespace ts { if (step !== Step.Emit) return undefined; return emit(writeFile, cancellationToken, customTransformers); }, - getCurrentDirectory: () => state.currentDirectory, - done: cancellationToken => { - executeSteps(Step.Done, cancellationToken); - state.projectPendingBuild.delete(projectPath); - } + 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: cancellationToken => { - executeSteps(Step.Done, cancellationToken); - state.projectPendingBuild.delete(projectPath); - } + done, }; + function done(cancellationToken?: CancellationToken) { + executeSteps(Step.Done, cancellationToken); + return doneInvalidatedProject(state, projectPath); + } + function withProgramOrUndefined(action: (program: T) => U | undefined): U | undefined { executeSteps(Step.CreateProgram); return program && action(program); @@ -1152,6 +1173,12 @@ namespace ts { function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { if (!state.projectPendingBuild.size) 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++) { @@ -1200,7 +1227,8 @@ namespace ts { state, project, projectPath, - config + config, + buildOrder ); } } @@ -1635,20 +1663,6 @@ namespace ts { } } - function buildNextProject(state: SolutionBuilderState, cancellationToken?: CancellationToken): SolutionBuilderResult | undefined { - setupInitialBuild(state, cancellationToken); - const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); - if (!invalidatedProject) return undefined; - - invalidatedProject.done(cancellationToken); - return { - project: invalidatedProject.project, - result: state.diagnostics.has(invalidatedProject.projectPath) ? - ExitStatus.DiagnosticsPresent_OutputsSkipped : - ExitStatus.Success - }; - } - function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken): ExitStatus { const buildOrder = getBuildOrderFor(state, project); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; @@ -1883,14 +1897,17 @@ namespace ts { * 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: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, options: BuildOptions): SolutionBuilder { + function createSolutionBuilderWorker(watch: false, host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, options: BuildOptions): SolutionBuilder { const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options); return { build: (project, cancellationToken) => build(state, project, cancellationToken), clean: project => clean(state, project), - buildNextProject: cancellationToken => buildNextProject(state, cancellationToken), + getNextInvalidatedProject: cancellationToken => { + setupInitialBuild(state, cancellationToken); + return getNextInvalidatedProject(state, getBuildOrder(state)); + }, getBuildOrder: () => getBuildOrder(state), getUpToDateStatusOfProject: project => { const configFileName = resolveProjectName(state, project); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 056e894bf53..130ebd9b8eb 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -350,7 +350,12 @@ namespace ts { assert.equal(result, ExitStatus.InvalidProject_OutputsSkipped); }); - it("building using buildNextProject", () => { + it("building using getNextInvalidatedProject", () => { + interface SolutionBuilderResult { + project: ResolvedConfigFileName; + result: T; + } + const fs = projFs.shadow(); const host = new fakes.SolutionBuilderHost(fs); const builder = createSolutionBuilder(host, ["/src/tests"], {}); @@ -376,8 +381,9 @@ namespace ts { presentOutputs: readonly string[], absentOutputs: readonly string[] ) { - const result = builder.buildNextProject(); - assert.deepEqual(result, expected); + const project = builder.getNextInvalidatedProject(); + const result = project && project.done(); + assert.deepEqual(project && { project: project.project, result }, expected); verifyOutputsPresent(fs, presentOutputs); verifyOutputsAbsent(fs, absentOutputs); } diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index f67b575e979..54f174356b5 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -676,7 +676,7 @@ let x: string = 10;`); } function verifyScenario( - edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, expectedFilesAfterEdit: ReadonlyArray ) { it("with tsc-watch", () => { @@ -899,7 +899,7 @@ export function gfoo() { } function verifyScenario( - edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, + edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder) => void, expectedEditErrors: ReadonlyArray, expectedProgramFiles: ReadonlyArray, expectedWatchedFiles: ReadonlyArray, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index df479429c17..05d823728e4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4594,14 +4594,10 @@ declare namespace ts { } interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } - interface SolutionBuilderResult { - project: ResolvedConfigFileName; - result: T; - } - interface SolutionBuilder { + interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; - buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; + getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; } /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic @@ -4609,8 +4605,47 @@ declare namespace ts { function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter; function createSolutionBuilderHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost; function createSolutionBuilderWithWatchHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost; - function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + enum InvalidatedProjectKind { + Build = 0, + UpdateBundle = 1, + UpdateOutputFileStamps = 2 + } + interface InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind; + readonly project: ResolvedConfigFileName; + /** + * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly + */ + done(cancellationToken?: CancellationToken): ExitStatus; + getCompilerOptions(): CompilerOptions; + getCurrentDirectory(): string; + } + interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; + updateOutputFileStatmps(): void; + } + interface BuildInvalidedProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.Build; + getBuilderProgram(): T | undefined; + getProgram(): Program | undefined; + getSourceFile(fileName: string): SourceFile | undefined; + getSourceFiles(): ReadonlyArray; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getConfigFileParsingDiagnostics(): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; + } + interface UpdateBundleProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateBundle; + emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; + } + type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; } declare namespace ts.server { type ActionSet = "action::set"; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4062969e8cd..357186f140d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4594,14 +4594,10 @@ declare namespace ts { } interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } - interface SolutionBuilderResult { - project: ResolvedConfigFileName; - result: T; - } - interface SolutionBuilder { + interface SolutionBuilder { build(project?: string, cancellationToken?: CancellationToken): ExitStatus; clean(project?: string): ExitStatus; - buildNextProject(cancellationToken?: CancellationToken): SolutionBuilderResult | undefined; + getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; } /** * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic @@ -4609,8 +4605,47 @@ declare namespace ts { function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter; function createSolutionBuilderHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost; function createSolutionBuilderWithWatchHost(system?: System, createProgram?: CreateProgram, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost; - function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + enum InvalidatedProjectKind { + Build = 0, + UpdateBundle = 1, + UpdateOutputFileStamps = 2 + } + interface InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind; + readonly project: ResolvedConfigFileName; + /** + * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly + */ + done(cancellationToken?: CancellationToken): ExitStatus; + getCompilerOptions(): CompilerOptions; + getCurrentDirectory(): string; + } + interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; + updateOutputFileStatmps(): void; + } + interface BuildInvalidedProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.Build; + getBuilderProgram(): T | undefined; + getProgram(): Program | undefined; + getSourceFile(fileName: string): SourceFile | undefined; + getSourceFiles(): ReadonlyArray; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getConfigFileParsingDiagnostics(): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; + } + interface UpdateBundleProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateBundle; + emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; + } + type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; } declare namespace ts.server { type ActionSet = "action::set"; From 8965650bc332a8b86db77c8183098c9d8b511df1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 12 May 2019 23:14:18 +0300 Subject: [PATCH 035/111] ignore trigger chars within a string literal --- src/services/completions.ts | 4 ++- .../completionsStringsWithTriggerCharacter.ts | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionsStringsWithTriggerCharacter.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 9d569fbedde..0d69f5951f8 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -40,7 +40,9 @@ namespace ts.Completions { const compilerOptions = program.getCompilerOptions(); const contextToken = findPrecedingToken(position, sourceFile); - if (triggerCharacter && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) return undefined; + if (triggerCharacter && !isInString(sourceFile, position, contextToken) && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) { + return undefined; + } const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, contextToken, typeChecker, compilerOptions, host, log, preferences); if (stringCompletions) { diff --git a/tests/cases/fourslash/completionsStringsWithTriggerCharacter.ts b/tests/cases/fourslash/completionsStringsWithTriggerCharacter.ts new file mode 100644 index 00000000000..277218305f4 --- /dev/null +++ b/tests/cases/fourslash/completionsStringsWithTriggerCharacter.ts @@ -0,0 +1,30 @@ +/// + +//// type A = "a/b" | "b/a"; +//// const a: A = "a/*1*/"; +//// +//// type B = "a@b" | "b@a"; +//// const a: B = "a@/*2*/"; +//// +//// type C = "a.b" | "b.a"; +//// const c: C = "a./*3*/"; +//// +//// type D = "a'b" | "b'a"; +//// const d: D = "a'/*4*/"; +//// +//// type E = "a`b" | "b`a"; +//// const e: E = "a`/*5*/"; +//// +//// type F = 'a"b' | 'b"a'; +//// const f: F = 'a"/*6*/'; +//// +//// type G = "a Date: Tue, 14 May 2019 16:32:46 -0700 Subject: [PATCH 036/111] Add getParsedCommandLine as optional method on SolutionBuilderHost --- src/compiler/tsbuild.ts | 20 +++++++++++++++---- .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f30800c100d..99bb27aba2c 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -245,6 +245,7 @@ namespace ts { 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; @@ -493,10 +494,17 @@ namespace ts { } let diagnostic: Diagnostic | undefined; - const { parseConfigFileHost, baseCompilerOptions, extendedConfigCache } = state; - parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; - const parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache); - parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; + 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; } @@ -1730,6 +1738,10 @@ namespace ts { } 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; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b9d1f1ffab7..01cef9552db 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4591,6 +4591,7 @@ declare namespace ts { getModifiedTime(fileName: string): Date | undefined; setModifiedTime(fileName: string, date: Date): void; deleteFile(fileName: string): void; + getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; reportDiagnostic: DiagnosticReporter; reportSolutionBuilderStatus: DiagnosticReporter; afterProgramEmitAndDiagnostics?(program: T): void; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index a140501c411..ce0e7b81c82 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4591,6 +4591,7 @@ declare namespace ts { getModifiedTime(fileName: string): Date | undefined; setModifiedTime(fileName: string, date: Date): void; deleteFile(fileName: string): void; + getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; reportDiagnostic: DiagnosticReporter; reportSolutionBuilderStatus: DiagnosticReporter; afterProgramEmitAndDiagnostics?(program: T): void; From 89d1475fde16da386f065e30b5ee7746e383379b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 15 May 2019 09:14:23 -0700 Subject: [PATCH 037/111] Add writeFileCallbacks to done method and also on host --- src/compiler/tsbuild.ts | 19 +++++++++++++------ .../reference/api/tsserverlibrary.d.ts | 8 +++++++- tests/baselines/reference/api/typescript.d.ts | 8 +++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 99bb27aba2c..77de45bee01 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -242,6 +242,13 @@ namespace ts { 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; @@ -673,7 +680,7 @@ namespace ts { /** * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly */ - done(cancellationToken?: CancellationToken): ExitStatus; + done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; getCompilerOptions(): CompilerOptions; getCurrentDirectory(): string; } @@ -861,8 +868,8 @@ namespace ts { done, }; - function done(cancellationToken?: CancellationToken) { - executeSteps(Step.Done, cancellationToken); + function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { + executeSteps(Step.Done, cancellationToken, writeFile, customTransformers); return doneInvalidatedProject(state, projectPath); } @@ -1127,7 +1134,7 @@ namespace ts { return { emitSkipped: false, diagnostics: emitDiagnostics }; } - function executeSteps(till: Step, cancellationToken?: CancellationToken) { + function executeSteps(till: Step, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { while (step <= till && step < Step.Done) { const currentStep = step; switch (step) { @@ -1144,11 +1151,11 @@ namespace ts { break; case Step.Emit: - emit(/*writeFileCallback*/ undefined, cancellationToken); + emit(writeFile, cancellationToken, customTransformers); break; case Step.EmitBundle: - emitBundle(); + emitBundle(writeFile, customTransformers); break; case Step.BuildInvalidatedProjectOfBundle: diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 01cef9552db..05f8195b992 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4588,6 +4588,12 @@ declare namespace ts { } type ReportEmitErrorSummary = (errorCount: number) => void; 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; @@ -4625,7 +4631,7 @@ declare namespace ts { /** * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly */ - done(cancellationToken?: CancellationToken): ExitStatus; + done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; getCompilerOptions(): CompilerOptions; getCurrentDirectory(): string; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ce0e7b81c82..86a37c9f498 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4588,6 +4588,12 @@ declare namespace ts { } type ReportEmitErrorSummary = (errorCount: number) => void; 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; @@ -4625,7 +4631,7 @@ declare namespace ts { /** * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly */ - done(cancellationToken?: CancellationToken): ExitStatus; + done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; getCompilerOptions(): CompilerOptions; getCurrentDirectory(): string; } From 629bc0c04d7126e3481a23742a9688a422ec69e3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 15 May 2019 09:51:02 -0700 Subject: [PATCH 038/111] Always emit tsbuild info if path says so (irrespecitive of if there exists bundle and project) --- src/compiler/emitter.ts | 1 - src/compiler/tsbuild.ts | 2 +- src/harness/fakes.ts | 7 ++++- src/testRunner/unittests/tsbuild/sample.ts | 35 ++++++++++++++++++++-- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6a339e26594..e6e3c44afcd 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -284,7 +284,6 @@ namespace ts { // Write build information if applicable if (!buildInfoPath || targetSourceFile || emitSkipped) return; const program = host.getProgramBuildInfo(); - if (!bundle && !program) return; if (host.isEmitBlocked(buildInfoPath) || compilerOptions.noEmit) { emitSkipped = true; return; diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 77de45bee01..43b1e24a573 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1524,7 +1524,7 @@ namespace ts { if (buildInfoPath) { const value = state.readFileWithCache(buildInfoPath); const buildInfo = value && getBuildInfo(value); - if (buildInfo && buildInfo.version !== version) { + if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) { return { type: UpToDateStatusType.TsVersionOutputOfDate, version: buildInfo.version diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index c3a3bb621ce..31e64e2c931 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -418,7 +418,12 @@ namespace fakes { export const version = "FakeTSVersion"; export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { - createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram; + createProgram: ts.CreateProgram; + + constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram) { + super(sys, options, setParentNodes); + this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram; + } readFile(path: string) { const value = super.readFile(path); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 130ebd9b8eb..25ad761c8f1 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -2,9 +2,9 @@ namespace ts { describe("unittests:: tsbuild:: on 'sample1' project", () => { let projFs: vfs.FileSystem; const { time, tick } = getTime(); - const testsOutputs = ["/src/tests/index.js"]; - const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"]; - const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map"]; + const testsOutputs = ["/src/tests/index.js", "/src/tests/index.d.ts", "/src/tests/tsconfig.tsbuildinfo"]; + const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts", "/src/logic/tsconfig.tsbuildinfo"]; + const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", "/src/core/tsconfig.tsbuildinfo"]; const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs]; before(() => { @@ -272,6 +272,35 @@ namespace ts { ); }); + it("does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder); + let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + builder.build(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"], + [Diagnostics.Building_project_0, "/src/tests/tsconfig.json"] + ); + verifyOutputsPresent(fs, allExpectedOutputs); + + host.clearDiagnostics(); + tick(); + builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + changeCompilerVersion(host); + builder.build(); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"), + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"], + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"], + [Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"] + ); + }); + it("rebuilds from start if --f is passed", () => { const { host, builder } = initializeWithBuild({ force: true }); builder.build(); From 0cb980dd6e009c28de63b79f24bd4f47d4bf33f2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 15 May 2019 14:56:06 -0700 Subject: [PATCH 039/111] Add api to build referenced projects --- src/compiler/tsbuild.ts | 39 +++++++++++++------ src/testRunner/unittests/tsbuild/sample.ts | 16 ++++++++ .../reference/api/tsserverlibrary.d.ts | 2 + tests/baselines/reference/api/typescript.d.ts | 2 + 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 43b1e24a573..8a43dd446f2 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -275,6 +275,8 @@ namespace ts { 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: @@ -565,7 +567,7 @@ namespace ts { (state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)))); } - function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined) { + function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) { const resolvedProject = project && resolveProjectName(state, project); if (resolvedProject) { const projectPath = toResolvedConfigFilePath(state, resolvedProject); @@ -575,7 +577,10 @@ namespace ts { ); if (projectIndex === -1) return undefined; } - return resolvedProject ? createBuildOrder(state, [resolvedProject]) : getBuildOrder(state); + const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) : getBuildOrder(state); + 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) { @@ -653,7 +658,6 @@ namespace ts { if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); } enableCache(state); const buildOrder = getBuildOrder(state); - reportBuildQueue(state, buildOrder); buildOrder.forEach(configFileName => state.projectPendingBuild.set( toResolvedConfigFilePath(state, configFileName), @@ -1186,7 +1190,11 @@ namespace ts { !isIncrementalCompilation(config.options); } - function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined { + function getNextInvalidatedProject( + state: SolutionBuilderState, + buildOrder: readonly ResolvedConfigFileName[], + reportQueue: boolean + ): InvalidatedProject | undefined { if (!state.projectPendingBuild.size) return undefined; if (state.currentInvalidatedProject) { // Only if same buildOrder the currentInvalidated project can be sent again @@ -1202,6 +1210,11 @@ namespace ts { 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); @@ -1678,17 +1691,19 @@ namespace ts { } } - function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken): ExitStatus { - const buildOrder = getBuildOrderFor(state, project); + 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; let errorProjects = 0; while (true) { - const invalidatedProject = getNextInvalidatedProject(state, buildOrder); + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); if (!invalidatedProject) break; + reportQueue = false; invalidatedProject.done(cancellationToken); if (state.diagnostics.has(invalidatedProject.projectPath)) { errorProjects++; @@ -1709,8 +1724,8 @@ namespace ts { ExitStatus.Success; } - function clean(state: SolutionBuilderState, project?: string) { - const buildOrder = getBuildOrderFor(state, project); + function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; const { options, host } = state; @@ -1783,7 +1798,7 @@ namespace ts { state.projectErrorsReported.clear(); reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); } - const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state)); + const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); if (invalidatedProject) { invalidatedProject.done(); if (state.projectPendingBuild.size) { @@ -1923,9 +1938,11 @@ namespace ts { 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)); + return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); }, getBuildOrder: () => getBuildOrder(state), getUpToDateStatusOfProject: project => { diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 25ad761c8f1..df2fb62db25 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -417,6 +417,22 @@ namespace ts { verifyOutputsAbsent(fs, absentOutputs); } }); + + it("building using buildReferencedProject", () => { + const fs = projFs.shadow(); + const host = new fakes.SolutionBuilderHost(fs); + const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true }); + builder.buildReferences("/src/tests"); + host.assertDiagnosticMessages( + getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json"), + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"], + [Diagnostics.Building_project_0, "/src/core/tsconfig.json"], + [Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"], + [Diagnostics.Building_project_0, "/src/logic/tsconfig.json"], + ); + verifyOutputsPresent(fs, [...coreOutputs, ...logicOutputs]); + verifyOutputsAbsent(fs, testsOutputs); + }); }); describe("downstream-blocked compilations", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 05f8195b992..8e78922e636 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4610,6 +4610,8 @@ declare namespace ts { 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; } /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 86a37c9f498..3d0df0fabec 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4610,6 +4610,8 @@ declare namespace ts { 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; } /** From ec4ea0e4748930748c8d76f57708f6a44f92e17b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 16 May 2019 11:21:09 -0700 Subject: [PATCH 040/111] Watch only built projects --- src/compiler/tsbuild.ts | 22 +++++++++++--------- src/testRunner/unittests/tsbuildWatchMode.ts | 22 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 8a43dd446f2..1c1c093bc7a 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -569,15 +569,16 @@ namespace ts { function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) { const resolvedProject = project && resolveProjectName(state, project); + const buildOrderFromState = getBuildOrder(state); if (resolvedProject) { const projectPath = toResolvedConfigFilePath(state, resolvedProject); const projectIndex = findIndex( - getBuildOrder(state), + buildOrderFromState, configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath ); if (projectIndex === -1) return undefined; } - const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) : getBuildOrder(state); + const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) : buildOrderFromState; Debug.assert(!onlyReferences || resolvedProject !== undefined); Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; @@ -1714,8 +1715,8 @@ namespace ts { } disableCache(state); - reportErrorSummary(state); - startWatching(state); + reportErrorSummary(state, buildOrder); + startWatching(state, buildOrder); return errorProjects ? successfulProjects ? @@ -1798,7 +1799,8 @@ namespace ts { state.projectErrorsReported.clear(); reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); } - const invalidatedProject = getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); + const buildOrder = getBuildOrder(state); + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, /*reportQueue*/ false); if (invalidatedProject) { invalidatedProject.done(); if (state.projectPendingBuild.size) { @@ -1810,7 +1812,7 @@ namespace ts { } } disableCache(state); - reportErrorSummary(state); + reportErrorSummary(state, buildOrder); } function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { @@ -1908,10 +1910,10 @@ namespace ts { ); } - function startWatching(state: SolutionBuilderState) { + function startWatching(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]) { if (!state.watchAllProjectsPending) return; state.watchAllProjectsPending = false; - for (const resolved of getBuildOrder(state)) { + for (const resolved of buildOrder) { const resolvedPath = toResolvedConfigFilePath(state, resolved); // Watch this file watchConfigFile(state, resolved, resolvedPath); @@ -1985,12 +1987,12 @@ namespace ts { reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]); } - function reportErrorSummary(state: SolutionBuilderState) { + function reportErrorSummary(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]) { if (!state.needsSummary || (!state.watch && !state.host.reportErrorSummary)) return; state.needsSummary = false; const { diagnostics } = state; // Report errors from the other projects - getBuildOrder(state).forEach(project => { + buildOrder.forEach(project => { const projectPath = toResolvedConfigFilePath(state, project); if (!state.projectErrorsReported.has(projectPath)) { reportErrors(state, diagnostics.get(projectPath) || emptyArray); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 54f174356b5..a630e28f1d8 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -143,6 +143,28 @@ namespace ts.tscWatch { createSolutionInWatchMode(allFiles); }); + it("verify building references watches only those projects", () => { + const system = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation }); + const host = createSolutionBuilderWithWatchHost(system); + const solutionBuilder = ts.createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`], { watch: true }); + solutionBuilder.buildReferences(`${project}/${SubProject.tests}`); + + checkWatchedFiles(system, testProjectExpectedWatchedFiles.slice(0, testProjectExpectedWatchedFiles.length - tests.length)); + checkWatchedDirectories(system, emptyArray, /*recursive*/ false); + checkWatchedDirectories(system, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true); + + checkOutputErrorsInitial(system, emptyArray); + const testOutput = getOutputStamps(system, SubProject.tests, "index"); + const outputFileStamps = getOutputFileStamps(system); + for (const stamp of outputFileStamps.slice(0, outputFileStamps.length - testOutput.length)) { + assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); + } + for (const stamp of testOutput) { + assert.isUndefined(stamp[1], `${stamp[0]} expected to be missing`); + } + return system; + }); + describe("validates the changes and watched files", () => { const newFileWithoutExtension = "newFile"; const newFile: File = { From 138f757709624fbd06a34a97b9d514b0c9f40508 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 16 May 2019 12:50:17 -0700 Subject: [PATCH 041/111] Fix the test since tsbuildinfo is now always emitted (629bc0c) --- src/testRunner/unittests/config/projectReferences.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index 3acffd7a8c6..26ed746d279 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -325,7 +325,7 @@ namespace ts { }; testProjectReferences(spec, "/alpha/tsconfig.json", (program, host) => { program.emit(); - assert.deepEqual(host.outputs.map(e => e.file).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js"]); + assert.deepEqual(host.outputs.map(e => e.file).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js", "/alpha/bin/tsconfig.tsbuildinfo"]); }); }); }); From 098c9008b88b233819d063a93d5190823eb8c6f8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 16 May 2019 14:49:53 -0700 Subject: [PATCH 042/111] Make more build options internal which correspond to internal compiler options Also fix return type of readBuilderProgram --- src/compiler/builder.ts | 2 +- src/compiler/tsbuild.ts | 8 ++++---- tests/baselines/reference/api/tsserverlibrary.d.ts | 6 +----- tests/baselines/reference/api/typescript.d.ts | 6 +----- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 7190407dd86..d4c2132d36b 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -993,7 +993,7 @@ namespace ts { return map; } - export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo): EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram { + export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo): EmitAndSemanticDiagnosticsBuilderProgram { const fileInfos = createMapFromTemplate(program.fileInfos); const state: ReusableBuilderProgramState = { fileInfos, diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 1c1c093bc7a..94718a1b368 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -163,10 +163,10 @@ namespace ts { /*@internal*/ watch?: boolean; /*@internal*/ help?: boolean; - preserveWatchOutput?: boolean; - listEmittedFiles?: boolean; - listFiles?: boolean; - pretty?: boolean; + /*@internal*/ preserveWatchOutput?: boolean; + /*@internal*/ listEmittedFiles?: boolean; + /*@internal*/ listFiles?: boolean; + /*@internal*/ pretty?: boolean; incremental?: boolean; traceResolution?: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8e78922e636..0351c6b343a 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4449,7 +4449,7 @@ declare namespace ts { function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; } declare namespace ts { - function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined): (EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram) | undefined; + function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined): EmitAndSemanticDiagnosticsBuilderProgram | undefined; function createIncrementalCompilerHost(options: CompilerOptions, system?: System): CompilerHost; interface IncrementalProgramOptions { rootNames: ReadonlyArray; @@ -4578,10 +4578,6 @@ declare namespace ts { dry?: boolean; force?: boolean; verbose?: boolean; - preserveWatchOutput?: boolean; - listEmittedFiles?: boolean; - listFiles?: boolean; - pretty?: boolean; incremental?: boolean; traceResolution?: boolean; [option: string]: CompilerOptionsValue | undefined; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3d0df0fabec..9becdad6aa0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4449,7 +4449,7 @@ declare namespace ts { function createAbstractBuilder(rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: BuilderProgram, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray): BuilderProgram; } declare namespace ts { - function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined): (EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram) | undefined; + function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined): EmitAndSemanticDiagnosticsBuilderProgram | undefined; function createIncrementalCompilerHost(options: CompilerOptions, system?: System): CompilerHost; interface IncrementalProgramOptions { rootNames: ReadonlyArray; @@ -4578,10 +4578,6 @@ declare namespace ts { dry?: boolean; force?: boolean; verbose?: boolean; - preserveWatchOutput?: boolean; - listEmittedFiles?: boolean; - listFiles?: boolean; - pretty?: boolean; incremental?: boolean; traceResolution?: boolean; [option: string]: CompilerOptionsValue | undefined; From 0f15bda45fa00f7b0b69305cb4161d2dc4379f1b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 20 May 2019 13:31:44 -0700 Subject: [PATCH 043/111] Add failing test --- .../unittests/services/organizeImports.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index 8116b1e268a..9216a7adf88 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -343,6 +343,19 @@ import { } from "lib"; }, libFile); + testOrganizeImports("Unused_false_positive_module_augmentation", + { + path: "/test.d.ts", + content: ` +import { Caseless } from 'caseless'; + +declare module 'caseless' { + interface Caseless { + test(name: KeyType): boolean; + } +}` + }); + testOrganizeImports("Unused_false_positive_shorthand_assignment", { path: "/test.ts", From 6faeee449d0395529ff7cddc37cf00b988f3c6bf Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 20 May 2019 14:49:28 -0700 Subject: [PATCH 044/111] =?UTF-8?q?Don=E2=80=99t=20remove=20imports=20that?= =?UTF-8?q?=20are=20used=20for=20module=20augmentation,=20just=20remove=20?= =?UTF-8?q?their=20import=20clauses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/organizeImports.ts | 18 ++++++++++++++- .../unittests/services/organizeImports.ts | 4 +++- ...used_false_positive_module_augmentation.ts | 22 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/organizeImports/Unused_false_positive_module_augmentation.ts diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index 5bfa0c74ab1..b6be92fbf9c 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -89,7 +89,7 @@ namespace ts.OrganizeImports { const usedImports: ImportDeclaration[] = []; for (const importDecl of oldImports) { - const {importClause} = importDecl; + const { importClause, moduleSpecifier } = importDecl; if (!importClause) { // Imports without import clauses are assumed to be included for their side effects and are not removed. @@ -125,6 +125,14 @@ namespace ts.OrganizeImports { if (name || namedBindings) { usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); } + // If a module is imported to be augmented, keep the import declaration, but without an import clause + else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { + usedImports.push(createImportDeclaration( + importDecl.decorators, + importDecl.modifiers, + /*importClause*/ undefined, + moduleSpecifier)); + } } return usedImports; @@ -135,6 +143,14 @@ namespace ts.OrganizeImports { } } + function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { + const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; + return isString(moduleSpecifierText) && some(sourceFile.statements, statement => + isModuleDeclaration(statement) + && isStringLiteral(statement.name) + && statement.name.text === moduleSpecifierText); + } + function getExternalModuleName(specifier: Expression) { return specifier !== undefined && isStringLiteralLike(specifier) ? specifier.text diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index 9216a7adf88..8003e1867cd 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -1,5 +1,5 @@ namespace ts { - describe("unittests:: services:: Organize imports", () => { + describe("unittests:: services:: organizeImports", () => { describe("Sort imports", () => { it("Sort - non-relative vs non-relative", () => { assertSortsBefore( @@ -347,8 +347,10 @@ import { } from "lib"; { path: "/test.d.ts", content: ` +import foo from 'foo'; import { Caseless } from 'caseless'; +declare module 'foo' {} declare module 'caseless' { interface Caseless { test(name: KeyType): boolean; diff --git a/tests/baselines/reference/organizeImports/Unused_false_positive_module_augmentation.ts b/tests/baselines/reference/organizeImports/Unused_false_positive_module_augmentation.ts new file mode 100644 index 00000000000..4eaf184d493 --- /dev/null +++ b/tests/baselines/reference/organizeImports/Unused_false_positive_module_augmentation.ts @@ -0,0 +1,22 @@ +// ==ORIGINAL== + +import foo from 'foo'; +import { Caseless } from 'caseless'; + +declare module 'foo' {} +declare module 'caseless' { + interface Caseless { + test(name: KeyType): boolean; + } +} +// ==ORGANIZED== + +import 'caseless'; +import 'foo'; + +declare module 'foo' {} +declare module 'caseless' { + interface Caseless { + test(name: KeyType): boolean; + } +} \ No newline at end of file From 60d0bb9b199e76974cdc65647663267e9fcf75b1 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 22 May 2019 11:37:35 -0700 Subject: [PATCH 045/111] =?UTF-8?q?Don=E2=80=99t=20touch=20imports=20used?= =?UTF-8?q?=20for=20module=20augmentation=20in=20non-declaration=20files?= =?UTF-8?q?=20since=20it=20could=20change=20JS=20emit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/organizeImports.ts | 28 ++++++++++++------- .../unittests/services/organizeImports.ts | 15 ++++++++++ ...le_augmentation_in_non_declaration_file.ts | 22 +++++++++++++++ 3 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/organizeImports/Unused_preserve_imports_for_module_augmentation_in_non_declaration_file.ts diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index b6be92fbf9c..81c9108749f 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -125,13 +125,22 @@ namespace ts.OrganizeImports { if (name || namedBindings) { usedImports.push(updateImportDeclarationAndClause(importDecl, name, namedBindings)); } - // If a module is imported to be augmented, keep the import declaration, but without an import clause + // If a module is imported to be augmented, it’s used else if (hasModuleDeclarationMatchingSpecifier(sourceFile, moduleSpecifier)) { - usedImports.push(createImportDeclaration( - importDecl.decorators, - importDecl.modifiers, - /*importClause*/ undefined, - moduleSpecifier)); + // If we’re in a declaration file, it’s safe to remove the import clause from it + if (sourceFile.isDeclarationFile) { + usedImports.push(createImportDeclaration( + importDecl.decorators, + importDecl.modifiers, + /*importClause*/ undefined, + moduleSpecifier)); + } + // If we’re not in a declaration file, we can’t remove the import clause even though + // the imported symbols are unused, because removing them makes it look like the import + // declaration has side effects, which will cause it to be preserved in the JS emit. + else { + usedImports.push(importDecl); + } } } @@ -145,10 +154,9 @@ namespace ts.OrganizeImports { function hasModuleDeclarationMatchingSpecifier(sourceFile: SourceFile, moduleSpecifier: Expression) { const moduleSpecifierText = isStringLiteral(moduleSpecifier) && moduleSpecifier.text; - return isString(moduleSpecifierText) && some(sourceFile.statements, statement => - isModuleDeclaration(statement) - && isStringLiteral(statement.name) - && statement.name.text === moduleSpecifierText); + return isString(moduleSpecifierText) && some(sourceFile.moduleAugmentations, moduleName => + isStringLiteral(moduleName) + && moduleName.text === moduleSpecifierText); } function getExternalModuleName(specifier: Expression) { diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index 8003e1867cd..c119aadb34e 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -350,6 +350,21 @@ import { } from "lib"; import foo from 'foo'; import { Caseless } from 'caseless'; +declare module 'foo' {} +declare module 'caseless' { + interface Caseless { + test(name: KeyType): boolean; + } +}` + }); + + testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", + { + path: "/test.ts", + content: ` +import foo from 'foo'; +import { Caseless } from 'caseless'; + declare module 'foo' {} declare module 'caseless' { interface Caseless { diff --git a/tests/baselines/reference/organizeImports/Unused_preserve_imports_for_module_augmentation_in_non_declaration_file.ts b/tests/baselines/reference/organizeImports/Unused_preserve_imports_for_module_augmentation_in_non_declaration_file.ts new file mode 100644 index 00000000000..6d28f89903a --- /dev/null +++ b/tests/baselines/reference/organizeImports/Unused_preserve_imports_for_module_augmentation_in_non_declaration_file.ts @@ -0,0 +1,22 @@ +// ==ORIGINAL== + +import foo from 'foo'; +import { Caseless } from 'caseless'; + +declare module 'foo' {} +declare module 'caseless' { + interface Caseless { + test(name: KeyType): boolean; + } +} +// ==ORGANIZED== + +import { Caseless } from 'caseless'; +import foo from 'foo'; + +declare module 'foo' {} +declare module 'caseless' { + interface Caseless { + test(name: KeyType): boolean; + } +} \ No newline at end of file From b688e25cd37b4e0df5727846a02295e8b84ca702 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 22 May 2019 17:12:37 -0700 Subject: [PATCH 046/111] Implement inferrable index signatures for enum types --- src/compiler/checker.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9e5c864f6b9..e786eca9981 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7299,7 +7299,8 @@ namespace ts { stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); } } - const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined; + const numberIndexInfo = symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || + some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ? enumNumberIndexInfo : undefined; setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); // We resolve the members before computing the signatures because a signature may use // typeof with a qualified name expression that circularly references the type we are @@ -8161,6 +8162,9 @@ namespace ts { propTypes.push(getTypeOfSymbol(prop)); } } + if (kind === IndexKind.String) { + append(propTypes, getIndexTypeOfType(type, IndexKind.Number)); + } if (propTypes.length) { return getUnionType(propTypes, UnionReduction.Subtype); } @@ -14498,7 +14502,7 @@ namespace ts { * with no call or construct signatures. */ function isObjectTypeWithInferableIndex(type: Type) { - return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.ValueModule)) !== 0 && + return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum| SymbolFlags.ValueModule)) !== 0 && !typeHasCallOrConstructSignatures(type); } From a95d18e7b4492e0cba86e295b767a9516167646b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 22 May 2019 17:13:36 -0700 Subject: [PATCH 047/111] Accept new baselines --- .../baselines/reference/useObjectValuesAndEntries1.types | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.types b/tests/baselines/reference/useObjectValuesAndEntries1.types index 94deebdfd8c..bbfd4c87d2a 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.types +++ b/tests/baselines/reference/useObjectValuesAndEntries1.types @@ -121,16 +121,16 @@ enum E { A, B } >B : E.B var entries5 = Object.entries(E); // [string, any][] ->entries5 : [string, any][] ->Object.entries(E) : [string, any][] +>entries5 : [string, string | E][] +>Object.entries(E) : [string, string | E][] >Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor >entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >E : typeof E var values5 = Object.values(E); // any[] ->values5 : any[] ->Object.values(E) : any[] +>values5 : (string | E)[] +>Object.values(E) : (string | E)[] >Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor >values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } From 300cbef07105247ec1c826de9d785bfc72849103 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 22 May 2019 17:42:05 -0700 Subject: [PATCH 048/111] =?UTF-8?q?Don=E2=80=99t=20crash=20when=20creating?= =?UTF-8?q?=20a=20union=20signature=20from=20signatures=20that=20do=20and?= =?UTF-8?q?=20don=E2=80=99t=20have=20this=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/compiler/checker.ts | 6 ++-- .../reference/unionTypeCallSignatures5.js | 17 ++++++++++ .../unionTypeCallSignatures5.symbols | 31 +++++++++++++++++++ .../reference/unionTypeCallSignatures5.types | 24 ++++++++++++++ .../types/union/unionTypeCallSignatures5.ts | 12 +++++++ 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/unionTypeCallSignatures5.js create mode 100644 tests/baselines/reference/unionTypeCallSignatures5.symbols create mode 100644 tests/baselines/reference/unionTypeCallSignatures5.types create mode 100644 tests/cases/conformance/types/union/unionTypeCallSignatures5.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 05b1015b124..936c73ee9a0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7052,10 +7052,10 @@ namespace ts { // Union the result types when more than one signature matches if (unionSignatures.length > 1) { let thisParameter = signature.thisParameter; - if (forEach(unionSignatures, sig => sig.thisParameter)) { - // TODO: GH#18217 We tested that *some* has thisParameter and now act as if *all* do + const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter); + if (firstThisParameterOfUnionSignatures) { const thisType = getUnionType(map(unionSignatures, sig => sig.thisParameter ? getTypeOfSymbol(sig.thisParameter) : anyType), UnionReduction.Subtype); - thisParameter = createSymbolWithType(signature.thisParameter!, thisType); + thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType); } s = createUnionSignature(signature, unionSignatures); s.thisParameter = thisParameter; diff --git a/tests/baselines/reference/unionTypeCallSignatures5.js b/tests/baselines/reference/unionTypeCallSignatures5.js new file mode 100644 index 00000000000..2e756540d94 --- /dev/null +++ b/tests/baselines/reference/unionTypeCallSignatures5.js @@ -0,0 +1,17 @@ +//// [unionTypeCallSignatures5.ts] +// #31485 +interface A { + (this: void, b?: number): void; +} +interface B { + (this: number, b?: number): void; +} +interface C { + (i: number): void; +} +declare const fn: A | B | C; +fn(0); + + +//// [unionTypeCallSignatures5.js] +fn(0); diff --git a/tests/baselines/reference/unionTypeCallSignatures5.symbols b/tests/baselines/reference/unionTypeCallSignatures5.symbols new file mode 100644 index 00000000000..d729dcd7aba --- /dev/null +++ b/tests/baselines/reference/unionTypeCallSignatures5.symbols @@ -0,0 +1,31 @@ +=== tests/cases/conformance/types/union/unionTypeCallSignatures5.ts === +// #31485 +interface A { +>A : Symbol(A, Decl(unionTypeCallSignatures5.ts, 0, 0)) + + (this: void, b?: number): void; +>this : Symbol(this, Decl(unionTypeCallSignatures5.ts, 2, 3)) +>b : Symbol(b, Decl(unionTypeCallSignatures5.ts, 2, 14)) +} +interface B { +>B : Symbol(B, Decl(unionTypeCallSignatures5.ts, 3, 1)) + + (this: number, b?: number): void; +>this : Symbol(this, Decl(unionTypeCallSignatures5.ts, 5, 3)) +>b : Symbol(b, Decl(unionTypeCallSignatures5.ts, 5, 16)) +} +interface C { +>C : Symbol(C, Decl(unionTypeCallSignatures5.ts, 6, 1)) + + (i: number): void; +>i : Symbol(i, Decl(unionTypeCallSignatures5.ts, 8, 3)) +} +declare const fn: A | B | C; +>fn : Symbol(fn, Decl(unionTypeCallSignatures5.ts, 10, 13)) +>A : Symbol(A, Decl(unionTypeCallSignatures5.ts, 0, 0)) +>B : Symbol(B, Decl(unionTypeCallSignatures5.ts, 3, 1)) +>C : Symbol(C, Decl(unionTypeCallSignatures5.ts, 6, 1)) + +fn(0); +>fn : Symbol(fn, Decl(unionTypeCallSignatures5.ts, 10, 13)) + diff --git a/tests/baselines/reference/unionTypeCallSignatures5.types b/tests/baselines/reference/unionTypeCallSignatures5.types new file mode 100644 index 00000000000..53ce4ea6550 --- /dev/null +++ b/tests/baselines/reference/unionTypeCallSignatures5.types @@ -0,0 +1,24 @@ +=== tests/cases/conformance/types/union/unionTypeCallSignatures5.ts === +// #31485 +interface A { + (this: void, b?: number): void; +>this : void +>b : number +} +interface B { + (this: number, b?: number): void; +>this : number +>b : number +} +interface C { + (i: number): void; +>i : number +} +declare const fn: A | B | C; +>fn : A | B | C + +fn(0); +>fn(0) : void +>fn : A | B | C +>0 : 0 + diff --git a/tests/cases/conformance/types/union/unionTypeCallSignatures5.ts b/tests/cases/conformance/types/union/unionTypeCallSignatures5.ts new file mode 100644 index 00000000000..30a5ac031ad --- /dev/null +++ b/tests/cases/conformance/types/union/unionTypeCallSignatures5.ts @@ -0,0 +1,12 @@ +// #31485 +interface A { + (this: void, b?: number): void; +} +interface B { + (this: number, b?: number): void; +} +interface C { + (i: number): void; +} +declare const fn: A | B | C; +fn(0); From f97f57c1556bde40149ef30f034480917c27fce0 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 23 May 2019 12:15:50 -0700 Subject: [PATCH 049/111] Fix containsPrecedingToken for tokens whose preceding token is a missing node --- src/harness/fourslash.ts | 10 +++++----- src/services/signatureHelp.ts | 20 +++++++++++++++----- tests/cases/fourslash/fourslash.ts | 8 ++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8a0a2bffbd4..bdcb3e082d2 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1208,7 +1208,7 @@ Actual: ${stringify(fullActual)}`); } } - public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray) { + public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray) { if (markers.length) { for (const marker of markers) { this.goToMarker(marker); @@ -3768,15 +3768,15 @@ namespace FourSlashInterface { assert(ranges.length !== 0, "Array of ranges is expected to be non-empty"); } - public noSignatureHelp(...markers: string[]): void { + public noSignatureHelp(...markers: (string | FourSlash.Marker)[]): void { this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers); } - public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void { + public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers); } - public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void { + public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers); } @@ -5124,7 +5124,7 @@ namespace FourSlashInterface { } export interface VerifySignatureHelpOptions { - readonly marker?: ArrayOrSingle; + readonly marker?: ArrayOrSingle; /** @default 1 */ readonly overloadsCount?: number; /** @default undefined */ diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 25f958dfc6a..998e45bd461 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -133,11 +133,21 @@ namespace ts.SignatureHelp { } function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) { - const precedingToken = Debug.assertDefined( - findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true) - ); - - return rangeContainsRange(container, precedingToken); + const pos = startingToken.getFullStart(); + // There’s a possibility that `startingToken.parent` contains only `startingToken` and + // missing nodes, none of which are valid to be returned by `findPrecedingToken`. In that + // case, the preceding token we want is actually higher up the tree—almost definitely the + // next parent, but theoretically the situation with missing nodes might be happening on + // multiple nested levels. + let currentParent: Node | undefined = startingToken.parent; + while (currentParent) { + const precedingToken = findPrecedingToken(pos, sourceFile, currentParent, /*excludeJsdoc*/ true); + if (precedingToken) { + return rangeContainsRange(container, precedingToken); + } + currentParent = currentParent.parent; + } + return Debug.fail("Could not find preceding token"); } export interface ArgumentInfoForCompletions { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index fa9cdd61c2f..b3253bcd59f 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -232,9 +232,9 @@ declare namespace FourSlashInterface { rangesWithSameTextAreRenameLocations(): void; rangesAreRenameLocations(options?: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: Range[] }); findReferencesDefinitionDisplayPartsAtCaretAre(expected: ts.SymbolDisplayPart[]): void; - noSignatureHelp(...markers: string[]): void; - noSignatureHelpForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: string[]): void - signatureHelpPresentForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: string[]): void + noSignatureHelp(...markers: (string | Marker)[]): void; + noSignatureHelpForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: (string | Marker)[]): void + signatureHelpPresentForTriggerReason(triggerReason: SignatureHelpTriggerReason, ...markers: (string | Marker)[]): void signatureHelp(...options: VerifySignatureHelpOptions[], ): void; // Checks that there are no compile errors. noErrors(): void; @@ -538,7 +538,7 @@ declare namespace FourSlashInterface { } interface VerifySignatureHelpOptions { - marker?: ArrayOrSingle; + marker?: ArrayOrSingle; /** @default 1 */ overloadsCount?: number; docComment?: string; From 7359ff8158df18207ff66cb7040509051eab5242 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 23 May 2019 13:33:38 -0700 Subject: [PATCH 050/111] Add test --- tests/cases/fourslash/signatureHelpJSX.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/cases/fourslash/signatureHelpJSX.ts diff --git a/tests/cases/fourslash/signatureHelpJSX.ts b/tests/cases/fourslash/signatureHelpJSX.ts new file mode 100644 index 00000000000..181ff85541b --- /dev/null +++ b/tests/cases/fourslash/signatureHelpJSX.ts @@ -0,0 +1,10 @@ +/// + +//@Filename: test.tsx +//@jsx: react +////declare var React: any; +////const z =
{[].map(x => Date: Fri, 24 May 2019 01:27:50 +0300 Subject: [PATCH 051/111] Improve error messages when indexing into a type (#31379) * Improved error messages when indexing an object type with a literal string, a literal string union or a string. * Added more specific message when using the indexing operator with an incompatible index argument. * Fixed spelling and error message. --- src/compiler/checker.ts | 32 ++++++++-- src/compiler/diagnosticMessages.json | 8 +++ .../globalThisUnknownNoImplicitAny.errors.txt | 12 ++-- .../keyofAndIndexedAccess2.errors.txt | 6 +- .../reference/noImplicitAnyForIn.errors.txt | 12 ++-- .../noImplicitAnyIndexing.errors.txt | 16 +++-- ...mplicitAnyStringIndexerOnObject.errors.txt | 59 +++++++++++++++++-- .../noImplicitAnyStringIndexerOnObject.js | 38 ++++++++++++ ...noImplicitAnyStringIndexerOnObject.symbols | 52 ++++++++++++++++ .../noImplicitAnyStringIndexerOnObject.types | 59 +++++++++++++++++++ ...eIndexingWithForInNoImplicitAny.errors.txt | 6 +- .../objectSpreadIndexSignature.errors.txt | 6 +- ...ringIndexSignatureNoImplicitAny.errors.txt | 6 +- .../typeFromPrototypeAssignment3.errors.txt | 6 +- .../unionTypeWithIndexSignature.errors.txt | 12 ++-- .../noImplicitAnyStringIndexerOnObject.ts | 20 +++++++ 16 files changed, 312 insertions(+), 38 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c221da7bdb2..0d7c7b022dd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10067,7 +10067,7 @@ namespace ts { return false; } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { + function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = isTypeUsableAsPropertyName(indexType) ? getPropertyNameFromType(indexType) : @@ -10141,7 +10141,7 @@ namespace ts { if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } - else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors) { + else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) { if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { error(accessExpression, Diagnostics.Property_0_is_a_static_member_of_type_1, propName as string, typeToString(objectType)); } @@ -10161,7 +10161,29 @@ namespace ts { error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); } else { - error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType)); + let errorInfo: DiagnosticMessageChain | undefined; + if (indexType.flags & TypeFlags.EnumLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.UniqueESSymbol) { + const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.StringLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); + } + else if (indexType.flags & TypeFlags.NumberLiteral) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); + } + else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { + errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); + } + + errorInfo = chainDiagnosticMessages( + errorInfo, + Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType) + ); + diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo)); } } } @@ -10360,7 +10382,7 @@ namespace ts { const propTypes: Type[] = []; let wasMissingProp = false; for (const t of (indexType).types) { - const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, accessFlags); + const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags); if (propType) { propTypes.push(propType); } @@ -10378,7 +10400,7 @@ namespace ts { } return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes); } - return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol); + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol); } function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 101b584ca13..e0579429def 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4288,6 +4288,14 @@ "category": "Error", "code": 7052 }, + "Element implicitly has an 'any' type because expression of type '{0}' can't be used to index type '{1}'.": { + "category": "Error", + "code": 7053 + }, + "No index signature with a parameter of type '{0}' was found on type '{1}'.": { + "category": "Error", + "code": 7054 + }, "You cannot rename this element.": { "category": "Error", "code": 8000 diff --git a/tests/baselines/reference/globalThisUnknownNoImplicitAny.errors.txt b/tests/baselines/reference/globalThisUnknownNoImplicitAny.errors.txt index 0668d03f93b..907a6cd1ced 100644 --- a/tests/baselines/reference/globalThisUnknownNoImplicitAny.errors.txt +++ b/tests/baselines/reference/globalThisUnknownNoImplicitAny.errors.txt @@ -2,8 +2,10 @@ tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(4,5): error TS2 tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(5,6): error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(6,12): error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(8,5): error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. -tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(9,1): error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. -tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(10,1): error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. +tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(9,1): error TS7053: Element implicitly has an 'any' type because expression of type '"hi"' can't be used to index type 'typeof globalThis'. + Property 'hi' does not exist on type 'typeof globalThis'. +tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(10,1): error TS7053: Element implicitly has an 'any' type because expression of type '"hi"' can't be used to index type 'typeof globalThis'. + Property 'hi' does not exist on type 'typeof globalThis'. ==== tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts (6 errors) ==== @@ -25,8 +27,10 @@ tests/cases/conformance/es2019/globalThisUnknownNoImplicitAny.ts(10,1): error TS !!! error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. this['hi'] ~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"hi"' can't be used to index type 'typeof globalThis'. +!!! error TS7053: Property 'hi' does not exist on type 'typeof globalThis'. globalThis['hi'] ~~~~~~~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"hi"' can't be used to index type 'typeof globalThis'. +!!! error TS7053: Property 'hi' does not exist on type 'typeof globalThis'. \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt index 1cad297c52d..40ccd13dac4 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt @@ -15,7 +15,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(26,7): error TS233 tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(27,5): error TS2322: Type '1' is not assignable to type 'T[keyof T]'. tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(31,5): error TS2322: Type '{ [key: string]: number; }' is not assignable to type '{ [P in K]: number; }'. tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(38,5): error TS2322: Type '{ [x: string]: number; }' is not assignable to type '{ [P in K]: number; }'. -tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(50,3): error TS7017: Element implicitly has an 'any' type because type 'Item' has no index signature. +tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(50,3): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Item'. + No index signature with a parameter of type 'string' was found on type 'Item'. tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(51,3): error TS2322: Type '123' is not assignable to type 'string & number'. Type '123' is not assignable to type 'string'. tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(52,3): error TS2322: Type '123' is not assignable to type 'T[keyof T]'. @@ -112,7 +113,8 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(108,5): error TS23 function f10(obj: T, k1: string, k2: keyof Item, k3: keyof T, k4: K) { obj[k1] = 123; // Error ~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type 'Item' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Item'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type 'Item'. obj[k2] = 123; // Error ~~~~~~~ !!! error TS2322: Type '123' is not assignable to type 'string & number'. diff --git a/tests/baselines/reference/noImplicitAnyForIn.errors.txt b/tests/baselines/reference/noImplicitAnyForIn.errors.txt index b384735d82e..c66a7e07f96 100644 --- a/tests/baselines/reference/noImplicitAnyForIn.errors.txt +++ b/tests/baselines/reference/noImplicitAnyForIn.errors.txt @@ -1,5 +1,7 @@ -tests/cases/compiler/noImplicitAnyForIn.ts(7,18): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. -tests/cases/compiler/noImplicitAnyForIn.ts(14,18): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +tests/cases/compiler/noImplicitAnyForIn.ts(7,18): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. + No index signature with a parameter of type 'string' was found on type '{}'. +tests/cases/compiler/noImplicitAnyForIn.ts(14,18): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. + No index signature with a parameter of type 'string' was found on type '{}'. tests/cases/compiler/noImplicitAnyForIn.ts(28,5): error TS7005: Variable 'n' implicitly has an 'any[][]' type. tests/cases/compiler/noImplicitAnyForIn.ts(30,6): error TS2405: The left-hand side of a 'for...in' statement must be of type 'string' or 'any'. @@ -13,7 +15,8 @@ tests/cases/compiler/noImplicitAnyForIn.ts(30,6): error TS2405: The left-hand si //Should yield an implicit 'any' error var _j = x[i][j]; ~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type '{}'. } for (var k in x[0]) { @@ -22,7 +25,8 @@ tests/cases/compiler/noImplicitAnyForIn.ts(30,6): error TS2405: The left-hand si //Should yield an implicit 'any' error var k2 = k1[k]; ~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type '{}'. } } diff --git a/tests/baselines/reference/noImplicitAnyIndexing.errors.txt b/tests/baselines/reference/noImplicitAnyIndexing.errors.txt index 8d1d3ea6c46..f9631f29aa1 100644 --- a/tests/baselines/reference/noImplicitAnyIndexing.errors.txt +++ b/tests/baselines/reference/noImplicitAnyIndexing.errors.txt @@ -1,7 +1,9 @@ tests/cases/compiler/noImplicitAnyIndexing.ts(12,37): error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'. -tests/cases/compiler/noImplicitAnyIndexing.ts(19,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. -tests/cases/compiler/noImplicitAnyIndexing.ts(22,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. -tests/cases/compiler/noImplicitAnyIndexing.ts(30,10): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +tests/cases/compiler/noImplicitAnyIndexing.ts(19,9): error TS7053: Element implicitly has an 'any' type because expression of type '"hi"' can't be used to index type '{}'. + Property 'hi' does not exist on type '{}'. +tests/cases/compiler/noImplicitAnyIndexing.ts(22,9): error TS7053: Element implicitly has an 'any' type because expression of type '10' can't be used to index type '{}'. + Property '10' does not exist on type '{}'. +tests/cases/compiler/noImplicitAnyIndexing.ts(30,10): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'. ==== tests/cases/compiler/noImplicitAnyIndexing.ts (4 errors) ==== @@ -27,12 +29,14 @@ tests/cases/compiler/noImplicitAnyIndexing.ts(30,10): error TS7017: Element impl // Should report an implicit 'any'. var x = {}["hi"]; ~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"hi"' can't be used to index type '{}'. +!!! error TS7053: Property 'hi' does not exist on type '{}'. // Should report an implicit 'any'. var y = {}[10]; ~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '10' can't be used to index type '{}'. +!!! error TS7053: Property '10' does not exist on type '{}'. var hi: any = "hi"; @@ -42,7 +46,7 @@ tests/cases/compiler/noImplicitAnyIndexing.ts(30,10): error TS7017: Element impl // Should report an implicit 'any'. var z1 = emptyObj[hi]; ~~~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'. var z2 = (emptyObj)[hi]; interface MyMap { diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt index 6df52aea46a..0d2fc1b860e 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt @@ -1,16 +1,29 @@ -tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(1,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(1,9): error TS7053: Element implicitly has an 'any' type because expression of type '"hello"' can't be used to index type '{}'. + Property 'hello' does not exist on type '{}'. tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(7,1): error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ? tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(8,13): error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ? -tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(13,13): error TS7017: Element implicitly has an 'any' type because type '{ set: (key: string) => string; }' has no index signature. +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(13,13): error TS7053: Element implicitly has an 'any' type because expression of type '"hello"' can't be used to index type '{ set: (key: string) => string; }'. + Property 'hello' does not exist on type '{ set: (key: string) => string; }'. tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(19,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ? tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(20,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ? tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(21,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ? +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(26,1): error TS7053: Element implicitly has an 'any' type because expression of type '"a" | "b" | "c"' can't be used to index type '{ a: number; }'. + Property 'b' does not exist on type '{ a: number; }'. +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(30,1): error TS7053: Element implicitly has an 'any' type because expression of type '"c"' can't be used to index type '{ a: number; }'. + Property 'c' does not exist on type '{ a: number; }'. +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(33,1): error TS7053: Element implicitly has an 'any' type because expression of type 'unique symbol' can't be used to index type '{ a: number; }'. + Property '[sym]' does not exist on type '{ a: number; }'. +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(37,1): error TS7053: Element implicitly has an 'any' type because expression of type 'NumEnum' can't be used to index type '{ a: number; }'. + Property '[NumEnum.a]' does not exist on type '{ a: number; }'. +tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(42,1): error TS7053: Element implicitly has an 'any' type because expression of type 'StrEnum' can't be used to index type '{ a: number; }'. + Property '[StrEnum.b]' does not exist on type '{ a: number; }'. -==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (7 errors) ==== +==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (12 errors) ==== var a = {}["hello"]; ~~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"hello"' can't be used to index type '{}'. +!!! error TS7053: Property 'hello' does not exist on type '{}'. var b: string = { '': 'foo' }['']; var c = { @@ -28,7 +41,8 @@ tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(21,1): error TS7052: }; const bar = d['hello']; ~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{ set: (key: string) => string; }' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"hello"' can't be used to index type '{ set: (key: string) => string; }'. +!!! error TS7053: Property 'hello' does not exist on type '{ set: (key: string) => string; }'. var e = { set: (key: string) => 'foobar', @@ -44,4 +58,39 @@ tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(21,1): error TS7052: ~~~~~~~~~~ !!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ? + const o = { a: 0 }; + + declare const k: "a" | "b" | "c"; + o[k]; + ~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"a" | "b" | "c"' can't be used to index type '{ a: number; }'. +!!! error TS7053: Property 'b' does not exist on type '{ a: number; }'. + + + declare const k2: "c"; + o[k2]; + ~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"c"' can't be used to index type '{ a: number; }'. +!!! error TS7053: Property 'c' does not exist on type '{ a: number; }'. + + declare const sym : unique symbol; + o[sym]; + ~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'unique symbol' can't be used to index type '{ a: number; }'. +!!! error TS7053: Property '[sym]' does not exist on type '{ a: number; }'. + + enum NumEnum { a, b } + let numEnumKey: NumEnum; + o[numEnumKey]; + ~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'NumEnum' can't be used to index type '{ a: number; }'. +!!! error TS7053: Property '[NumEnum.a]' does not exist on type '{ a: number; }'. + + + enum StrEnum { a = "a", b = "b" } + let strEnumKey: StrEnum; + o[strEnumKey]; + ~~~~~~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'StrEnum' can't be used to index type '{ a: number; }'. +!!! error TS7053: Property '[StrEnum.b]' does not exist on type '{ a: number; }'. \ No newline at end of file diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js index a02740351cf..572d9f38caf 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js @@ -21,6 +21,26 @@ e['hello'] = 'modified'; e['hello'] += 1; e['hello'] ++; +const o = { a: 0 }; + +declare const k: "a" | "b" | "c"; +o[k]; + + +declare const k2: "c"; +o[k2]; + +declare const sym : unique symbol; +o[sym]; + +enum NumEnum { a, b } +let numEnumKey: NumEnum; +o[numEnumKey]; + + +enum StrEnum { a = "a", b = "b" } +let strEnumKey: StrEnum; +o[strEnumKey]; //// [noImplicitAnyStringIndexerOnObject.js] @@ -42,3 +62,21 @@ var e = { e['hello'] = 'modified'; e['hello'] += 1; e['hello']++; +var o = { a: 0 }; +o[k]; +o[k2]; +o[sym]; +var NumEnum; +(function (NumEnum) { + NumEnum[NumEnum["a"] = 0] = "a"; + NumEnum[NumEnum["b"] = 1] = "b"; +})(NumEnum || (NumEnum = {})); +var numEnumKey; +o[numEnumKey]; +var StrEnum; +(function (StrEnum) { + StrEnum["a"] = "a"; + StrEnum["b"] = "b"; +})(StrEnum || (StrEnum = {})); +var strEnumKey; +o[strEnumKey]; diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols index f86c0d14b6f..307144b8563 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols @@ -55,4 +55,56 @@ e['hello'] += 1; e['hello'] ++; >e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3)) +const o = { a: 0 }; +>o : Symbol(o, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 5)) +>a : Symbol(a, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 11)) + +declare const k: "a" | "b" | "c"; +>k : Symbol(k, Decl(noImplicitAnyStringIndexerOnObject.ts, 24, 13)) + +o[k]; +>o : Symbol(o, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 5)) +>k : Symbol(k, Decl(noImplicitAnyStringIndexerOnObject.ts, 24, 13)) + + +declare const k2: "c"; +>k2 : Symbol(k2, Decl(noImplicitAnyStringIndexerOnObject.ts, 28, 13)) + +o[k2]; +>o : Symbol(o, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 5)) +>k2 : Symbol(k2, Decl(noImplicitAnyStringIndexerOnObject.ts, 28, 13)) + +declare const sym : unique symbol; +>sym : Symbol(sym, Decl(noImplicitAnyStringIndexerOnObject.ts, 31, 13)) + +o[sym]; +>o : Symbol(o, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 5)) +>sym : Symbol(sym, Decl(noImplicitAnyStringIndexerOnObject.ts, 31, 13)) + +enum NumEnum { a, b } +>NumEnum : Symbol(NumEnum, Decl(noImplicitAnyStringIndexerOnObject.ts, 32, 7)) +>a : Symbol(NumEnum.a, Decl(noImplicitAnyStringIndexerOnObject.ts, 34, 14)) +>b : Symbol(NumEnum.b, Decl(noImplicitAnyStringIndexerOnObject.ts, 34, 17)) + +let numEnumKey: NumEnum; +>numEnumKey : Symbol(numEnumKey, Decl(noImplicitAnyStringIndexerOnObject.ts, 35, 3)) +>NumEnum : Symbol(NumEnum, Decl(noImplicitAnyStringIndexerOnObject.ts, 32, 7)) + +o[numEnumKey]; +>o : Symbol(o, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 5)) +>numEnumKey : Symbol(numEnumKey, Decl(noImplicitAnyStringIndexerOnObject.ts, 35, 3)) + + +enum StrEnum { a = "a", b = "b" } +>StrEnum : Symbol(StrEnum, Decl(noImplicitAnyStringIndexerOnObject.ts, 36, 14)) +>a : Symbol(StrEnum.a, Decl(noImplicitAnyStringIndexerOnObject.ts, 39, 14)) +>b : Symbol(StrEnum.b, Decl(noImplicitAnyStringIndexerOnObject.ts, 39, 23)) + +let strEnumKey: StrEnum; +>strEnumKey : Symbol(strEnumKey, Decl(noImplicitAnyStringIndexerOnObject.ts, 40, 3)) +>StrEnum : Symbol(StrEnum, Decl(noImplicitAnyStringIndexerOnObject.ts, 36, 14)) + +o[strEnumKey]; +>o : Symbol(o, Decl(noImplicitAnyStringIndexerOnObject.ts, 22, 5)) +>strEnumKey : Symbol(strEnumKey, Decl(noImplicitAnyStringIndexerOnObject.ts, 40, 3)) diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types index dcf01d8c5e8..fa74a192248 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types @@ -89,4 +89,63 @@ e['hello'] ++; >e : { set: (key: string) => string; get: (key: string) => string; } >'hello' : "hello" +const o = { a: 0 }; +>o : { a: number; } +>{ a: 0 } : { a: number; } +>a : number +>0 : 0 + +declare const k: "a" | "b" | "c"; +>k : "a" | "b" | "c" + +o[k]; +>o[k] : any +>o : { a: number; } +>k : "a" | "b" | "c" + + +declare const k2: "c"; +>k2 : "c" + +o[k2]; +>o[k2] : any +>o : { a: number; } +>k2 : "c" + +declare const sym : unique symbol; +>sym : unique symbol + +o[sym]; +>o[sym] : any +>o : { a: number; } +>sym : unique symbol + +enum NumEnum { a, b } +>NumEnum : NumEnum +>a : NumEnum.a +>b : NumEnum.b + +let numEnumKey: NumEnum; +>numEnumKey : NumEnum + +o[numEnumKey]; +>o[numEnumKey] : any +>o : { a: number; } +>numEnumKey : NumEnum + + +enum StrEnum { a = "a", b = "b" } +>StrEnum : StrEnum +>a : StrEnum.a +>"a" : "a" +>b : StrEnum.b +>"b" : "b" + +let strEnumKey: StrEnum; +>strEnumKey : StrEnum + +o[strEnumKey]; +>o[strEnumKey] : any +>o : { a: number; } +>strEnumKey : StrEnum diff --git a/tests/baselines/reference/nonPrimitiveIndexingWithForInNoImplicitAny.errors.txt b/tests/baselines/reference/nonPrimitiveIndexingWithForInNoImplicitAny.errors.txt index f7f2ee3db16..07f52bf3e6f 100644 --- a/tests/baselines/reference/nonPrimitiveIndexingWithForInNoImplicitAny.errors.txt +++ b/tests/baselines/reference/nonPrimitiveIndexingWithForInNoImplicitAny.errors.txt @@ -1,4 +1,5 @@ -tests/cases/conformance/types/nonPrimitive/nonPrimitiveIndexingWithForInNoImplicitAny.ts(4,17): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveIndexingWithForInNoImplicitAny.ts(4,17): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. + No index signature with a parameter of type 'string' was found on type '{}'. ==== tests/cases/conformance/types/nonPrimitive/nonPrimitiveIndexingWithForInNoImplicitAny.ts (1 errors) ==== @@ -7,6 +8,7 @@ tests/cases/conformance/types/nonPrimitive/nonPrimitiveIndexingWithForInNoImplic for (var key in a) { var value = a[key]; // error ~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type '{}'. } \ No newline at end of file diff --git a/tests/baselines/reference/objectSpreadIndexSignature.errors.txt b/tests/baselines/reference/objectSpreadIndexSignature.errors.txt index dede126d063..5c373b82ae4 100644 --- a/tests/baselines/reference/objectSpreadIndexSignature.errors.txt +++ b/tests/baselines/reference/objectSpreadIndexSignature.errors.txt @@ -1,4 +1,5 @@ -tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts(6,1): error TS7017: Element implicitly has an 'any' type because type '{ b: number; a: number; }' has no index signature. +tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts(6,1): error TS7053: Element implicitly has an 'any' type because expression of type '101' can't be used to index type '{ b: number; a: number; }'. + Property '101' does not exist on type '{ b: number; a: number; }'. ==== tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts (1 errors) ==== @@ -9,7 +10,8 @@ tests/cases/conformance/types/spread/objectSpreadIndexSignature.ts(6,1): error T // only indexed has indexer, so i[101]: any i[101]; ~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{ b: number; a: number; }' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '101' can't be used to index type '{ b: number; a: number; }'. +!!! error TS7053: Property '101' does not exist on type '{ b: number; a: number; }'. let ii = { ...indexed1, ...indexed2 }; // both have indexer, so i[1001]: number | boolean ii[1001]; diff --git a/tests/baselines/reference/propertyAccessStringIndexSignatureNoImplicitAny.errors.txt b/tests/baselines/reference/propertyAccessStringIndexSignatureNoImplicitAny.errors.txt index d5fda181096..0211bf321bf 100644 --- a/tests/baselines/reference/propertyAccessStringIndexSignatureNoImplicitAny.errors.txt +++ b/tests/baselines/reference/propertyAccessStringIndexSignatureNoImplicitAny.errors.txt @@ -1,5 +1,6 @@ tests/cases/conformance/expressions/propertyAccess/propertyAccessStringIndexSignatureNoImplicitAny.ts(10,7): error TS2339: Property 'nope' does not exist on type 'Empty'. -tests/cases/conformance/expressions/propertyAccess/propertyAccessStringIndexSignatureNoImplicitAny.ts(11,1): error TS7017: Element implicitly has an 'any' type because type 'Empty' has no index signature. +tests/cases/conformance/expressions/propertyAccess/propertyAccessStringIndexSignatureNoImplicitAny.ts(11,1): error TS7053: Element implicitly has an 'any' type because expression of type '"not allowed either"' can't be used to index type 'Empty'. + Property 'not allowed either' does not exist on type 'Empty'. ==== tests/cases/conformance/expressions/propertyAccess/propertyAccessStringIndexSignatureNoImplicitAny.ts (2 errors) ==== @@ -17,5 +18,6 @@ tests/cases/conformance/expressions/propertyAccess/propertyAccessStringIndexSign !!! error TS2339: Property 'nope' does not exist on type 'Empty'. empty["not allowed either"]; ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type 'Empty' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '"not allowed either"' can't be used to index type 'Empty'. +!!! error TS7053: Property 'not allowed either' does not exist on type 'Empty'. \ No newline at end of file diff --git a/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt b/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt index 579c3efac72..02ff4ae344c 100644 --- a/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt +++ b/tests/baselines/reference/typeFromPrototypeAssignment3.errors.txt @@ -1,5 +1,6 @@ tests/cases/conformance/salsa/bug26885.js(2,5): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. -tests/cases/conformance/salsa/bug26885.js(11,16): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +tests/cases/conformance/salsa/bug26885.js(11,16): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. + No index signature with a parameter of type 'string' was found on type '{}'. ==== tests/cases/conformance/salsa/bug26885.js (2 errors) ==== @@ -17,7 +18,8 @@ tests/cases/conformance/salsa/bug26885.js(11,16): error TS7017: Element implicit get(key) { return this._map[key + '']; ~~~~~~~~~~~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type '{}'. } } diff --git a/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt b/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt index fb4463bc2d9..14a3ffc58a6 100644 --- a/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt +++ b/tests/baselines/reference/unionTypeWithIndexSignature.errors.txt @@ -1,9 +1,11 @@ tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(11,3): error TS2339: Property 'bar' does not exist on type 'Missing'. Property 'bar' does not exist on type '{ [s: string]: string; }'. tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(14,4): error TS2540: Cannot assign to 'foo' because it is a read-only property. -tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(24,1): error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(24,1): error TS7053: Element implicitly has an 'any' type because expression of type '1' can't be used to index type 'Both'. + Property '1' does not exist on type 'Both'. tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(25,1): error TS2322: Type '"not ok"' is not assignable to type 'number'. -tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(26,1): error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. +tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(26,1): error TS7053: Element implicitly has an 'any' type because expression of type 'unique symbol' can't be used to index type 'Both'. + Property '[sym]' does not exist on type 'Both'. ==== tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts (5 errors) ==== @@ -37,11 +39,13 @@ tests/cases/conformance/types/union/unionTypeWithIndexSignature.ts(26,1): error both[0] = 1 both[1] = 0 // not ok ~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type '1' can't be used to index type 'Both'. +!!! error TS7053: Property '1' does not exist on type 'Both'. both[0] = 'not ok' ~~~~~~~ !!! error TS2322: Type '"not ok"' is not assignable to type 'number'. both[sym] = 'not ok' ~~~~~~~~~ -!!! error TS7017: Element implicitly has an 'any' type because type 'Both' has no index signature. +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'unique symbol' can't be used to index type 'Both'. +!!! error TS7053: Property '[sym]' does not exist on type 'Both'. \ No newline at end of file diff --git a/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts b/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts index 1cd967c294f..5e1b9430088 100644 --- a/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts +++ b/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts @@ -22,3 +22,23 @@ e['hello'] = 'modified'; e['hello'] += 1; e['hello'] ++; +const o = { a: 0 }; + +declare const k: "a" | "b" | "c"; +o[k]; + + +declare const k2: "c"; +o[k2]; + +declare const sym : unique symbol; +o[sym]; + +enum NumEnum { a, b } +let numEnumKey: NumEnum; +o[numEnumKey]; + + +enum StrEnum { a = "a", b = "b" } +let strEnumKey: StrEnum; +o[strEnumKey]; From f20a4fdfc48a5ea2c13896d9087d0492ed34811f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 May 2019 15:39:40 -0700 Subject: [PATCH 052/111] Limit size of union types resulting from intersection type normalization --- src/compiler/checker.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3111cffa126..8760f1709c4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9908,6 +9908,12 @@ namespace ts { else { // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. + // If the estimated size of the resulting union type exceeds 100000 constituents, report an error. + const size = reduceLeft(typeSet, (n, t) => n * (t.flags & TypeFlags.Union ? (t).types.length : 1), 1); + if (size >= 100000) { + error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); + return errorType; + } const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); const unionType = typeSet[unionIndex]; result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), From 53f37cfec3dec5810c6099a4545f0fc974698984 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 May 2019 17:09:17 -0700 Subject: [PATCH 053/111] Add test --- .../normalizedIntersectionTooComplex.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/cases/compiler/normalizedIntersectionTooComplex.ts diff --git a/tests/cases/compiler/normalizedIntersectionTooComplex.ts b/tests/cases/compiler/normalizedIntersectionTooComplex.ts new file mode 100644 index 00000000000..dff8bb12019 --- /dev/null +++ b/tests/cases/compiler/normalizedIntersectionTooComplex.ts @@ -0,0 +1,38 @@ +// @strict: true + +// Repro from #30050 + +interface Obj { + ref: T; +} +interface Func { + (x: T): void; +} +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +type CtorOf = (arg: UnionToIntersection) => T; + +interface Big { + "0": { common?: string; "0"?: number, ref?: Obj | Func; } + "1": { common?: string; "1"?: number, ref?: Obj | Func; } + "2": { common?: string; "2"?: number, ref?: Obj | Func; } + "3": { common?: string; "3"?: number, ref?: Obj | Func; } + "4": { common?: string; "4"?: number, ref?: Obj | Func; } + "5": { common?: string; "5"?: number, ref?: Obj | Func; } + "6": { common?: string; "6"?: number, ref?: Obj | Func; } + "7": { common?: string; "7"?: number, ref?: Obj | Func; } + "8": { common?: string; "8"?: number, ref?: Obj | Func; } + "9": { common?: string; "9"?: number, ref?: Obj | Func; } + "10": { common?: string; "10"?: number, ref?: Obj | Func; } + "11": { common?: string; "11"?: number, ref?: Obj | Func; } + "12": { common?: string; "12"?: number, ref?: Obj | Func; } + "13": { common?: string; "13"?: number, ref?: Obj | Func; } + "14": { common?: string; "14"?: number, ref?: Obj | Func; } + "15": { common?: string; "15"?: number, ref?: Obj | Func; } + "16": { common?: string; "16"?: number, ref?: Obj | Func; } + "17": { common?: string; "17"?: number, ref?: Obj | Func; } +} +declare function getCtor(comp: T): CtorOf + +declare var all: keyof Big; +const ctor = getCtor(all); +const comp = ctor({ common: "ok", ref: x => console.log(x) }); From 01d15145b402c65505baed1a078a8aa1c65dd4a1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 23 May 2019 17:09:25 -0700 Subject: [PATCH 054/111] Accept new baselines --- ...ormalizedIntersectionTooComplex.errors.txt | 46 ++++ .../normalizedIntersectionTooComplex.js | 44 +++ .../normalizedIntersectionTooComplex.symbols | 250 ++++++++++++++++++ .../normalizedIntersectionTooComplex.types | 158 +++++++++++ 4 files changed, 498 insertions(+) create mode 100644 tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt create mode 100644 tests/baselines/reference/normalizedIntersectionTooComplex.js create mode 100644 tests/baselines/reference/normalizedIntersectionTooComplex.symbols create mode 100644 tests/baselines/reference/normalizedIntersectionTooComplex.types diff --git a/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt b/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt new file mode 100644 index 00000000000..4e214eff507 --- /dev/null +++ b/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt @@ -0,0 +1,46 @@ +tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,14): error TS2590: Expression produces a union type that is too complex to represent. +tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS7006: Parameter 'x' implicitly has an 'any' type. + + +==== tests/cases/compiler/normalizedIntersectionTooComplex.ts (2 errors) ==== + // Repro from #30050 + + interface Obj { + ref: T; + } + interface Func { + (x: T): void; + } + type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; + type CtorOf = (arg: UnionToIntersection) => T; + + interface Big { + "0": { common?: string; "0"?: number, ref?: Obj | Func; } + "1": { common?: string; "1"?: number, ref?: Obj | Func; } + "2": { common?: string; "2"?: number, ref?: Obj | Func; } + "3": { common?: string; "3"?: number, ref?: Obj | Func; } + "4": { common?: string; "4"?: number, ref?: Obj | Func; } + "5": { common?: string; "5"?: number, ref?: Obj | Func; } + "6": { common?: string; "6"?: number, ref?: Obj | Func; } + "7": { common?: string; "7"?: number, ref?: Obj | Func; } + "8": { common?: string; "8"?: number, ref?: Obj | Func; } + "9": { common?: string; "9"?: number, ref?: Obj | Func; } + "10": { common?: string; "10"?: number, ref?: Obj | Func; } + "11": { common?: string; "11"?: number, ref?: Obj | Func; } + "12": { common?: string; "12"?: number, ref?: Obj | Func; } + "13": { common?: string; "13"?: number, ref?: Obj | Func; } + "14": { common?: string; "14"?: number, ref?: Obj | Func; } + "15": { common?: string; "15"?: number, ref?: Obj | Func; } + "16": { common?: string; "16"?: number, ref?: Obj | Func; } + "17": { common?: string; "17"?: number, ref?: Obj | Func; } + } + declare function getCtor(comp: T): CtorOf + + declare var all: keyof Big; + const ctor = getCtor(all); + const comp = ctor({ common: "ok", ref: x => console.log(x) }); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. + ~ +!!! error TS7006: Parameter 'x' implicitly has an 'any' type. + \ No newline at end of file diff --git a/tests/baselines/reference/normalizedIntersectionTooComplex.js b/tests/baselines/reference/normalizedIntersectionTooComplex.js new file mode 100644 index 00000000000..d55189f7a1c --- /dev/null +++ b/tests/baselines/reference/normalizedIntersectionTooComplex.js @@ -0,0 +1,44 @@ +//// [normalizedIntersectionTooComplex.ts] +// Repro from #30050 + +interface Obj { + ref: T; +} +interface Func { + (x: T): void; +} +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +type CtorOf = (arg: UnionToIntersection) => T; + +interface Big { + "0": { common?: string; "0"?: number, ref?: Obj | Func; } + "1": { common?: string; "1"?: number, ref?: Obj | Func; } + "2": { common?: string; "2"?: number, ref?: Obj | Func; } + "3": { common?: string; "3"?: number, ref?: Obj | Func; } + "4": { common?: string; "4"?: number, ref?: Obj | Func; } + "5": { common?: string; "5"?: number, ref?: Obj | Func; } + "6": { common?: string; "6"?: number, ref?: Obj | Func; } + "7": { common?: string; "7"?: number, ref?: Obj | Func; } + "8": { common?: string; "8"?: number, ref?: Obj | Func; } + "9": { common?: string; "9"?: number, ref?: Obj | Func; } + "10": { common?: string; "10"?: number, ref?: Obj | Func; } + "11": { common?: string; "11"?: number, ref?: Obj | Func; } + "12": { common?: string; "12"?: number, ref?: Obj | Func; } + "13": { common?: string; "13"?: number, ref?: Obj | Func; } + "14": { common?: string; "14"?: number, ref?: Obj | Func; } + "15": { common?: string; "15"?: number, ref?: Obj | Func; } + "16": { common?: string; "16"?: number, ref?: Obj | Func; } + "17": { common?: string; "17"?: number, ref?: Obj | Func; } +} +declare function getCtor(comp: T): CtorOf + +declare var all: keyof Big; +const ctor = getCtor(all); +const comp = ctor({ common: "ok", ref: x => console.log(x) }); + + +//// [normalizedIntersectionTooComplex.js] +"use strict"; +// Repro from #30050 +var ctor = getCtor(all); +var comp = ctor({ common: "ok", ref: function (x) { return console.log(x); } }); diff --git a/tests/baselines/reference/normalizedIntersectionTooComplex.symbols b/tests/baselines/reference/normalizedIntersectionTooComplex.symbols new file mode 100644 index 00000000000..0854ef907df --- /dev/null +++ b/tests/baselines/reference/normalizedIntersectionTooComplex.symbols @@ -0,0 +1,250 @@ +=== tests/cases/compiler/normalizedIntersectionTooComplex.ts === +// Repro from #30050 + +interface Obj { +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 2, 14)) + + ref: T; +>ref : Symbol(Obj.ref, Decl(normalizedIntersectionTooComplex.ts, 2, 18)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 2, 14)) +} +interface Func { +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 5, 15)) + + (x: T): void; +>x : Symbol(x, Decl(normalizedIntersectionTooComplex.ts, 6, 2)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 5, 15)) +} +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +>UnionToIntersection : Symbol(UnionToIntersection, Decl(normalizedIntersectionTooComplex.ts, 7, 1)) +>U : Symbol(U, Decl(normalizedIntersectionTooComplex.ts, 8, 25)) +>U : Symbol(U, Decl(normalizedIntersectionTooComplex.ts, 8, 25)) +>k : Symbol(k, Decl(normalizedIntersectionTooComplex.ts, 8, 48)) +>U : Symbol(U, Decl(normalizedIntersectionTooComplex.ts, 8, 25)) +>k : Symbol(k, Decl(normalizedIntersectionTooComplex.ts, 8, 81)) +>I : Symbol(I, Decl(normalizedIntersectionTooComplex.ts, 8, 89)) +>I : Symbol(I, Decl(normalizedIntersectionTooComplex.ts, 8, 89)) + +type CtorOf = (arg: UnionToIntersection) => T; +>CtorOf : Symbol(CtorOf, Decl(normalizedIntersectionTooComplex.ts, 8, 114)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 9, 12)) +>arg : Symbol(arg, Decl(normalizedIntersectionTooComplex.ts, 9, 18)) +>UnionToIntersection : Symbol(UnionToIntersection, Decl(normalizedIntersectionTooComplex.ts, 7, 1)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 9, 12)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 9, 12)) + +interface Big { +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "0": { common?: string; "0"?: number, ref?: Obj | Func; } +>"0" : Symbol(Big["0"], Decl(normalizedIntersectionTooComplex.ts, 11, 15)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 12, 10)) +>"0" : Symbol("0", Decl(normalizedIntersectionTooComplex.ts, 12, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 12, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "1": { common?: string; "1"?: number, ref?: Obj | Func; } +>"1" : Symbol(Big["1"], Decl(normalizedIntersectionTooComplex.ts, 12, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 13, 10)) +>"1" : Symbol("1", Decl(normalizedIntersectionTooComplex.ts, 13, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 13, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "2": { common?: string; "2"?: number, ref?: Obj | Func; } +>"2" : Symbol(Big["2"], Decl(normalizedIntersectionTooComplex.ts, 13, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 14, 10)) +>"2" : Symbol("2", Decl(normalizedIntersectionTooComplex.ts, 14, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 14, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "3": { common?: string; "3"?: number, ref?: Obj | Func; } +>"3" : Symbol(Big["3"], Decl(normalizedIntersectionTooComplex.ts, 14, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 15, 10)) +>"3" : Symbol("3", Decl(normalizedIntersectionTooComplex.ts, 15, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 15, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "4": { common?: string; "4"?: number, ref?: Obj | Func; } +>"4" : Symbol(Big["4"], Decl(normalizedIntersectionTooComplex.ts, 15, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 16, 10)) +>"4" : Symbol("4", Decl(normalizedIntersectionTooComplex.ts, 16, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 16, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "5": { common?: string; "5"?: number, ref?: Obj | Func; } +>"5" : Symbol(Big["5"], Decl(normalizedIntersectionTooComplex.ts, 16, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 17, 10)) +>"5" : Symbol("5", Decl(normalizedIntersectionTooComplex.ts, 17, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 17, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "6": { common?: string; "6"?: number, ref?: Obj | Func; } +>"6" : Symbol(Big["6"], Decl(normalizedIntersectionTooComplex.ts, 17, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 18, 10)) +>"6" : Symbol("6", Decl(normalizedIntersectionTooComplex.ts, 18, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 18, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "7": { common?: string; "7"?: number, ref?: Obj | Func; } +>"7" : Symbol(Big["7"], Decl(normalizedIntersectionTooComplex.ts, 18, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 19, 10)) +>"7" : Symbol("7", Decl(normalizedIntersectionTooComplex.ts, 19, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 19, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "8": { common?: string; "8"?: number, ref?: Obj | Func; } +>"8" : Symbol(Big["8"], Decl(normalizedIntersectionTooComplex.ts, 19, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 20, 10)) +>"8" : Symbol("8", Decl(normalizedIntersectionTooComplex.ts, 20, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 20, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "9": { common?: string; "9"?: number, ref?: Obj | Func; } +>"9" : Symbol(Big["9"], Decl(normalizedIntersectionTooComplex.ts, 20, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 21, 10)) +>"9" : Symbol("9", Decl(normalizedIntersectionTooComplex.ts, 21, 27)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 21, 41)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "10": { common?: string; "10"?: number, ref?: Obj | Func; } +>"10" : Symbol(Big["10"], Decl(normalizedIntersectionTooComplex.ts, 21, 81)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 22, 11)) +>"10" : Symbol("10", Decl(normalizedIntersectionTooComplex.ts, 22, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 22, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "11": { common?: string; "11"?: number, ref?: Obj | Func; } +>"11" : Symbol(Big["11"], Decl(normalizedIntersectionTooComplex.ts, 22, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 23, 11)) +>"11" : Symbol("11", Decl(normalizedIntersectionTooComplex.ts, 23, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 23, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "12": { common?: string; "12"?: number, ref?: Obj | Func; } +>"12" : Symbol(Big["12"], Decl(normalizedIntersectionTooComplex.ts, 23, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 24, 11)) +>"12" : Symbol("12", Decl(normalizedIntersectionTooComplex.ts, 24, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 24, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "13": { common?: string; "13"?: number, ref?: Obj | Func; } +>"13" : Symbol(Big["13"], Decl(normalizedIntersectionTooComplex.ts, 24, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 25, 11)) +>"13" : Symbol("13", Decl(normalizedIntersectionTooComplex.ts, 25, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 25, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "14": { common?: string; "14"?: number, ref?: Obj | Func; } +>"14" : Symbol(Big["14"], Decl(normalizedIntersectionTooComplex.ts, 25, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 26, 11)) +>"14" : Symbol("14", Decl(normalizedIntersectionTooComplex.ts, 26, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 26, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "15": { common?: string; "15"?: number, ref?: Obj | Func; } +>"15" : Symbol(Big["15"], Decl(normalizedIntersectionTooComplex.ts, 26, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 27, 11)) +>"15" : Symbol("15", Decl(normalizedIntersectionTooComplex.ts, 27, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 27, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "16": { common?: string; "16"?: number, ref?: Obj | Func; } +>"16" : Symbol(Big["16"], Decl(normalizedIntersectionTooComplex.ts, 27, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 28, 11)) +>"16" : Symbol("16", Decl(normalizedIntersectionTooComplex.ts, 28, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 28, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + + "17": { common?: string; "17"?: number, ref?: Obj | Func; } +>"17" : Symbol(Big["17"], Decl(normalizedIntersectionTooComplex.ts, 28, 85)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 29, 11)) +>"17" : Symbol("17", Decl(normalizedIntersectionTooComplex.ts, 29, 28)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 29, 43)) +>Obj : Symbol(Obj, Decl(normalizedIntersectionTooComplex.ts, 0, 0)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>Func : Symbol(Func, Decl(normalizedIntersectionTooComplex.ts, 4, 1)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +} +declare function getCtor(comp: T): CtorOf +>getCtor : Symbol(getCtor, Decl(normalizedIntersectionTooComplex.ts, 30, 1)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 31, 25)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>comp : Symbol(comp, Decl(normalizedIntersectionTooComplex.ts, 31, 46)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 31, 25)) +>CtorOf : Symbol(CtorOf, Decl(normalizedIntersectionTooComplex.ts, 8, 114)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) +>T : Symbol(T, Decl(normalizedIntersectionTooComplex.ts, 31, 25)) + +declare var all: keyof Big; +>all : Symbol(all, Decl(normalizedIntersectionTooComplex.ts, 33, 11)) +>Big : Symbol(Big, Decl(normalizedIntersectionTooComplex.ts, 9, 52)) + +const ctor = getCtor(all); +>ctor : Symbol(ctor, Decl(normalizedIntersectionTooComplex.ts, 34, 5)) +>getCtor : Symbol(getCtor, Decl(normalizedIntersectionTooComplex.ts, 30, 1)) +>all : Symbol(all, Decl(normalizedIntersectionTooComplex.ts, 33, 11)) + +const comp = ctor({ common: "ok", ref: x => console.log(x) }); +>comp : Symbol(comp, Decl(normalizedIntersectionTooComplex.ts, 35, 5)) +>ctor : Symbol(ctor, Decl(normalizedIntersectionTooComplex.ts, 34, 5)) +>common : Symbol(common, Decl(normalizedIntersectionTooComplex.ts, 35, 19)) +>ref : Symbol(ref, Decl(normalizedIntersectionTooComplex.ts, 35, 33)) +>x : Symbol(x, Decl(normalizedIntersectionTooComplex.ts, 35, 38)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>x : Symbol(x, Decl(normalizedIntersectionTooComplex.ts, 35, 38)) + diff --git a/tests/baselines/reference/normalizedIntersectionTooComplex.types b/tests/baselines/reference/normalizedIntersectionTooComplex.types new file mode 100644 index 00000000000..6750b0b0c73 --- /dev/null +++ b/tests/baselines/reference/normalizedIntersectionTooComplex.types @@ -0,0 +1,158 @@ +=== tests/cases/compiler/normalizedIntersectionTooComplex.ts === +// Repro from #30050 + +interface Obj { + ref: T; +>ref : T +} +interface Func { + (x: T): void; +>x : T +} +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +>UnionToIntersection : UnionToIntersection +>k : U +>k : I + +type CtorOf = (arg: UnionToIntersection) => T; +>CtorOf : CtorOf +>arg : UnionToIntersection + +interface Big { + "0": { common?: string; "0"?: number, ref?: Obj | Func; } +>"0" : { common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"0" : number | undefined +>ref : Obj<{ common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "1": { common?: string; "1"?: number, ref?: Obj | Func; } +>"1" : { common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"1" : number | undefined +>ref : Obj<{ common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "2": { common?: string; "2"?: number, ref?: Obj | Func; } +>"2" : { common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"2" : number | undefined +>ref : Obj<{ common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "3": { common?: string; "3"?: number, ref?: Obj | Func; } +>"3" : { common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"3" : number | undefined +>ref : Obj<{ common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "4": { common?: string; "4"?: number, ref?: Obj | Func; } +>"4" : { common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"4" : number | undefined +>ref : Obj<{ common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "5": { common?: string; "5"?: number, ref?: Obj | Func; } +>"5" : { common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"5" : number | undefined +>ref : Obj<{ common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "6": { common?: string; "6"?: number, ref?: Obj | Func; } +>"6" : { common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"6" : number | undefined +>ref : Obj<{ common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "7": { common?: string; "7"?: number, ref?: Obj | Func; } +>"7" : { common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"7" : number | undefined +>ref : Obj<{ common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "8": { common?: string; "8"?: number, ref?: Obj | Func; } +>"8" : { common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"8" : number | undefined +>ref : Obj<{ common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "9": { common?: string; "9"?: number, ref?: Obj | Func; } +>"9" : { common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"9" : number | undefined +>ref : Obj<{ common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "10": { common?: string; "10"?: number, ref?: Obj | Func; } +>"10" : { common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"10" : number | undefined +>ref : Obj<{ common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "11": { common?: string; "11"?: number, ref?: Obj | Func; } +>"11" : { common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"11" : number | undefined +>ref : Obj<{ common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "12": { common?: string; "12"?: number, ref?: Obj | Func; } +>"12" : { common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"12" : number | undefined +>ref : Obj<{ common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "13": { common?: string; "13"?: number, ref?: Obj | Func; } +>"13" : { common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"13" : number | undefined +>ref : Obj<{ common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "14": { common?: string; "14"?: number, ref?: Obj | Func; } +>"14" : { common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"14" : number | undefined +>ref : Obj<{ common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "15": { common?: string; "15"?: number, ref?: Obj | Func; } +>"15" : { common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"15" : number | undefined +>ref : Obj<{ common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "16": { common?: string; "16"?: number, ref?: Obj | Func; } +>"16" : { common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"16" : number | undefined +>ref : Obj<{ common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined + + "17": { common?: string; "17"?: number, ref?: Obj | Func; } +>"17" : { common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; } +>common : string | undefined +>"17" : number | undefined +>ref : Obj<{ common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; }> | Func<{ common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; }> | undefined +} +declare function getCtor(comp: T): CtorOf +>getCtor : (comp: T) => CtorOf +>comp : T + +declare var all: keyof Big; +>all : "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" + +const ctor = getCtor(all); +>ctor : CtorOf<{ common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; }> +>getCtor(all) : CtorOf<{ common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; }> +>getCtor : (comp: T) => CtorOf +>all : "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" + +const comp = ctor({ common: "ok", ref: x => console.log(x) }); +>comp : { common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; } +>ctor({ common: "ok", ref: x => console.log(x) }) : { common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; } +>ctor : CtorOf<{ common?: string | undefined; "0"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "1"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "2"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "3"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "4"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "5"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "6"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "7"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "8"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "9"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "10"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "11"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "12"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "13"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "14"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "15"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "16"?: number | undefined; ref?: Obj | Func | undefined; } | { common?: string | undefined; "17"?: number | undefined; ref?: Obj | Func | undefined; }> +>{ common: "ok", ref: x => console.log(x) } : { common: string; ref: (x: any) => void; } +>common : string +>"ok" : "ok" +>ref : (x: any) => void +>x => console.log(x) : (x: any) => void +>x : any +>console.log(x) : void +>console.log : (message?: any, ...optionalParams: any[]) => void +>console : Console +>log : (message?: any, ...optionalParams: any[]) => void +>x : any + From bb4080c175d0f554070b963bee05a0696bc958a2 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 23 May 2019 17:17:24 -0700 Subject: [PATCH 055/111] Collect _all_ symlinks a file may have witnessed when attempting to generate specifiers (#31571) --- src/compiler/moduleSpecifiers.ts | 6 +-- ...arationEmitForGlobalishSpecifierSymlink.js | 45 +++++++++++++++++++ ...onEmitForGlobalishSpecifierSymlink.symbols | 43 ++++++++++++++++++ ...tionEmitForGlobalishSpecifierSymlink.types | 41 +++++++++++++++++ ...rationEmitForGlobalishSpecifierSymlink2.js | 33 ++++++++++++++ ...nEmitForGlobalishSpecifierSymlink2.symbols | 30 +++++++++++++ ...ionEmitForGlobalishSpecifierSymlink2.types | 29 ++++++++++++ ...arationEmitForGlobalishSpecifierSymlink.ts | 35 +++++++++++++++ ...rationEmitForGlobalishSpecifierSymlink2.ts | 25 +++++++++++ 9 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.js create mode 100644 tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.symbols create mode 100644 tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.types create mode 100644 tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.js create mode 100644 tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.symbols create mode 100644 tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.types create mode 100644 tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink.ts create mode 100644 tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink2.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 59bb74a22c1..bc448698c69 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -175,9 +175,9 @@ namespace ts.moduleSpecifiers { function discoverProbableSymlinks(files: ReadonlyArray, getCanonicalFileName: GetCanonicalFileName, cwd: string): ReadonlyMap { const result = createMap(); - const symlinks = mapDefined(files, sf => - sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res => - res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined)); + const symlinks = flatten(mapDefined(files, sf => + sf.resolvedModules && compact(arrayFrom(mapIterator(sf.resolvedModules.values(), res => + res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] as const : undefined))))); for (const [resolvedPath, originalPath] of symlinks) { const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName); result.set(commonOriginal, commonResolved); diff --git a/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.js b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.js new file mode 100644 index 00000000000..00efdd49280 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.js @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink.ts] //// + +//// [impl.d.ts] +export function getA(): A; +export enum A { + Val +} +//// [index.d.ts] +export * from "./src/impl"; +//// [package.json] +{ + "name": "typescript-fsa", + "version": "1.0.0" +} +//// [impl.d.ts] +export function getA(): A; +export enum A { + Val +} +//// [index.d.ts] +export * from "./src/impl"; +//// [package.json] +{ + "name": "typescript-fsa", + "version": "1.0.0" +} +//// [index.ts] +import * as _whatever from "p2"; +import { getA } from "typescript-fsa"; + +export const a = getA(); +//// [index.d.ts] +export const a: import("typescript-fsa").A; + + + +//// [index.js] +"use strict"; +exports.__esModule = true; +var typescript_fsa_1 = require("typescript-fsa"); +exports.a = typescript_fsa_1.getA(); + + +//// [index.d.ts] +export declare const a: import("typescript-fsa").A; diff --git a/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.symbols b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.symbols new file mode 100644 index 00000000000..fa0a8593339 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.symbols @@ -0,0 +1,43 @@ +=== /p1/node_modules/typescript-fsa/src/impl.d.ts === +export function getA(): A; +>getA : Symbol(getA, Decl(impl.d.ts, 0, 0)) +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + +export enum A { +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + + Val +>Val : Symbol(A.Val, Decl(impl.d.ts, 1, 15)) +} +=== /p1/node_modules/typescript-fsa/index.d.ts === +export * from "./src/impl"; +No type information for this code.=== /p2/node_modules/typescript-fsa/src/impl.d.ts === +export function getA(): A; +>getA : Symbol(getA, Decl(impl.d.ts, 0, 0)) +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + +export enum A { +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + + Val +>Val : Symbol(A.Val, Decl(impl.d.ts, 1, 15)) +} +=== /p2/node_modules/typescript-fsa/index.d.ts === +export * from "./src/impl"; +No type information for this code.=== /p1/index.ts === +import * as _whatever from "p2"; +>_whatever : Symbol(_whatever, Decl(index.ts, 0, 6)) + +import { getA } from "typescript-fsa"; +>getA : Symbol(getA, Decl(index.ts, 1, 8)) + +export const a = getA(); +>a : Symbol(a, Decl(index.ts, 3, 12)) +>getA : Symbol(getA, Decl(index.ts, 1, 8)) + +=== /p2/index.d.ts === +export const a: import("typescript-fsa").A; +>a : Symbol(a, Decl(index.d.ts, 0, 12)) +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + + diff --git a/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.types b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.types new file mode 100644 index 00000000000..3f62193fd83 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink.types @@ -0,0 +1,41 @@ +=== /p1/node_modules/typescript-fsa/src/impl.d.ts === +export function getA(): A; +>getA : () => A + +export enum A { +>A : A + + Val +>Val : A +} +=== /p1/node_modules/typescript-fsa/index.d.ts === +export * from "./src/impl"; +No type information for this code.=== /p2/node_modules/typescript-fsa/src/impl.d.ts === +export function getA(): A; +>getA : () => A + +export enum A { +>A : A + + Val +>Val : A +} +=== /p2/node_modules/typescript-fsa/index.d.ts === +export * from "./src/impl"; +No type information for this code.=== /p1/index.ts === +import * as _whatever from "p2"; +>_whatever : typeof _whatever + +import { getA } from "typescript-fsa"; +>getA : () => import("/p1/node_modules/typescript-fsa/index").A + +export const a = getA(); +>a : import("/p1/node_modules/typescript-fsa/index").A +>getA() : import("/p1/node_modules/typescript-fsa/index").A +>getA : () => import("/p1/node_modules/typescript-fsa/index").A + +=== /p2/index.d.ts === +export const a: import("typescript-fsa").A; +>a : import("/p2/node_modules/typescript-fsa/index").A + + diff --git a/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.js b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.js new file mode 100644 index 00000000000..22889aba461 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.js @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink2.ts] //// + +//// [impl.d.ts] +export function getA(): A; +export enum A { + Val +} +//// [index.d.ts] +export * from "./src/impl"; +//// [package.json] +{ + "name": "typescript-fsa", + "version": "1.0.0" +} +//// [index.ts] +import * as _whatever from "p2"; +import { getA } from "typescript-fsa"; + +export const a = getA(); +//// [index.d.ts] +export const a: import("typescript-fsa").A; + + + +//// [index.js] +"use strict"; +exports.__esModule = true; +var typescript_fsa_1 = require("typescript-fsa"); +exports.a = typescript_fsa_1.getA(); + + +//// [index.d.ts] +export declare const a: import("typescript-fsa").A; diff --git a/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.symbols b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.symbols new file mode 100644 index 00000000000..e183f07d558 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.symbols @@ -0,0 +1,30 @@ +=== /cache/typescript-fsa/src/impl.d.ts === +export function getA(): A; +>getA : Symbol(getA, Decl(impl.d.ts, 0, 0)) +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + +export enum A { +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + + Val +>Val : Symbol(A.Val, Decl(impl.d.ts, 1, 15)) +} +=== /cache/typescript-fsa/index.d.ts === +export * from "./src/impl"; +No type information for this code.=== /p1/index.ts === +import * as _whatever from "p2"; +>_whatever : Symbol(_whatever, Decl(index.ts, 0, 6)) + +import { getA } from "typescript-fsa"; +>getA : Symbol(getA, Decl(index.ts, 1, 8)) + +export const a = getA(); +>a : Symbol(a, Decl(index.ts, 3, 12)) +>getA : Symbol(getA, Decl(index.ts, 1, 8)) + +=== /p2/index.d.ts === +export const a: import("typescript-fsa").A; +>a : Symbol(a, Decl(index.d.ts, 0, 12)) +>A : Symbol(A, Decl(impl.d.ts, 0, 26)) + + diff --git a/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.types b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.types new file mode 100644 index 00000000000..7c214ced6b6 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForGlobalishSpecifierSymlink2.types @@ -0,0 +1,29 @@ +=== /cache/typescript-fsa/src/impl.d.ts === +export function getA(): A; +>getA : () => A + +export enum A { +>A : A + + Val +>Val : A +} +=== /cache/typescript-fsa/index.d.ts === +export * from "./src/impl"; +No type information for this code.=== /p1/index.ts === +import * as _whatever from "p2"; +>_whatever : typeof _whatever + +import { getA } from "typescript-fsa"; +>getA : () => import("/cache/typescript-fsa/index").A + +export const a = getA(); +>a : import("/cache/typescript-fsa/index").A +>getA() : import("/cache/typescript-fsa/index").A +>getA : () => import("/cache/typescript-fsa/index").A + +=== /p2/index.d.ts === +export const a: import("typescript-fsa").A; +>a : import("/cache/typescript-fsa/index").A + + diff --git a/tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink.ts b/tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink.ts new file mode 100644 index 00000000000..b42d679b521 --- /dev/null +++ b/tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink.ts @@ -0,0 +1,35 @@ +// @useCaseSensitiveFilenames: true +// @declaration: true +// @filename: /p1/node_modules/typescript-fsa/src/impl.d.ts +export function getA(): A; +export enum A { + Val +} +// @filename: /p1/node_modules/typescript-fsa/index.d.ts +export * from "./src/impl"; +// @filename: /p1/node_modules/typescript-fsa/package.json +{ + "name": "typescript-fsa", + "version": "1.0.0" +} +// @filename: /p2/node_modules/typescript-fsa/src/impl.d.ts +export function getA(): A; +export enum A { + Val +} +// @filename: /p2/node_modules/typescript-fsa/index.d.ts +export * from "./src/impl"; +// @filename: /p2/node_modules/typescript-fsa/package.json +{ + "name": "typescript-fsa", + "version": "1.0.0" +} +// @filename: /p1/index.ts +import * as _whatever from "p2"; +import { getA } from "typescript-fsa"; + +export const a = getA(); +// @filename: /p2/index.d.ts +export const a: import("typescript-fsa").A; + +// @link: /p2 -> /p1/node_modules/p2 diff --git a/tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink2.ts b/tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink2.ts new file mode 100644 index 00000000000..5b46de99622 --- /dev/null +++ b/tests/cases/compiler/declarationEmitForGlobalishSpecifierSymlink2.ts @@ -0,0 +1,25 @@ +// @useCaseSensitiveFilenames: true +// @declaration: true +// @filename: /cache/typescript-fsa/src/impl.d.ts +export function getA(): A; +export enum A { + Val +} +// @filename: /cache/typescript-fsa/index.d.ts +export * from "./src/impl"; +// @filename: /cache/typescript-fsa/package.json +{ + "name": "typescript-fsa", + "version": "1.0.0" +} +// @filename: /p1/index.ts +import * as _whatever from "p2"; +import { getA } from "typescript-fsa"; + +export const a = getA(); +// @filename: /p2/index.d.ts +export const a: import("typescript-fsa").A; + +// @link: /p2 -> /p1/node_modules/p2 +// @link: /cache/typescript-fsa -> /p1/node_modules/typescript-fsa +// @link: /cache/typescript-fsa -> /p2/node_modules/typescript-fsa From dfd28d275100db73433318236a2299b002cf0ce3 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 23 May 2019 17:19:32 -0700 Subject: [PATCH 056/111] Fix handling of empty 'types', 'typings', etc. fields in package.json (#31539) --- src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/moduleNameResolver.ts | 10 +++++++- .../reference/typesVersions.emptyTypes.js | 20 ++++++++++++++++ .../typesVersions.emptyTypes.symbols | 8 +++++++ .../typesVersions.emptyTypes.trace.json | 24 +++++++++++++++++++ .../reference/typesVersions.emptyTypes.types | 9 +++++++ .../typesVersions.emptyTypes.ts | 18 ++++++++++++++ 7 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/typesVersions.emptyTypes.js create mode 100644 tests/baselines/reference/typesVersions.emptyTypes.symbols create mode 100644 tests/baselines/reference/typesVersions.emptyTypes.trace.json create mode 100644 tests/baselines/reference/typesVersions.emptyTypes.types create mode 100644 tests/cases/conformance/moduleResolution/typesVersions.emptyTypes.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e0579429def..5d6dc7d6df4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3927,6 +3927,10 @@ "category": "Message", "code": 6219 }, + "'package.json' had a falsy '{0}' field.": { + "category": "Message", + "code": 6220 + }, "Projects to reference": { "category": "Message", diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 19402b03ee9..604b82cce5e 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -141,7 +141,15 @@ namespace ts { function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined { const fileName = readPackageJsonField(jsonContent, fieldName, "string", state); - if (fileName === undefined) return; + if (fileName === undefined) { + return; + } + if (!fileName) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName); + } + return; + } const path = normalizePath(combinePaths(baseDirectory, fileName)); if (state.traceEnabled) { trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path); diff --git a/tests/baselines/reference/typesVersions.emptyTypes.js b/tests/baselines/reference/typesVersions.emptyTypes.js new file mode 100644 index 00000000000..0bcc27d7a65 --- /dev/null +++ b/tests/baselines/reference/typesVersions.emptyTypes.js @@ -0,0 +1,20 @@ +//// [tests/cases/conformance/moduleResolution/typesVersions.emptyTypes.ts] //// + +//// [package.json] +{ + "types": "", + "typesVersions": { + ">=3.1.0-0": { "*" : ["ts3.1/*"] } + } +} + +//// [index.d.ts] +export const a = 0; + +//// [user.ts] +import { a } from "a"; + + +//// [user.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/tests/baselines/reference/typesVersions.emptyTypes.symbols b/tests/baselines/reference/typesVersions.emptyTypes.symbols new file mode 100644 index 00000000000..a4441e407d7 --- /dev/null +++ b/tests/baselines/reference/typesVersions.emptyTypes.symbols @@ -0,0 +1,8 @@ +=== /a/ts3.1/index.d.ts === +export const a = 0; +>a : Symbol(a, Decl(index.d.ts, 0, 12)) + +=== /b/user.ts === +import { a } from "a"; +>a : Symbol(a, Decl(user.ts, 0, 8)) + diff --git a/tests/baselines/reference/typesVersions.emptyTypes.trace.json b/tests/baselines/reference/typesVersions.emptyTypes.trace.json new file mode 100644 index 00000000000..e7a9e7547cf --- /dev/null +++ b/tests/baselines/reference/typesVersions.emptyTypes.trace.json @@ -0,0 +1,24 @@ +[ + "======== Resolving module 'a' from '/b/user.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "'baseUrl' option is set to '/', using this value to resolve non-relative module name 'a'.", + "Resolving module name 'a' relative to base url '/' - '/a'.", + "Loading module as file / folder, candidate module location '/a', target file type 'TypeScript'.", + "File '/a.ts' does not exist.", + "File '/a.tsx' does not exist.", + "File '/a.d.ts' does not exist.", + "Found 'package.json' at '/a/package.json'.", + "'package.json' has a 'typesVersions' field with version-specific path mappings.", + "'package.json' does not have a 'typings' field.", + "'package.json' had a falsy 'types' field.", + "'package.json' does not have a 'main' field.", + "'package.json' has a 'typesVersions' entry '>=3.1.0-0' that matches compiler version '3.1.0-dev', looking for a pattern to match module name 'index'.", + "Module name 'index', matched pattern '*'.", + "Trying substitution 'ts3.1/*', candidate module location: 'ts3.1/index'.", + "File '/a/ts3.1/index' does not exist.", + "Loading module as file / folder, candidate module location '/a/ts3.1/index', target file type 'TypeScript'.", + "File '/a/ts3.1/index.ts' does not exist.", + "File '/a/ts3.1/index.tsx' does not exist.", + "File '/a/ts3.1/index.d.ts' exist - use it as a name resolution result.", + "======== Module name 'a' was successfully resolved to '/a/ts3.1/index.d.ts'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/typesVersions.emptyTypes.types b/tests/baselines/reference/typesVersions.emptyTypes.types new file mode 100644 index 00000000000..578d4695e1c --- /dev/null +++ b/tests/baselines/reference/typesVersions.emptyTypes.types @@ -0,0 +1,9 @@ +=== /a/ts3.1/index.d.ts === +export const a = 0; +>a : 0 +>0 : 0 + +=== /b/user.ts === +import { a } from "a"; +>a : 0 + diff --git a/tests/cases/conformance/moduleResolution/typesVersions.emptyTypes.ts b/tests/cases/conformance/moduleResolution/typesVersions.emptyTypes.ts new file mode 100644 index 00000000000..f5d0792bfc4 --- /dev/null +++ b/tests/cases/conformance/moduleResolution/typesVersions.emptyTypes.ts @@ -0,0 +1,18 @@ +// @baseUrl: / +// @traceResolution: true +// @target: esnext +// @module: commonjs + +// @filename: /a/package.json +{ + "types": "", + "typesVersions": { + ">=3.1.0-0": { "*" : ["ts3.1/*"] } + } +} + +// @filename: /a/ts3.1/index.d.ts +export const a = 0; + +// @filename: /b/user.ts +import { a } from "a"; From b460d8cd267a8fc01c949289de96d424223a666d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 23 May 2019 17:50:44 -0700 Subject: [PATCH 057/111] Expose getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment with better name (#31564) --- src/compiler/checker.ts | 21 +++++++++++-------- src/compiler/types.ts | 1 + .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d7c7b022dd..b1e62d69e22 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -182,6 +182,10 @@ namespace ts { node = getParseTreeNode(node); return node ? getTypeOfNode(node) : errorType; }, + getTypeOfAssignmentPattern: nodeIn => { + const node = getParseTreeNode(nodeIn, isAssignmentPattern); + return node && getTypeOfAssignmentPattern(node) || errorType; + }, getPropertySymbolOfDestructuringAssignment: locationIn => { const location = getParseTreeNode(locationIn, isIdentifier); return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; @@ -29982,7 +29986,7 @@ namespace ts { // } // [ a ] from // [a] = [ some array ...] - function getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr: Expression): Type { + function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined { Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); // If this is from "for of" // for ( { a } of elems) { @@ -30001,17 +30005,16 @@ namespace ts { // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { if (expr.parent.kind === SyntaxKind.PropertyAssignment) { const node = cast(expr.parent.parent, isObjectLiteralExpression); - const typeOfParentObjectLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(node); + const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; const propertyIndex = indexOfNode(node.properties, expr.parent); - return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral || errorType, propertyIndex)!; // TODO: GH#18217 + return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex); } // Array literal assignment - array destructuring pattern - Debug.assert(expr.parent.kind === SyntaxKind.ArrayLiteralExpression); + const node = cast(expr.parent, isArrayLiteralExpression); // [{ property1: p1, property2 }] = elems; - const typeOfArrayLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr.parent); - const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || errorType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || errorType; - return checkArrayLiteralDestructuringElementAssignment(expr.parent, typeOfArrayLiteral, - (expr.parent).elements.indexOf(expr), elementType || errorType)!; // TODO: GH#18217 + const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; + const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || errorType; + return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); } // Gets the property symbol corresponding to the property in destructuring assignment @@ -30022,7 +30025,7 @@ namespace ts { // [a] = [ property1, property2 ] function getPropertySymbolOfDestructuringAssignment(location: Identifier) { // Get the type of the object or array literal and then look for property of given name in the type - const typeOfObjectLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(location.parent.parent); + const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index db1a62937e6..03ca196a860 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3154,6 +3154,7 @@ namespace ts { */ getExportSymbolOfSymbol(symbol: Symbol): Symbol; getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; + getTypeOfAssignmentPattern(pattern: AssignmentPattern): Type; getTypeAtLocation(node: Node): Type; getTypeFromTypeNode(node: TypeNode): Type; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4b06bc9e839..d10aa28ffe4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1969,6 +1969,7 @@ declare namespace ts { */ getExportSymbolOfSymbol(symbol: Symbol): Symbol; getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; + getTypeOfAssignmentPattern(pattern: AssignmentPattern): Type; getTypeAtLocation(node: Node): Type; getTypeFromTypeNode(node: TypeNode): Type; signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f70916a882b..58ccd42344b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1969,6 +1969,7 @@ declare namespace ts { */ getExportSymbolOfSymbol(symbol: Symbol): Symbol; getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; + getTypeOfAssignmentPattern(pattern: AssignmentPattern): Type; getTypeAtLocation(node: Node): Type; getTypeFromTypeNode(node: TypeNode): Type; signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; From 57d9ecc39f588917c3c587795dea739f740b066b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 24 May 2019 15:04:42 -0700 Subject: [PATCH 058/111] Do not log errors when ts server plugin is not found in one folder but is eventually resolved. Fixes #30106 --- src/server/project.ts | 14 +++++++------- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index c204facad5c..20cd5667dd9 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -198,13 +198,13 @@ namespace ts.server { return hasOneOrMoreJsAndNoTsFiles(this); } - public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} | undefined { + public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined { const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules"))); log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`); const result = host.require!(resolvedPath, moduleName); // TODO: GH#18217 if (result.error) { const err = result.error.stack || result.error.message || JSON.stringify(result.error); - log(`Failed to load module '${moduleName}': ${err}`); + (logErrors || log)(`Failed to load module '${moduleName}' from ${resolvedPath}: ${err}`); return undefined; } return result.module; @@ -1142,12 +1142,11 @@ namespace ts.server { protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map | undefined) { this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`); - const log = (message: string) => { - this.projectService.logger.info(message); - }; - + const log = (message: string) => this.projectService.logger.info(message); + let errorLogs: string[] | undefined; + const logError = (message: string) => { (errorLogs || (errorLogs = [])).push(message); }; const resolvedModule = firstDefined(searchPaths, searchPath => - Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log)); + Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError)); if (resolvedModule) { const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name); if (configurationOverride) { @@ -1160,6 +1159,7 @@ namespace ts.server { this.enableProxy(resolvedModule, pluginConfigEntry); } else { + forEach(errorLogs, log); this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`); } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d10aa28ffe4..993a7494994 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8321,7 +8321,7 @@ declare namespace ts.server { private readonly cancellationToken; isNonTsProject(): boolean; isJsOnlyProject(): boolean; - static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} | undefined; + static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined; isKnownTypesPackageName(name: string): boolean; installPackage(options: InstallPackageOptions): Promise; private readonly typingsCache; From e70f2af25d7d94a88ef0d852c35e38ce8504d59f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 May 2019 10:51:47 -0700 Subject: [PATCH 059/111] Defer union or intersection property type normalization (#31486) * Defer union or intersection property type normalization * Accept moved span --- src/compiler/checker.ts | 77 +++++++++++++++++-- src/compiler/types.ts | 3 + .../reference/intersectionTypeMembers.js | 34 ++++++++ .../reference/intersectionTypeMembers.symbols | 55 +++++++++++++ .../reference/intersectionTypeMembers.types | 58 ++++++++++++++ ...ormalizedIntersectionTooComplex.errors.txt | 6 +- .../reactTagNameComponentWithPropsNoOOM.js | 33 ++++++++ ...eactTagNameComponentWithPropsNoOOM.symbols | 32 ++++++++ .../reactTagNameComponentWithPropsNoOOM.types | 35 +++++++++ .../reactTagNameComponentWithPropsNoOOM2.js | 33 ++++++++ ...actTagNameComponentWithPropsNoOOM2.symbols | 35 +++++++++ ...reactTagNameComponentWithPropsNoOOM2.types | 36 +++++++++ .../reactTagNameComponentWithPropsNoOOM.tsx | 13 ++++ .../reactTagNameComponentWithPropsNoOOM2.tsx | 13 ++++ .../intersection/intersectionTypeMembers.ts | 22 ++++++ 15 files changed, 474 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.js create mode 100644 tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.symbols create mode 100644 tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.types create mode 100644 tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.js create mode 100644 tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.symbols create mode 100644 tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.types create mode 100644 tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx create mode 100644 tests/cases/compiler/reactTagNameComponentWithPropsNoOOM2.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5091f5f0306..d59cf4c5470 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5921,7 +5921,20 @@ namespace ts { return anyType; } + function getTypeOfSymbolWithDeferredType(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.type) { + Debug.assertDefined(links.deferralParent); + Debug.assertDefined(links.deferralConstituents); + links.type = links.deferralParent!.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents!) : getIntersectionType(links.deferralConstituents!); + } + return links.type; + } + function getTypeOfSymbol(symbol: Symbol): Type { + if (getCheckFlags(symbol) & CheckFlags.DeferredType) { + return getTypeOfSymbolWithDeferredType(symbol); + } if (getCheckFlags(symbol) & CheckFlags.Instantiated) { return getTypeOfInstantiatedSymbol(symbol); } @@ -8061,7 +8074,15 @@ namespace ts { result.declarations = declarations!; result.nameType = nameType; - result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + if (propTypes.length > 2) { + // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed + result.checkFlags |= CheckFlags.DeferredType; + result.deferralParent = containingType; + result.deferralConstituents = propTypes; + } + else { + result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + } return result; } @@ -12313,8 +12334,8 @@ namespace ts { return false; } - function isIgnoredJsxProperty(source: Type, sourceProp: Symbol, targetMemberType: Type | undefined) { - return getObjectFlags(source) & ObjectFlags.JsxAttributes && !(isUnhyphenatedJsxName(sourceProp.escapedName) || targetMemberType); + function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { + return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName); } /** @@ -13495,6 +13516,49 @@ namespace ts { return result || properties; } + function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary { + const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); + const source = getTypeOfSourceProperty(sourceProp); + if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) { + // Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes + const links = getSymbolLinks(targetProp); + Debug.assertDefined(links.deferralParent); + Debug.assertDefined(links.deferralConstituents); + const unionParent = !!(links.deferralParent!.flags & TypeFlags.Union); + let result = unionParent ? Ternary.False : Ternary.True; + const targetTypes = links.deferralConstituents!; + for (const targetType of targetTypes) { + const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, /*isIntersectionConstituent*/ !unionParent); + if (!unionParent) { + if (!related) { + // Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization) + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); + } + result &= related; + } + else { + if (related) { + return related; + } + } + } + if (unionParent && !result && targetIsOptional) { + result = isRelatedTo(source, undefinedType); + } + if (unionParent && !result && reportErrors) { + // The easiest way to get the right errors here is to un-defer (which may be costly) + // If it turns out this is too costly too often, we can replicate the error handling logic within + // typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union + // type on which to hand discriminable properties, which we are expressly trying to avoid here) + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); + } + return result; + } + else { + return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); + } + } + function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary { const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); @@ -13537,7 +13601,7 @@ namespace ts { return Ternary.False; } // If the target comes from a partial union prop, allow `undefined` in the target type - const related = isRelatedTo(getTypeOfSourceProperty(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors); + const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors); if (!related) { if (reportErrors) { reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); @@ -13649,9 +13713,6 @@ namespace ts { if (!(targetProp.flags & SymbolFlags.Prototype)) { const sourceProp = getPropertyOfType(source, targetProp.escapedName); if (sourceProp && sourceProp !== targetProp) { - if (isIgnoredJsxProperty(source, sourceProp, getTypeOfSymbol(targetProp))) { - continue; - } const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors); if (!related) { return Ternary.False; @@ -13797,7 +13858,7 @@ namespace ts { function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { let result = Ternary.True; for (const prop of getPropertiesOfObjectType(source)) { - if (isIgnoredJsxProperty(source, prop, /*targetMemberType*/ undefined)) { + if (isIgnoredJsxProperty(source, prop)) { continue; } // Skip over symbol-named members diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 03ca196a860..3a80655385d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3752,6 +3752,8 @@ namespace ts { extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in extendedContainersByFile?: Map; // Containers (other than the parent) which this symbol is aliased in variances?: VarianceFlags[]; // Alias symbol type argument variance cache + deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type + deferralParent?: Type; // Source union/intersection of a deferred type } /* @internal */ @@ -3778,6 +3780,7 @@ namespace ts { ReverseMapped = 1 << 13, // Property of reverse-inferred homomorphic mapped type OptionalParameter = 1 << 14, // Optional parameter RestParameter = 1 << 15, // Rest parameter + DeferredType = 1 << 16, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType` Synthetic = SyntheticProperty | SyntheticMethod, Discriminant = HasNonUniformType | HasLiteralType, Partial = ReadPartial | WritePartial diff --git a/tests/baselines/reference/intersectionTypeMembers.js b/tests/baselines/reference/intersectionTypeMembers.js index 59968b007db..867a6f0fc66 100644 --- a/tests/baselines/reference/intersectionTypeMembers.js +++ b/tests/baselines/reference/intersectionTypeMembers.js @@ -43,6 +43,28 @@ const de: D & E = { other: { g: 101 } } } + +// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props +interface F { + nested: { doublyNested: { g: string; } } +} + +interface G { + nested: { doublyNested: { h: string; } } +} + +const defg: D & E & F & G = { + nested: { + doublyNested: { + d: 'yes', + f: 'no', + g: 'ok', + h: 'affirmative' + }, + different: { e: 12 }, + other: { g: 101 } + } +} //// [intersectionTypeMembers.js] @@ -69,3 +91,15 @@ var de = { other: { g: 101 } } }; +var defg = { + nested: { + doublyNested: { + d: 'yes', + f: 'no', + g: 'ok', + h: 'affirmative' + }, + different: { e: 12 }, + other: { g: 101 } + } +}; diff --git a/tests/baselines/reference/intersectionTypeMembers.symbols b/tests/baselines/reference/intersectionTypeMembers.symbols index be47b2960db..b4629d1659d 100644 --- a/tests/baselines/reference/intersectionTypeMembers.symbols +++ b/tests/baselines/reference/intersectionTypeMembers.symbols @@ -146,3 +146,58 @@ const de: D & E = { } } +// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props +interface F { +>F : Symbol(F, Decl(intersectionTypeMembers.ts, 43, 1)) + + nested: { doublyNested: { g: string; } } +>nested : Symbol(F.nested, Decl(intersectionTypeMembers.ts, 46, 13)) +>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 47, 13)) +>g : Symbol(g, Decl(intersectionTypeMembers.ts, 47, 29)) +} + +interface G { +>G : Symbol(G, Decl(intersectionTypeMembers.ts, 48, 1)) + + nested: { doublyNested: { h: string; } } +>nested : Symbol(G.nested, Decl(intersectionTypeMembers.ts, 50, 13)) +>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 51, 13)) +>h : Symbol(h, Decl(intersectionTypeMembers.ts, 51, 29)) +} + +const defg: D & E & F & G = { +>defg : Symbol(defg, Decl(intersectionTypeMembers.ts, 54, 5)) +>D : Symbol(D, Decl(intersectionTypeMembers.ts, 26, 14)) +>E : Symbol(E, Decl(intersectionTypeMembers.ts, 30, 1)) +>F : Symbol(F, Decl(intersectionTypeMembers.ts, 43, 1)) +>G : Symbol(G, Decl(intersectionTypeMembers.ts, 48, 1)) + + nested: { +>nested : Symbol(nested, Decl(intersectionTypeMembers.ts, 54, 29)) + + doublyNested: { +>doublyNested : Symbol(doublyNested, Decl(intersectionTypeMembers.ts, 55, 13)) + + d: 'yes', +>d : Symbol(d, Decl(intersectionTypeMembers.ts, 56, 23)) + + f: 'no', +>f : Symbol(f, Decl(intersectionTypeMembers.ts, 57, 21)) + + g: 'ok', +>g : Symbol(g, Decl(intersectionTypeMembers.ts, 58, 20)) + + h: 'affirmative' +>h : Symbol(h, Decl(intersectionTypeMembers.ts, 59, 20)) + + }, + different: { e: 12 }, +>different : Symbol(different, Decl(intersectionTypeMembers.ts, 61, 10)) +>e : Symbol(e, Decl(intersectionTypeMembers.ts, 62, 20)) + + other: { g: 101 } +>other : Symbol(other, Decl(intersectionTypeMembers.ts, 62, 29)) +>g : Symbol(g, Decl(intersectionTypeMembers.ts, 63, 16)) + } +} + diff --git a/tests/baselines/reference/intersectionTypeMembers.types b/tests/baselines/reference/intersectionTypeMembers.types index eb9d5fa4eb8..817fa044624 100644 --- a/tests/baselines/reference/intersectionTypeMembers.types +++ b/tests/baselines/reference/intersectionTypeMembers.types @@ -148,3 +148,61 @@ const de: D & E = { } } +// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props +interface F { + nested: { doublyNested: { g: string; } } +>nested : { doublyNested: { g: string; }; } +>doublyNested : { g: string; } +>g : string +} + +interface G { + nested: { doublyNested: { h: string; } } +>nested : { doublyNested: { h: string; }; } +>doublyNested : { h: string; } +>h : string +} + +const defg: D & E & F & G = { +>defg : D & E & F & G +>{ nested: { doublyNested: { d: 'yes', f: 'no', g: 'ok', h: 'affirmative' }, different: { e: 12 }, other: { g: 101 } }} : { nested: { doublyNested: { d: string; f: string; g: string; h: string; }; different: { e: number; }; other: { g: number; }; }; } + + nested: { +>nested : { doublyNested: { d: string; f: string; g: string; h: string; }; different: { e: number; }; other: { g: number; }; } +>{ doublyNested: { d: 'yes', f: 'no', g: 'ok', h: 'affirmative' }, different: { e: 12 }, other: { g: 101 } } : { doublyNested: { d: string; f: string; g: string; h: string; }; different: { e: number; }; other: { g: number; }; } + + doublyNested: { +>doublyNested : { d: string; f: string; g: string; h: string; } +>{ d: 'yes', f: 'no', g: 'ok', h: 'affirmative' } : { d: string; f: string; g: string; h: string; } + + d: 'yes', +>d : string +>'yes' : "yes" + + f: 'no', +>f : string +>'no' : "no" + + g: 'ok', +>g : string +>'ok' : "ok" + + h: 'affirmative' +>h : string +>'affirmative' : "affirmative" + + }, + different: { e: 12 }, +>different : { e: number; } +>{ e: 12 } : { e: number; } +>e : number +>12 : 12 + + other: { g: 101 } +>other : { g: number; } +>{ g: 101 } : { g: number; } +>g : number +>101 : 101 + } +} + diff --git a/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt b/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt index 4e214eff507..64e7d7f00c5 100644 --- a/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt +++ b/tests/baselines/reference/normalizedIntersectionTooComplex.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,14): error TS2590: Expression produces a union type that is too complex to represent. tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS7006: Parameter 'x' implicitly has an 'any' type. +tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS2590: Expression produces a union type that is too complex to represent. ==== tests/cases/compiler/normalizedIntersectionTooComplex.ts (2 errors) ==== @@ -39,8 +39,8 @@ tests/cases/compiler/normalizedIntersectionTooComplex.ts(36,40): error TS7006: P declare var all: keyof Big; const ctor = getCtor(all); const comp = ctor({ common: "ok", ref: x => console.log(x) }); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2590: Expression produces a union type that is too complex to represent. ~ !!! error TS7006: Parameter 'x' implicitly has an 'any' type. + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2590: Expression produces a union type that is too complex to represent. \ No newline at end of file diff --git a/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.js b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.js new file mode 100644 index 00000000000..c85d07ec99b --- /dev/null +++ b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.js @@ -0,0 +1,33 @@ +//// [reactTagNameComponentWithPropsNoOOM.tsx] +/// + +import * as React from "react"; +declare const Tag: keyof React.ReactHTML; + +const classes = ""; +const rest: {} = {}; +const children: any[] = []; + +{children} + + +//// [reactTagNameComponentWithPropsNoOOM.js] +"use strict"; +/// +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +exports.__esModule = true; +var React = require("react"); +var classes = ""; +var rest = {}; +var children = []; +React.createElement(Tag, __assign({ className: classes }, rest), children); diff --git a/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.symbols b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.symbols new file mode 100644 index 00000000000..0b827bf7a0d --- /dev/null +++ b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.symbols @@ -0,0 +1,32 @@ +=== tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx === +/// + +import * as React from "react"; +>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 2, 6)) + +declare const Tag: keyof React.ReactHTML; +>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 3, 13)) +>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 2, 6)) +>ReactHTML : Symbol(React.ReactHTML, Decl(react16.d.ts, 2089, 9)) + +const classes = ""; +>classes : Symbol(classes, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 5, 5)) + +const rest: {} = {}; +>rest : Symbol(rest, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 6, 5)) + +const children: any[] = []; +>children : Symbol(children, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 7, 5)) + + +>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 3, 13)) +>className : Symbol(className, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 8, 4)) +>classes : Symbol(classes, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 5, 5)) +>rest : Symbol(rest, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 6, 5)) + +{children} +>children : Symbol(children, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 7, 5)) + + +>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM.tsx, 3, 13)) + diff --git a/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.types b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.types new file mode 100644 index 00000000000..f31716a55e4 --- /dev/null +++ b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM.types @@ -0,0 +1,35 @@ +=== tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx === +/// + +import * as React from "react"; +>React : typeof React + +declare const Tag: keyof React.ReactHTML; +>Tag : "object" | "time" | "link" | "menu" | "dialog" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "kbd" | "keygen" | "label" | "legend" | "li" | "main" | "map" | "mark" | "menuitem" | "meta" | "meter" | "nav" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "pre" | "progress" | "q" | "rp" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "textarea" | "tfoot" | "th" | "thead" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" | "wbr" | "webview" +>React : any + +const classes = ""; +>classes : "" +>"" : "" + +const rest: {} = {}; +>rest : {} +>{} : {} + +const children: any[] = []; +>children : any[] +>[] : never[] + + +>{children} : JSX.Element +>Tag : "object" | "time" | "link" | "menu" | "dialog" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "kbd" | "keygen" | "label" | "legend" | "li" | "main" | "map" | "mark" | "menuitem" | "meta" | "meter" | "nav" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "pre" | "progress" | "q" | "rp" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "textarea" | "tfoot" | "th" | "thead" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" | "wbr" | "webview" +>className : string +>classes : "" +>rest : {} + +{children} +>children : any[] + + +>Tag : "object" | "time" | "link" | "menu" | "dialog" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "kbd" | "keygen" | "label" | "legend" | "li" | "main" | "map" | "mark" | "menuitem" | "meta" | "meter" | "nav" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "pre" | "progress" | "q" | "rp" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "textarea" | "tfoot" | "th" | "thead" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" | "wbr" | "webview" + diff --git a/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.js b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.js new file mode 100644 index 00000000000..4b6e9d0c54a --- /dev/null +++ b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.js @@ -0,0 +1,33 @@ +//// [reactTagNameComponentWithPropsNoOOM2.tsx] +/// + +import * as React from "react"; +declare const Tag: keyof React.ReactHTML; + +const classes = ""; +const rest: React.HTMLAttributes = {}; +const children: any[] = []; + +{children} + + +//// [reactTagNameComponentWithPropsNoOOM2.js] +"use strict"; +/// +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +exports.__esModule = true; +var React = require("react"); +var classes = ""; +var rest = {}; +var children = []; +React.createElement(Tag, __assign({ className: classes }, rest), children); diff --git a/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.symbols b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.symbols new file mode 100644 index 00000000000..658f9fd5c43 --- /dev/null +++ b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.symbols @@ -0,0 +1,35 @@ +=== tests/cases/compiler/reactTagNameComponentWithPropsNoOOM2.tsx === +/// + +import * as React from "react"; +>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 2, 6)) + +declare const Tag: keyof React.ReactHTML; +>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 3, 13)) +>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 2, 6)) +>ReactHTML : Symbol(React.ReactHTML, Decl(react16.d.ts, 2089, 9)) + +const classes = ""; +>classes : Symbol(classes, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 5, 5)) + +const rest: React.HTMLAttributes = {}; +>rest : Symbol(rest, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 6, 5)) +>React : Symbol(React, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 2, 6)) +>HTMLAttributes : Symbol(React.HTMLAttributes, Decl(react16.d.ts, 1048, 9), Decl(react16.d.ts, 1105, 9)) +>HTMLElement : Symbol(HTMLElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) + +const children: any[] = []; +>children : Symbol(children, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 7, 5)) + + +>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 3, 13)) +>className : Symbol(className, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 8, 4)) +>classes : Symbol(classes, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 5, 5)) +>rest : Symbol(rest, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 6, 5)) + +{children} +>children : Symbol(children, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 7, 5)) + + +>Tag : Symbol(Tag, Decl(reactTagNameComponentWithPropsNoOOM2.tsx, 3, 13)) + diff --git a/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.types b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.types new file mode 100644 index 00000000000..fac8a844e03 --- /dev/null +++ b/tests/baselines/reference/reactTagNameComponentWithPropsNoOOM2.types @@ -0,0 +1,36 @@ +=== tests/cases/compiler/reactTagNameComponentWithPropsNoOOM2.tsx === +/// + +import * as React from "react"; +>React : typeof React + +declare const Tag: keyof React.ReactHTML; +>Tag : "object" | "time" | "link" | "menu" | "dialog" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "kbd" | "keygen" | "label" | "legend" | "li" | "main" | "map" | "mark" | "menuitem" | "meta" | "meter" | "nav" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "pre" | "progress" | "q" | "rp" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "textarea" | "tfoot" | "th" | "thead" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" | "wbr" | "webview" +>React : any + +const classes = ""; +>classes : "" +>"" : "" + +const rest: React.HTMLAttributes = {}; +>rest : React.HTMLAttributes +>React : any +>{} : {} + +const children: any[] = []; +>children : any[] +>[] : never[] + + +>{children} : JSX.Element +>Tag : "object" | "time" | "link" | "menu" | "dialog" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "kbd" | "keygen" | "label" | "legend" | "li" | "main" | "map" | "mark" | "menuitem" | "meta" | "meter" | "nav" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "pre" | "progress" | "q" | "rp" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "textarea" | "tfoot" | "th" | "thead" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" | "wbr" | "webview" +>className : string +>classes : "" +>rest : React.HTMLAttributes + +{children} +>children : any[] + + +>Tag : "object" | "time" | "link" | "menu" | "dialog" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "details" | "dfn" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "kbd" | "keygen" | "label" | "legend" | "li" | "main" | "map" | "mark" | "menuitem" | "meta" | "meter" | "nav" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "pre" | "progress" | "q" | "rp" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "small" | "source" | "span" | "strong" | "style" | "sub" | "summary" | "sup" | "table" | "tbody" | "td" | "textarea" | "tfoot" | "th" | "thead" | "title" | "tr" | "track" | "u" | "ul" | "var" | "video" | "wbr" | "webview" + diff --git a/tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx b/tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx new file mode 100644 index 00000000000..e6011974230 --- /dev/null +++ b/tests/cases/compiler/reactTagNameComponentWithPropsNoOOM.tsx @@ -0,0 +1,13 @@ +// @jsx: react +// @strict: true +/// + +import * as React from "react"; +declare const Tag: keyof React.ReactHTML; + +const classes = ""; +const rest: {} = {}; +const children: any[] = []; + +{children} + \ No newline at end of file diff --git a/tests/cases/compiler/reactTagNameComponentWithPropsNoOOM2.tsx b/tests/cases/compiler/reactTagNameComponentWithPropsNoOOM2.tsx new file mode 100644 index 00000000000..64d46b5dd95 --- /dev/null +++ b/tests/cases/compiler/reactTagNameComponentWithPropsNoOOM2.tsx @@ -0,0 +1,13 @@ +// @jsx: react +// @strict: true +/// + +import * as React from "react"; +declare const Tag: keyof React.ReactHTML; + +const classes = ""; +const rest: React.HTMLAttributes = {}; +const children: any[] = []; + +{children} + \ No newline at end of file diff --git a/tests/cases/conformance/types/intersection/intersectionTypeMembers.ts b/tests/cases/conformance/types/intersection/intersectionTypeMembers.ts index a350c657f61..bfb257b8ac2 100644 --- a/tests/cases/conformance/types/intersection/intersectionTypeMembers.ts +++ b/tests/cases/conformance/types/intersection/intersectionTypeMembers.ts @@ -42,3 +42,25 @@ const de: D & E = { other: { g: 101 } } } + +// Additional test case with >2 doubly nested members so fix for #31441 is tested w/ excess props +interface F { + nested: { doublyNested: { g: string; } } +} + +interface G { + nested: { doublyNested: { h: string; } } +} + +const defg: D & E & F & G = { + nested: { + doublyNested: { + d: 'yes', + f: 'no', + g: 'ok', + h: 'affirmative' + }, + different: { e: 12 }, + other: { g: 101 } + } +} From 63b8c6443f809367c2e902fb5261126755b23cca Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Tue, 28 May 2019 11:03:29 -0700 Subject: [PATCH 060/111] Update user baselines (#31615) --- tests/baselines/reference/user/bluebird.log | 106 +++++++++--------- .../user/chrome-devtools-frontend.log | 9 +- tests/baselines/reference/user/webpack.log | 5 +- 3 files changed, 56 insertions(+), 64 deletions(-) diff --git a/tests/baselines/reference/user/bluebird.log b/tests/baselines/reference/user/bluebird.log index 5c1689ff49b..26d7270bcb6 100644 --- a/tests/baselines/reference/user/bluebird.log +++ b/tests/baselines/reference/user/bluebird.log @@ -131,60 +131,62 @@ node_modules/bluebird/js/release/promise.js(65,15): error TS2350: Only a void fu node_modules/bluebird/js/release/promise.js(65,68): error TS2339: Property 'classString' does not exist on type 'typeof ret'. node_modules/bluebird/js/release/promise.js(95,22): error TS2339: Property 'isObject' does not exist on type 'typeof ret'. node_modules/bluebird/js/release/promise.js(99,59): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(119,22): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(121,32): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(123,14): error TS2339: Property '_warn' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(136,68): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(148,14): error TS2551: Property 'isFulfilled' does not exist on type 'Promise'. Did you mean '_setFulfilled'? -node_modules/bluebird/js/release/promise.js(149,37): error TS2339: Property 'value' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(151,21): error TS2551: Property 'isRejected' does not exist on type 'Promise'. Did you mean '_setRejected'? -node_modules/bluebird/js/release/promise.js(152,36): error TS2339: Property 'reason' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(160,14): error TS2339: Property '_warn' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(166,29): error TS2339: Property 'originatesFromRejection' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(177,9): error TS2339: Property '_captureStackTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(207,9): error TS2339: Property '_captureStackTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(214,15): error TS2350: Only a void function can be called with the 'new' keyword. -node_modules/bluebird/js/release/promise.js(214,68): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(238,63): error TS2339: Property '_boundTo' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(241,14): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(253,20): error TS2339: Property '_unsetRejectionIsUnhandled' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(257,20): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(264,26): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(295,10): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(106,19): error TS2350: Only a void function can be called with the 'new' keyword. +node_modules/bluebird/js/release/promise.js(107,60): error TS2554: Expected 0 arguments, but got 1. +node_modules/bluebird/js/release/promise.js(124,22): error TS2339: Property 'classString' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(126,32): error TS2339: Property 'classString' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(128,14): error TS2339: Property '_warn' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(141,68): error TS2339: Property 'classString' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(153,14): error TS2551: Property 'isFulfilled' does not exist on type 'Promise'. Did you mean '_setFulfilled'? +node_modules/bluebird/js/release/promise.js(154,37): error TS2339: Property 'value' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(156,21): error TS2551: Property 'isRejected' does not exist on type 'Promise'. Did you mean '_setRejected'? +node_modules/bluebird/js/release/promise.js(157,36): error TS2339: Property 'reason' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(165,14): error TS2339: Property '_warn' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(171,29): error TS2339: Property 'originatesFromRejection' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(182,9): error TS2339: Property '_captureStackTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(212,9): error TS2339: Property '_captureStackTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(219,15): error TS2350: Only a void function can be called with the 'new' keyword. +node_modules/bluebird/js/release/promise.js(219,68): error TS2339: Property 'classString' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(243,63): error TS2339: Property '_boundTo' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(246,14): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(258,20): error TS2339: Property '_unsetRejectionIsUnhandled' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(262,20): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(269,26): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. node_modules/bluebird/js/release/promise.js(300,10): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. node_modules/bluebird/js/release/promise.js(305,10): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(322,10): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(339,42): error TS2339: Property '_isBound' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(400,50): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(404,49): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(412,50): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(416,49): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(434,26): error TS2339: Property '_propagateFrom' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(454,31): error TS2339: Property '_value' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(456,30): error TS2339: Property '_reason' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(459,17): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(466,22): error TS2339: Property 'ensureErrorObject' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(470,18): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(471,14): error TS2339: Property '_warn' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(473,10): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(480,10): error TS2339: Property '_captureStackTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(481,10): error TS2339: Property '_pushContext' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(483,18): error TS2339: Property '_execute' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(489,10): error TS2339: Property '_popContext' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(506,19): error TS2350: Only a void function can be called with the 'new' keyword. -node_modules/bluebird/js/release/promise.js(507,42): error TS2339: Property 'classString' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(558,22): error TS2339: Property '_promiseCancelled' does not exist on type '{}'. -node_modules/bluebird/js/release/promise.js(572,23): error TS2339: Property '_isResolved' does not exist on type '{}'. -node_modules/bluebird/js/release/promise.js(574,26): error TS2339: Property '_promiseFulfilled' does not exist on type '{}'. -node_modules/bluebird/js/release/promise.js(576,26): error TS2339: Property '_promiseRejected' does not exist on type '{}'. -node_modules/bluebird/js/release/promise.js(630,14): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(642,14): error TS2339: Property '_dereferenceTrace' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(653,46): error TS2339: Property 'isNode' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(659,14): error TS2339: Property '_ensurePossibleRejectionHandled' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(699,10): error TS2339: Property '_clearCancellationData' does not exist on type 'Promise'. -node_modules/bluebird/js/release/promise.js(724,6): error TS2339: Property 'notEnumerableProp' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(754,10): error TS2339: Property 'toFastProperties' does not exist on type 'typeof ret'. -node_modules/bluebird/js/release/promise.js(755,10): error TS2339: Property 'toFastProperties' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(310,10): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(327,10): error TS2339: Property '_fireEvent' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(344,42): error TS2339: Property '_isBound' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(405,50): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(409,49): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(417,50): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(421,49): error TS2339: Property 'domainBind' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(439,26): error TS2339: Property '_propagateFrom' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(459,31): error TS2339: Property '_value' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(461,30): error TS2339: Property '_reason' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(464,17): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(471,22): error TS2339: Property 'ensureErrorObject' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(475,18): error TS2339: Property 'classString' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(476,14): error TS2339: Property '_warn' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(478,10): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(485,10): error TS2339: Property '_captureStackTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(486,10): error TS2339: Property '_pushContext' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(488,18): error TS2339: Property '_execute' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(494,10): error TS2339: Property '_popContext' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(511,19): error TS2350: Only a void function can be called with the 'new' keyword. +node_modules/bluebird/js/release/promise.js(512,42): error TS2339: Property 'classString' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(563,22): error TS2339: Property '_promiseCancelled' does not exist on type '{}'. +node_modules/bluebird/js/release/promise.js(577,23): error TS2339: Property '_isResolved' does not exist on type '{}'. +node_modules/bluebird/js/release/promise.js(579,26): error TS2339: Property '_promiseFulfilled' does not exist on type '{}'. +node_modules/bluebird/js/release/promise.js(581,26): error TS2339: Property '_promiseRejected' does not exist on type '{}'. +node_modules/bluebird/js/release/promise.js(635,14): error TS2339: Property '_attachExtraTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(647,14): error TS2339: Property '_dereferenceTrace' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(658,46): error TS2339: Property 'isNode' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(664,14): error TS2339: Property '_ensurePossibleRejectionHandled' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(704,10): error TS2339: Property '_clearCancellationData' does not exist on type 'Promise'. +node_modules/bluebird/js/release/promise.js(737,6): error TS2339: Property 'notEnumerableProp' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(767,10): error TS2339: Property 'toFastProperties' does not exist on type 'typeof ret'. +node_modules/bluebird/js/release/promise.js(768,10): error TS2339: Property 'toFastProperties' does not exist on type 'typeof ret'. node_modules/bluebird/js/release/promise_array.js(5,20): error TS2339: Property 'isArray' does not exist on type 'typeof ret'. node_modules/bluebird/js/release/promise_array.js(26,6): error TS2339: Property 'inherits' does not exist on type 'typeof ret'. node_modules/bluebird/js/release/promise_array.js(61,19): error TS2339: Property 'asArray' does not exist on type 'typeof ret'. diff --git a/tests/baselines/reference/user/chrome-devtools-frontend.log b/tests/baselines/reference/user/chrome-devtools-frontend.log index bdde6b918c0..d32422fb416 100644 --- a/tests/baselines/reference/user/chrome-devtools-frontend.log +++ b/tests/baselines/reference/user/chrome-devtools-frontend.log @@ -698,13 +698,6 @@ node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighth node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(10092,16): error TS2304: Cannot find name 'd41d8cd98f00b204e9800998ecf8427e_LibraryDetectorTests'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(10513,19): error TS2488: Type 'NodeListOf' must have a '[Symbol.iterator]()' method that returns an iterator. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(10811,19): error TS2304: Cannot find name 'getElementsInDocument'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11441,1): error TS2322: Type 'unknown' is not assignable to type 'number'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11444,1): error TS2322: Type 'unknown' is not assignable to type 'number'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11447,15): error TS2339: Property 'textLength' does not exist on type 'unknown'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11447,28): error TS2339: Property 'textLength' does not exist on type 'unknown'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11450,43): error TS2339: Property 'node' does not exist on type 'unknown'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11453,6): error TS2339: Property 'cssRule' does not exist on type 'unknown'. -node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(11460,6): error TS2339: Property 'cssRule' does not exist on type 'unknown'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(12197,34): error TS2554: Expected 0 arguments, but got 2. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(12327,36): error TS2554: Expected 0 arguments, but got 1. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(13607,7): error TS2339: Property 'protocolMethod' does not exist on type 'Error'. @@ -3389,7 +3382,7 @@ node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2765,22): error node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2785,91): error TS2339: Property 'xRel' does not exist on type 'Pos'. node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2855,32): error TS2339: Property 'left' does not exist on type 'never'. node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2855,49): error TS2339: Property 'right' does not exist on type 'never'. -node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2856,10): error TS2365: Operator '+' cannot be applied to types 'null' and '0 | 1'. +node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2856,10): error TS2365: Operator '+' cannot be applied to types 'null' and '1 | 0'. node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2858,32): error TS2339: Property 'left' does not exist on type 'never'. node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(2858,49): error TS2339: Property 'right' does not exist on type 'never'. node_modules/chrome-devtools-frontend/front_end/cm/codemirror.js(3034,25): error TS2339: Property 'xRel' does not exist on type 'Pos'. diff --git a/tests/baselines/reference/user/webpack.log b/tests/baselines/reference/user/webpack.log index 0cde6bb1623..ce91836eddf 100644 --- a/tests/baselines/reference/user/webpack.log +++ b/tests/baselines/reference/user/webpack.log @@ -1,10 +1,7 @@ Exit Code: 1 Standard output: -../../../../../built/local/lib.dom.d.ts(17676,19): error TS2451: Cannot redeclare block-scoped variable 'WebAssembly'. +../../../../../built/local/lib.dom.d.ts(17677,19): error TS2451: Cannot redeclare block-scoped variable 'WebAssembly'. declarations.d.ts(258,15): error TS2451: Cannot redeclare block-scoped variable 'WebAssembly'. -lib/ContextModuleFactory.js(253,50): error TS2339: Property 'concat' does not exist on type 'unknown'. -lib/web/JsonpChunkTemplatePlugin.js(13,6): error TS2339: Property 'id' does not exist on type 'unknown'. -lib/web/JsonpMainTemplatePlugin.js(501,11): error TS2339: Property 'id' does not exist on type 'unknown'. From cd09cbbd5e5e483d5441e41d0acbcde9e868af60 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 28 May 2019 13:13:46 -0700 Subject: [PATCH 061/111] Cache widened types (#31586) * Cache widened types * Fix lint --- src/compiler/checker.ts | 26 ++-- src/compiler/types.ts | 2 + .../reference/noImplicitThisBigThis.js | 115 ++++++++++++++++++ .../reference/noImplicitThisBigThis.symbols | 99 +++++++++++++++ .../reference/noImplicitThisBigThis.types | 103 ++++++++++++++++ tests/cases/compiler/noImplicitThisBigThis.ts | 50 ++++++++ 6 files changed, 386 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/noImplicitThisBigThis.js create mode 100644 tests/baselines/reference/noImplicitThisBigThis.symbols create mode 100644 tests/baselines/reference/noImplicitThisBigThis.types create mode 100644 tests/cases/compiler/noImplicitThisBigThis.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d59cf4c5470..85b3641f004 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14740,26 +14740,34 @@ namespace ts { function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { + if (context === undefined && type.widened) { + return type.widened; + } + let result: Type | undefined; if (type.flags & TypeFlags.Nullable) { - return anyType; + result = anyType; } - if (isObjectLiteralType(type)) { - return getWidenedTypeOfObjectLiteral(type, context); + else if (isObjectLiteralType(type)) { + result = getWidenedTypeOfObjectLiteral(type, context); } - if (type.flags & TypeFlags.Union) { + else if (type.flags & TypeFlags.Union) { const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type).types); const widenedTypes = sameMap((type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext)); // Widening an empty object literal transitions from a highly restrictive type to // a highly inclusive one. For that reason we perform subtype reduction here if the // union includes empty object types (e.g. reducing {} | string to just {}). - return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); + result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal); } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(sameMap((type).types, getWidenedType)); + else if (type.flags & TypeFlags.Intersection) { + result = getIntersectionType(sameMap((type).types, getWidenedType)); } - if (isArrayType(type) || isTupleType(type)) { - return createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType)); + else if (isArrayType(type) || isTupleType(type)) { + result = createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType)); } + if (result && context === undefined) { + type.widened = result; + } + return result || type; } return type; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3a80655385d..cb646a0b687 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4013,6 +4013,8 @@ namespace ts { restrictiveInstantiation?: Type; // Instantiation with type parameters mapped to unconstrained form /* @internal */ immediateBaseConstraint?: Type; // Immediate base constraint cache + /* @internal */ + widened?: Type; // Cached widened form of the type } /* @internal */ diff --git a/tests/baselines/reference/noImplicitThisBigThis.js b/tests/baselines/reference/noImplicitThisBigThis.js new file mode 100644 index 00000000000..f2e633f2dcf --- /dev/null +++ b/tests/baselines/reference/noImplicitThisBigThis.js @@ -0,0 +1,115 @@ +//// [noImplicitThisBigThis.ts] +// https://github.com/microsoft/TypeScript/issues/29902 + +function createObj() { + return { + func1() { + return this; + }, + func2() { + return this; + }, + func3() { + return this; + } + }; +} + +function createObjNoCrash() { + return { + func1() { + return this; + }, + func2() { + return this; + }, + func3() { + return this; + }, + func4() { + return this; + }, + func5() { + return this; + }, + func6() { + return this; + }, + func7() { + return this; + }, + func8() { + return this; + }, + func9() { + return this; + } + }; +} + + +//// [noImplicitThisBigThis.js] +// https://github.com/microsoft/TypeScript/issues/29902 +function createObj() { + return { + func1: function () { + return this; + }, + func2: function () { + return this; + }, + func3: function () { + return this; + } + }; +} +function createObjNoCrash() { + return { + func1: function () { + return this; + }, + func2: function () { + return this; + }, + func3: function () { + return this; + }, + func4: function () { + return this; + }, + func5: function () { + return this; + }, + func6: function () { + return this; + }, + func7: function () { + return this; + }, + func8: function () { + return this; + }, + func9: function () { + return this; + } + }; +} + + +//// [noImplicitThisBigThis.d.ts] +declare function createObj(): { + func1(): any; + func2(): any; + func3(): any; +}; +declare function createObjNoCrash(): { + func1(): any; + func2(): any; + func3(): any; + func4(): any; + func5(): any; + func6(): any; + func7(): any; + func8(): any; + func9(): any; +}; diff --git a/tests/baselines/reference/noImplicitThisBigThis.symbols b/tests/baselines/reference/noImplicitThisBigThis.symbols new file mode 100644 index 00000000000..9f0c8c1bf24 --- /dev/null +++ b/tests/baselines/reference/noImplicitThisBigThis.symbols @@ -0,0 +1,99 @@ +=== tests/cases/compiler/noImplicitThisBigThis.ts === +// https://github.com/microsoft/TypeScript/issues/29902 + +function createObj() { +>createObj : Symbol(createObj, Decl(noImplicitThisBigThis.ts, 0, 0)) + + return { + func1() { +>func1 : Symbol(func1, Decl(noImplicitThisBigThis.ts, 3, 12)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 3, 10)) + + }, + func2() { +>func2 : Symbol(func2, Decl(noImplicitThisBigThis.ts, 6, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 3, 10)) + + }, + func3() { +>func3 : Symbol(func3, Decl(noImplicitThisBigThis.ts, 9, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 3, 10)) + } + }; +} + +function createObjNoCrash() { +>createObjNoCrash : Symbol(createObjNoCrash, Decl(noImplicitThisBigThis.ts, 14, 1)) + + return { + func1() { +>func1 : Symbol(func1, Decl(noImplicitThisBigThis.ts, 17, 12)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func2() { +>func2 : Symbol(func2, Decl(noImplicitThisBigThis.ts, 20, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func3() { +>func3 : Symbol(func3, Decl(noImplicitThisBigThis.ts, 23, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func4() { +>func4 : Symbol(func4, Decl(noImplicitThisBigThis.ts, 26, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func5() { +>func5 : Symbol(func5, Decl(noImplicitThisBigThis.ts, 29, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func6() { +>func6 : Symbol(func6, Decl(noImplicitThisBigThis.ts, 32, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func7() { +>func7 : Symbol(func7, Decl(noImplicitThisBigThis.ts, 35, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func8() { +>func8 : Symbol(func8, Decl(noImplicitThisBigThis.ts, 38, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + + }, + func9() { +>func9 : Symbol(func9, Decl(noImplicitThisBigThis.ts, 41, 10)) + + return this; +>this : Symbol(__object, Decl(noImplicitThisBigThis.ts, 17, 10)) + } + }; +} + diff --git a/tests/baselines/reference/noImplicitThisBigThis.types b/tests/baselines/reference/noImplicitThisBigThis.types new file mode 100644 index 00000000000..4e1731aebc7 --- /dev/null +++ b/tests/baselines/reference/noImplicitThisBigThis.types @@ -0,0 +1,103 @@ +=== tests/cases/compiler/noImplicitThisBigThis.ts === +// https://github.com/microsoft/TypeScript/issues/29902 + +function createObj() { +>createObj : () => { func1(): any; func2(): any; func3(): any; } + + return { +>{ func1() { return this; }, func2() { return this; }, func3() { return this; } } : { func1(): { func1(): any; func2(): any; func3(): any; }; func2(): { func1(): any; func2(): any; func3(): any; }; func3(): { func1(): any; func2(): any; func3(): any; }; } + + func1() { +>func1 : () => { func1(): any; func2(): any; func3(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; }; func2(): { func1(): any; func2(): any; func3(): any; }; func3(): { func1(): any; func2(): any; func3(): any; }; } + + }, + func2() { +>func2 : () => { func1(): any; func2(): any; func3(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; }; func2(): { func1(): any; func2(): any; func3(): any; }; func3(): { func1(): any; func2(): any; func3(): any; }; } + + }, + func3() { +>func3 : () => { func1(): any; func2(): any; func3(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; }; func2(): { func1(): any; func2(): any; func3(): any; }; func3(): { func1(): any; func2(): any; func3(): any; }; } + } + }; +} + +function createObjNoCrash() { +>createObjNoCrash : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return { +>{ func1() { return this; }, func2() { return this; }, func3() { return this; }, func4() { return this; }, func5() { return this; }, func6() { return this; }, func7() { return this; }, func8() { return this; }, func9() { return this; } } : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + func1() { +>func1 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func2() { +>func2 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func3() { +>func3 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func4() { +>func4 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func5() { +>func5 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func6() { +>func6 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func7() { +>func7 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func8() { +>func8 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + + }, + func9() { +>func9 : () => { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; } + + return this; +>this : { func1(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func2(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func3(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func4(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func5(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func6(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func7(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func8(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; func9(): { func1(): any; func2(): any; func3(): any; func4(): any; func5(): any; func6(): any; func7(): any; func8(): any; func9(): any; }; } + } + }; +} + diff --git a/tests/cases/compiler/noImplicitThisBigThis.ts b/tests/cases/compiler/noImplicitThisBigThis.ts new file mode 100644 index 00000000000..687f2f9355d --- /dev/null +++ b/tests/cases/compiler/noImplicitThisBigThis.ts @@ -0,0 +1,50 @@ +// @declaration: true +// @noImplicitThis: true + +// https://github.com/microsoft/TypeScript/issues/29902 + +function createObj() { + return { + func1() { + return this; + }, + func2() { + return this; + }, + func3() { + return this; + } + }; +} + +function createObjNoCrash() { + return { + func1() { + return this; + }, + func2() { + return this; + }, + func3() { + return this; + }, + func4() { + return this; + }, + func5() { + return this; + }, + func6() { + return this; + }, + func7() { + return this; + }, + func8() { + return this; + }, + func9() { + return this; + } + }; +} From b75a90e95ab619a8874737a1bce136284b963e11 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 28 May 2019 16:32:10 -0700 Subject: [PATCH 062/111] Return type inference should not include parameter inferences --- src/compiler/checker.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5091f5f0306..e83137b87e8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14864,13 +14864,6 @@ namespace ts { return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); } - function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { - const inferences = filter(context.inferences, hasInferenceCandidates); - return inferences.length ? - createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : - undefined; - } - function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { const context: InferenceContext = { inferences, @@ -20846,7 +20839,8 @@ namespace ts { // We clone the inference context to avoid disturbing a resolution in progress for an // outer call expression. Effectively we just want a snapshot of whatever has been // inferred for any outer call expression so far. - const outerMapper = getMapperFromContext(cloneInferenceContext(getInferenceContext(node), InferenceFlags.NoDefault)); + const outerContext = getInferenceContext(node); + const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); const instantiatedType = instantiateType(contextualType, outerMapper); // If the contextual type is a generic function type with a single call signature, we // instantiate the type with its own type parameters and type arguments. This ensures that @@ -20864,7 +20858,10 @@ namespace ts { inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); // Create a type mapper for instantiating generic contextual types using the inferences made // from the return type. - context.returnMapper = getMapperFromContext(cloneInferredPartOfContext(context)); + const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); + const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); + inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); + context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(returnContext) : undefined; } } From 94f19c7edd741ea7fe985c57541dcf233677d22b Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 29 May 2019 08:59:44 -0700 Subject: [PATCH 063/111] Update version to 3.6.0. --- package.json | 2 +- src/compiler/core.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a50a79aff13..ae67f3166b5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "https://www.typescriptlang.org/", - "version": "3.5.0", + "version": "3.6.0", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0034c275999..f1511b79c12 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,7 +1,7 @@ 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.5"; + export const versionMajorMinor = "3.6"; /** The version of the TypeScript compiler release */ export const version = `${versionMajorMinor}.0-dev`; } From c5c869f673044d7e0daa27e194103409251d89dd Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 29 May 2019 10:20:49 -0700 Subject: [PATCH 064/111] Accepted baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 993a7494994..e940c5055ff 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -14,7 +14,7 @@ and limitations under the License. ***************************************************************************** */ declare namespace ts { - const versionMajorMinor = "3.5"; + const versionMajorMinor = "3.6"; /** The version of the TypeScript compiler release */ const version: string; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 58ccd42344b..d67a1638a5e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -14,7 +14,7 @@ and limitations under the License. ***************************************************************************** */ declare namespace ts { - const versionMajorMinor = "3.5"; + const versionMajorMinor = "3.6"; /** The version of the TypeScript compiler release */ const version: string; } From 08cd0b3700cbe62a170d9c5693170fc0210d598a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 May 2019 12:42:43 -0700 Subject: [PATCH 065/111] Use proper variances when inferring between type alias instantiations --- src/compiler/checker.ts | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 85b3641f004..99ae67d764f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15171,11 +15171,7 @@ namespace ts { if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { // Source and target are types originating in the same generic type alias declaration. // Simply infer from source type arguments to target type arguments. - const sourceTypes = source.aliasTypeArguments; - const targetTypes = target.aliasTypeArguments!; - for (let i = 0; i < sourceTypes.length; i++) { - inferFromTypes(sourceTypes[i], targetTypes[i]); - } + inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); return; } if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && !(source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.EnumLiteral) || @@ -15281,18 +15277,7 @@ namespace ts { } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { // If source and target are references to the same generic type, infer from type arguments - const sourceTypes = (source).typeArguments || emptyArray; - const targetTypes = (target).typeArguments || emptyArray; - const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; - const variances = getVariances((source).target); - for (let i = 0; i < count; i++) { - if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { - inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); - } - else { - inferFromTypes(sourceTypes[i], targetTypes[i]); - } - } + inferFromTypeArguments((source).typeArguments || emptyArray, (target).typeArguments || emptyArray, getVariances((source).target)); } else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { contravariant = !contravariant; @@ -15412,6 +15397,18 @@ namespace ts { } } + function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { + const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + for (let i = 0; i < count; i++) { + if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); + } + else { + inferFromTypes(sourceTypes[i], targetTypes[i]); + } + } + } + function inferFromContravariantTypes(source: Type, target: Type) { if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { contravariant = !contravariant; From 22475e8958ce2892c5311c3249165548606b10e8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 May 2019 13:09:51 -0700 Subject: [PATCH 066/111] Add regression tests --- .../contravariantTypeAliasInference.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cases/compiler/contravariantTypeAliasInference.ts diff --git a/tests/cases/compiler/contravariantTypeAliasInference.ts b/tests/cases/compiler/contravariantTypeAliasInference.ts new file mode 100644 index 00000000000..77044eeae76 --- /dev/null +++ b/tests/cases/compiler/contravariantTypeAliasInference.ts @@ -0,0 +1,19 @@ +// @strict: true + +type Func1 = (x: T) => void; +type Func2 = ((x: T) => void) | undefined; + +declare let f1: Func1; +declare let f2: Func1<"a">; + +declare function foo(f1: Func1, f2: Func1): void; + +foo(f1, f2); + +declare let g1: Func2; +declare let g2: Func2<"a">; + +declare function bar(g1: Func2, g2: Func2): void; + +bar(f1, f2); +bar(g1, g2); From bb412ab73b1cbc9eaa38145bc733b3325c55f30d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 29 May 2019 13:10:01 -0700 Subject: [PATCH 067/111] Accept new baselines --- .../contravariantTypeAliasInference.js | 25 ++++++++ .../contravariantTypeAliasInference.symbols | 64 +++++++++++++++++++ .../contravariantTypeAliasInference.types | 49 ++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 tests/baselines/reference/contravariantTypeAliasInference.js create mode 100644 tests/baselines/reference/contravariantTypeAliasInference.symbols create mode 100644 tests/baselines/reference/contravariantTypeAliasInference.types diff --git a/tests/baselines/reference/contravariantTypeAliasInference.js b/tests/baselines/reference/contravariantTypeAliasInference.js new file mode 100644 index 00000000000..a0a7b45db91 --- /dev/null +++ b/tests/baselines/reference/contravariantTypeAliasInference.js @@ -0,0 +1,25 @@ +//// [contravariantTypeAliasInference.ts] +type Func1 = (x: T) => void; +type Func2 = ((x: T) => void) | undefined; + +declare let f1: Func1; +declare let f2: Func1<"a">; + +declare function foo(f1: Func1, f2: Func1): void; + +foo(f1, f2); + +declare let g1: Func2; +declare let g2: Func2<"a">; + +declare function bar(g1: Func2, g2: Func2): void; + +bar(f1, f2); +bar(g1, g2); + + +//// [contravariantTypeAliasInference.js] +"use strict"; +foo(f1, f2); +bar(f1, f2); +bar(g1, g2); diff --git a/tests/baselines/reference/contravariantTypeAliasInference.symbols b/tests/baselines/reference/contravariantTypeAliasInference.symbols new file mode 100644 index 00000000000..ff34d3ea17e --- /dev/null +++ b/tests/baselines/reference/contravariantTypeAliasInference.symbols @@ -0,0 +1,64 @@ +=== tests/cases/compiler/contravariantTypeAliasInference.ts === +type Func1 = (x: T) => void; +>Func1 : Symbol(Func1, Decl(contravariantTypeAliasInference.ts, 0, 0)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 0, 11)) +>x : Symbol(x, Decl(contravariantTypeAliasInference.ts, 0, 17)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 0, 11)) + +type Func2 = ((x: T) => void) | undefined; +>Func2 : Symbol(Func2, Decl(contravariantTypeAliasInference.ts, 0, 31)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 1, 11)) +>x : Symbol(x, Decl(contravariantTypeAliasInference.ts, 1, 18)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 1, 11)) + +declare let f1: Func1; +>f1 : Symbol(f1, Decl(contravariantTypeAliasInference.ts, 3, 11)) +>Func1 : Symbol(Func1, Decl(contravariantTypeAliasInference.ts, 0, 0)) + +declare let f2: Func1<"a">; +>f2 : Symbol(f2, Decl(contravariantTypeAliasInference.ts, 4, 11)) +>Func1 : Symbol(Func1, Decl(contravariantTypeAliasInference.ts, 0, 0)) + +declare function foo(f1: Func1, f2: Func1): void; +>foo : Symbol(foo, Decl(contravariantTypeAliasInference.ts, 4, 27)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 6, 21)) +>f1 : Symbol(f1, Decl(contravariantTypeAliasInference.ts, 6, 24)) +>Func1 : Symbol(Func1, Decl(contravariantTypeAliasInference.ts, 0, 0)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 6, 21)) +>f2 : Symbol(f2, Decl(contravariantTypeAliasInference.ts, 6, 37)) +>Func1 : Symbol(Func1, Decl(contravariantTypeAliasInference.ts, 0, 0)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 6, 21)) + +foo(f1, f2); +>foo : Symbol(foo, Decl(contravariantTypeAliasInference.ts, 4, 27)) +>f1 : Symbol(f1, Decl(contravariantTypeAliasInference.ts, 3, 11)) +>f2 : Symbol(f2, Decl(contravariantTypeAliasInference.ts, 4, 11)) + +declare let g1: Func2; +>g1 : Symbol(g1, Decl(contravariantTypeAliasInference.ts, 10, 11)) +>Func2 : Symbol(Func2, Decl(contravariantTypeAliasInference.ts, 0, 31)) + +declare let g2: Func2<"a">; +>g2 : Symbol(g2, Decl(contravariantTypeAliasInference.ts, 11, 11)) +>Func2 : Symbol(Func2, Decl(contravariantTypeAliasInference.ts, 0, 31)) + +declare function bar(g1: Func2, g2: Func2): void; +>bar : Symbol(bar, Decl(contravariantTypeAliasInference.ts, 11, 27)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 13, 21)) +>g1 : Symbol(g1, Decl(contravariantTypeAliasInference.ts, 13, 24)) +>Func2 : Symbol(Func2, Decl(contravariantTypeAliasInference.ts, 0, 31)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 13, 21)) +>g2 : Symbol(g2, Decl(contravariantTypeAliasInference.ts, 13, 37)) +>Func2 : Symbol(Func2, Decl(contravariantTypeAliasInference.ts, 0, 31)) +>T : Symbol(T, Decl(contravariantTypeAliasInference.ts, 13, 21)) + +bar(f1, f2); +>bar : Symbol(bar, Decl(contravariantTypeAliasInference.ts, 11, 27)) +>f1 : Symbol(f1, Decl(contravariantTypeAliasInference.ts, 3, 11)) +>f2 : Symbol(f2, Decl(contravariantTypeAliasInference.ts, 4, 11)) + +bar(g1, g2); +>bar : Symbol(bar, Decl(contravariantTypeAliasInference.ts, 11, 27)) +>g1 : Symbol(g1, Decl(contravariantTypeAliasInference.ts, 10, 11)) +>g2 : Symbol(g2, Decl(contravariantTypeAliasInference.ts, 11, 11)) + diff --git a/tests/baselines/reference/contravariantTypeAliasInference.types b/tests/baselines/reference/contravariantTypeAliasInference.types new file mode 100644 index 00000000000..9ff197cdfce --- /dev/null +++ b/tests/baselines/reference/contravariantTypeAliasInference.types @@ -0,0 +1,49 @@ +=== tests/cases/compiler/contravariantTypeAliasInference.ts === +type Func1 = (x: T) => void; +>Func1 : Func1 +>x : T + +type Func2 = ((x: T) => void) | undefined; +>Func2 : Func2 +>x : T + +declare let f1: Func1; +>f1 : Func1 + +declare let f2: Func1<"a">; +>f2 : Func1<"a"> + +declare function foo(f1: Func1, f2: Func1): void; +>foo : (f1: Func1, f2: Func1) => void +>f1 : Func1 +>f2 : Func1 + +foo(f1, f2); +>foo(f1, f2) : void +>foo : (f1: Func1, f2: Func1) => void +>f1 : Func1 +>f2 : Func1<"a"> + +declare let g1: Func2; +>g1 : Func2 + +declare let g2: Func2<"a">; +>g2 : Func2<"a"> + +declare function bar(g1: Func2, g2: Func2): void; +>bar : (g1: Func2, g2: Func2) => void +>g1 : Func2 +>g2 : Func2 + +bar(f1, f2); +>bar(f1, f2) : void +>bar : (g1: Func2, g2: Func2) => void +>f1 : Func1 +>f2 : Func1<"a"> + +bar(g1, g2); +>bar(g1, g2) : void +>bar : (g1: Func2, g2: Func2) => void +>g1 : Func2 +>g2 : Func2<"a"> + From 953153e565caa9eaad5572d1eac7cc7c997e8464 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Thu, 30 May 2019 11:12:25 -0700 Subject: [PATCH 068/111] Update user baselines (#31674) --- .../reference/user/chrome-devtools-frontend.log | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/baselines/reference/user/chrome-devtools-frontend.log b/tests/baselines/reference/user/chrome-devtools-frontend.log index d32422fb416..6b6b46ab180 100644 --- a/tests/baselines/reference/user/chrome-devtools-frontend.log +++ b/tests/baselines/reference/user/chrome-devtools-frontend.log @@ -3122,9 +3122,6 @@ node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(53 node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(655,51): error TS2339: Property 'valuesArray' does not exist on type 'Map'. node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(667,51): error TS2339: Property 'valuesArray' does not exist on type 'Map'. node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(674,79): error TS2345: Argument of type 'this' is not assignable to parameter of type '{ modelAdded(model: T): void; modelRemoved(model: T): void; }'. - Type 'Breakpoint' is not assignable to type '{ modelAdded(model: T): void; modelRemoved(model: T): void; }'. - Types of property 'modelAdded' are incompatible. - Type '(debuggerModel: DebuggerModel) => void' is not assignable to type '(model: T) => void'. node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(713,51): error TS2339: Property 'valuesArray' does not exist on type 'Map'. node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(787,14): error TS7014: Function type, which lacks return-type annotation, implicitly has an 'any' return type. node_modules/chrome-devtools-frontend/front_end/bindings/BreakpointManager.js(862,14): error TS7014: Function type, which lacks return-type annotation, implicitly has an 'any' return type. @@ -6621,8 +6618,6 @@ node_modules/chrome-devtools-frontend/front_end/heap_snapshot_worker/HeapSnapsho Type 'HeapSnapshotNode' is not assignable to type '{ itemIndex(): number; serialize(): any; }'. Property '_snapshot' does not exist on type '{ itemIndex(): number; serialize(): any; }'. node_modules/chrome-devtools-frontend/front_end/heap_snapshot_worker/HeapSnapshot.js(2341,15): error TS2345: Argument of type 'HeapSnapshotNodeIndexProvider' is not assignable to parameter of type '{ itemForIndex(newIndex: number): { itemIndex(): number; serialize(): any; }; }'. - Types of property 'itemForIndex' are incompatible. - Type '(index: number) => HeapSnapshotNode' is not assignable to type '(newIndex: number) => { itemIndex(): number; serialize(): any; }'. node_modules/chrome-devtools-frontend/front_end/heap_snapshot_worker/HeapSnapshot.js(2353,12): error TS2339: Property 'nodeIndex' does not exist on type 'void'. node_modules/chrome-devtools-frontend/front_end/heap_snapshot_worker/HeapSnapshot.js(2354,16): error TS2339: Property 'id' does not exist on type 'void'. node_modules/chrome-devtools-frontend/front_end/heap_snapshot_worker/HeapSnapshot.js(2397,13): error TS2339: Property 'nodeIndex' does not exist on type 'void'. @@ -7707,9 +7702,6 @@ node_modules/chrome-devtools-frontend/front_end/network_log/NetworkLog.js(44,57) Type 'T' is not assignable to type 'NetworkManager'. node_modules/chrome-devtools-frontend/front_end/network_log/NetworkLog.js(99,59): error TS2345: Argument of type 'this' is not assignable to parameter of type '{ modelAdded(model: T): void; modelRemoved(model: T): void; }'. node_modules/chrome-devtools-frontend/front_end/network_log/NetworkLog.js(101,61): error TS2345: Argument of type 'this' is not assignable to parameter of type '{ modelAdded(model: T): void; modelRemoved(model: T): void; }'. - Type 'NetworkLog' is not assignable to type '{ modelAdded(model: T): void; modelRemoved(model: T): void; }'. - Types of property 'modelAdded' are incompatible. - Type '(networkManager: NetworkManager) => void' is not assignable to type '(model: T) => void'. node_modules/chrome-devtools-frontend/front_end/network_log/NetworkLog.js(123,24): error TS2694: Namespace 'Protocol' has no exported member 'Network'. node_modules/chrome-devtools-frontend/front_end/network_log/NetworkLog.js(154,49): error TS2694: Namespace 'NetworkLog.NetworkLog' has no exported member '_InitiatorInfo'. node_modules/chrome-devtools-frontend/front_end/network_log/NetworkLog.js(165,38): error TS2694: Namespace 'NetworkLog.NetworkLog' has no exported member '_InitiatorInfo'. @@ -10835,7 +10827,7 @@ node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(45,10 node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(46,24): error TS2555: Expected at least 2 arguments, but got 1. node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(54,43): error TS2555: Expected at least 2 arguments, but got 1. node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(69,55): error TS2339: Property 'runtime' does not exist on type 'Window'. -node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(85,5): error TS2739: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is missing the following properties from type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...': appendApplicableItems, appendView, showView, removeView, widget +node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(85,5): error TS2322: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is not assignable to type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...'. node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(100,15): error TS2339: Property 'keyCode' does not exist on type 'Event'. node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(119,31): error TS2339: Property 'createChild' does not exist on type 'Element'. node_modules/chrome-devtools-frontend/front_end/settings/SettingsScreen.js(121,42): error TS2339: Property 'createChild' does not exist on type 'Element'. @@ -11362,7 +11354,7 @@ node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(133,55): node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(208,22): error TS2694: Namespace 'Common' has no exported member 'Event'. node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(227,52): error TS2339: Property '_instance' does not exist on type 'typeof WrapperView'. node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(242,40): error TS2339: Property '_instance' does not exist on type 'typeof WrapperView'. -node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(257,7): error TS2739: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is missing the following properties from type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...': appendApplicableItems, appendView, showView, removeView, widget +node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(257,7): error TS2322: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is not assignable to type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...'. node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(277,20): error TS2339: Property 'window' does not exist on type 'Element'. node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(289,22): error TS2694: Namespace 'Common' has no exported member 'Event'. node_modules/chrome-devtools-frontend/front_end/sources/SourcesPanel.js(330,22): error TS2694: Namespace 'Common' has no exported member 'Event'. @@ -12884,8 +12876,8 @@ node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(53,50): erro node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(69,40): error TS2555: Expected at least 2 arguments, but got 1. node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(80,24): error TS2694: Namespace 'Common' has no exported member 'Event'. node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(92,51): error TS2339: Property 'runtime' does not exist on type 'Window'. -node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(116,7): error TS2739: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is missing the following properties from type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...': appendApplicableItems, appendView, showView, removeView, widget -node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(118,7): error TS2739: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is missing the following properties from type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...': appendApplicableItems, appendView, showView, removeView, widget +node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(116,7): error TS2322: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is not assignable to type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...'. +node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(118,7): error TS2322: Type '{ tabbedPane(): TabbedPane; enableMoreTabsButton(): void; }' is not assignable to type '{ appendApplicableItems(locationName: string): void; appendView(view: { viewId(): string; title(): string; isCloseable(): boolean; isTransient(): boolean; toolbarItems(): Promise; widget(): Promise<...>; disposeView(): void; }, insertBefore?: { ...; }): void; showView(view: { ...; }, insertBefore?: { ...'. node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(247,73): error TS2339: Property 'altKey' does not exist on type 'Event'. node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(247,89): error TS2339: Property 'shiftKey' does not exist on type 'Event'. node_modules/chrome-devtools-frontend/front_end/ui/InspectorView.js(254,17): error TS2339: Property 'keyCode' does not exist on type 'Event'. From 2b36fdd08b9a2618ad8531904d9c19ecbb503a2c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 30 May 2019 14:40:03 -0700 Subject: [PATCH 069/111] Add regression tests --- .../inferFromGenericFunctionReturnTypes3.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts b/tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts index 429d98b6a39..d14111c7a7a 100644 --- a/tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts +++ b/tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts @@ -182,3 +182,22 @@ enum State { A, B } type Foo = { state: State } declare function bar(f: () => T[]): T[]; let x: Foo[] = bar(() => !!true ? [{ state: State.A }] : [{ state: State.B }]); // Error + +// Repros from #31443 + +enum Enum { A, B } + +class ClassWithConvert { + constructor(val: T) { } + convert(converter: { to: (v: T) => T; }) { } +} + +function fn(arg: ClassWithConvert, f: () => ClassWithConvert) { } +fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)); + +type Func = (x: T) => T; + +declare function makeFoo(x: T): Func; +declare function baz(x: Func, y: Func): void; + +baz(makeFoo(Enum.A), makeFoo(Enum.A)); From c3ef035b022b64c692aa9b3c8e691a269a68fc32 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 30 May 2019 14:40:09 -0700 Subject: [PATCH 070/111] Accept new baselines --- ...FromGenericFunctionReturnTypes3.errors.txt | 19 +++++ .../inferFromGenericFunctionReturnTypes3.js | 32 ++++++++ ...ferFromGenericFunctionReturnTypes3.symbols | 81 +++++++++++++++++++ ...inferFromGenericFunctionReturnTypes3.types | 67 +++++++++++++++ 4 files changed, 199 insertions(+) diff --git a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.errors.txt b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.errors.txt index 7c79c63f78a..aea21ac2504 100644 --- a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.errors.txt +++ b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.errors.txt @@ -200,4 +200,23 @@ tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts(180,26): error TS23 !!! error TS2322: Types of property 'state' are incompatible. !!! error TS2322: Type 'State.B' is not assignable to type 'State.A'. !!! related TS6502 tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts:179:28: The expected type comes from the return type of this signature. + + // Repros from #31443 + + enum Enum { A, B } + + class ClassWithConvert { + constructor(val: T) { } + convert(converter: { to: (v: T) => T; }) { } + } + + function fn(arg: ClassWithConvert, f: () => ClassWithConvert) { } + fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)); + + type Func = (x: T) => T; + + declare function makeFoo(x: T): Func; + declare function baz(x: Func, y: Func): void; + + baz(makeFoo(Enum.A), makeFoo(Enum.A)); \ No newline at end of file diff --git a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.js b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.js index 99070e6ddcb..b6a2e890b11 100644 --- a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.js +++ b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.js @@ -179,6 +179,25 @@ enum State { A, B } type Foo = { state: State } declare function bar(f: () => T[]): T[]; let x: Foo[] = bar(() => !!true ? [{ state: State.A }] : [{ state: State.B }]); // Error + +// Repros from #31443 + +enum Enum { A, B } + +class ClassWithConvert { + constructor(val: T) { } + convert(converter: { to: (v: T) => T; }) { } +} + +function fn(arg: ClassWithConvert, f: () => ClassWithConvert) { } +fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)); + +type Func = (x: T) => T; + +declare function makeFoo(x: T): Func; +declare function baz(x: Func, y: Func): void; + +baz(makeFoo(Enum.A), makeFoo(Enum.A)); //// [inferFromGenericFunctionReturnTypes3.js] @@ -278,6 +297,19 @@ var State; State[State["B"] = 1] = "B"; })(State || (State = {})); let x = bar(() => !!true ? [{ state: State.A }] : [{ state: State.B }]); // Error +// Repros from #31443 +var Enum; +(function (Enum) { + Enum[Enum["A"] = 0] = "A"; + Enum[Enum["B"] = 1] = "B"; +})(Enum || (Enum = {})); +class ClassWithConvert { + constructor(val) { } + convert(converter) { } +} +function fn(arg, f) { } +fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)); +baz(makeFoo(Enum.A), makeFoo(Enum.A)); //// [inferFromGenericFunctionReturnTypes3.d.ts] diff --git a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.symbols b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.symbols index f89421cde07..ecf867a2bf7 100644 --- a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.symbols +++ b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.symbols @@ -459,3 +459,84 @@ let x: Foo[] = bar(() => !!true ? [{ state: State.A }] : [{ state: State.B }]); >State : Symbol(State, Decl(inferFromGenericFunctionReturnTypes3.ts, 174, 56)) >B : Symbol(State.B, Decl(inferFromGenericFunctionReturnTypes3.ts, 176, 15)) +// Repros from #31443 + +enum Enum { A, B } +>Enum : Symbol(Enum, Decl(inferFromGenericFunctionReturnTypes3.ts, 179, 79)) +>A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>B : Symbol(Enum.B, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 14)) + +class ClassWithConvert { +>ClassWithConvert : Symbol(ClassWithConvert, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 18)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 185, 23)) + + constructor(val: T) { } +>val : Symbol(val, Decl(inferFromGenericFunctionReturnTypes3.ts, 186, 14)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 185, 23)) + + convert(converter: { to: (v: T) => T; }) { } +>convert : Symbol(ClassWithConvert.convert, Decl(inferFromGenericFunctionReturnTypes3.ts, 186, 25)) +>converter : Symbol(converter, Decl(inferFromGenericFunctionReturnTypes3.ts, 187, 10)) +>to : Symbol(to, Decl(inferFromGenericFunctionReturnTypes3.ts, 187, 22)) +>v : Symbol(v, Decl(inferFromGenericFunctionReturnTypes3.ts, 187, 28)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 185, 23)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 185, 23)) +} + +function fn(arg: ClassWithConvert, f: () => ClassWithConvert) { } +>fn : Symbol(fn, Decl(inferFromGenericFunctionReturnTypes3.ts, 188, 1)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 190, 12)) +>arg : Symbol(arg, Decl(inferFromGenericFunctionReturnTypes3.ts, 190, 15)) +>ClassWithConvert : Symbol(ClassWithConvert, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 18)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 190, 12)) +>f : Symbol(f, Decl(inferFromGenericFunctionReturnTypes3.ts, 190, 40)) +>ClassWithConvert : Symbol(ClassWithConvert, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 18)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 190, 12)) + +fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)); +>fn : Symbol(fn, Decl(inferFromGenericFunctionReturnTypes3.ts, 188, 1)) +>ClassWithConvert : Symbol(ClassWithConvert, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 18)) +>Enum.A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>Enum : Symbol(Enum, Decl(inferFromGenericFunctionReturnTypes3.ts, 179, 79)) +>A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>ClassWithConvert : Symbol(ClassWithConvert, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 18)) +>Enum.A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>Enum : Symbol(Enum, Decl(inferFromGenericFunctionReturnTypes3.ts, 179, 79)) +>A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) + +type Func = (x: T) => T; +>Func : Symbol(Func, Decl(inferFromGenericFunctionReturnTypes3.ts, 191, 69)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 10)) +>x : Symbol(x, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 16)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 10)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 10)) + +declare function makeFoo(x: T): Func; +>makeFoo : Symbol(makeFoo, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 27)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 195, 25)) +>x : Symbol(x, Decl(inferFromGenericFunctionReturnTypes3.ts, 195, 28)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 195, 25)) +>Func : Symbol(Func, Decl(inferFromGenericFunctionReturnTypes3.ts, 191, 69)) +>T : Symbol(T, Decl(inferFromGenericFunctionReturnTypes3.ts, 195, 25)) + +declare function baz(x: Func, y: Func): void; +>baz : Symbol(baz, Decl(inferFromGenericFunctionReturnTypes3.ts, 195, 43)) +>U : Symbol(U, Decl(inferFromGenericFunctionReturnTypes3.ts, 196, 21)) +>x : Symbol(x, Decl(inferFromGenericFunctionReturnTypes3.ts, 196, 24)) +>Func : Symbol(Func, Decl(inferFromGenericFunctionReturnTypes3.ts, 191, 69)) +>U : Symbol(U, Decl(inferFromGenericFunctionReturnTypes3.ts, 196, 21)) +>y : Symbol(y, Decl(inferFromGenericFunctionReturnTypes3.ts, 196, 35)) +>Func : Symbol(Func, Decl(inferFromGenericFunctionReturnTypes3.ts, 191, 69)) +>U : Symbol(U, Decl(inferFromGenericFunctionReturnTypes3.ts, 196, 21)) + +baz(makeFoo(Enum.A), makeFoo(Enum.A)); +>baz : Symbol(baz, Decl(inferFromGenericFunctionReturnTypes3.ts, 195, 43)) +>makeFoo : Symbol(makeFoo, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 27)) +>Enum.A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>Enum : Symbol(Enum, Decl(inferFromGenericFunctionReturnTypes3.ts, 179, 79)) +>A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>makeFoo : Symbol(makeFoo, Decl(inferFromGenericFunctionReturnTypes3.ts, 193, 27)) +>Enum.A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) +>Enum : Symbol(Enum, Decl(inferFromGenericFunctionReturnTypes3.ts, 179, 79)) +>A : Symbol(Enum.A, Decl(inferFromGenericFunctionReturnTypes3.ts, 183, 11)) + diff --git a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types index fd83694a171..4c56070fd3c 100644 --- a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types +++ b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types @@ -506,3 +506,70 @@ let x: Foo[] = bar(() => !!true ? [{ state: State.A }] : [{ state: State.B }]); >State : typeof State >B : State.B +// Repros from #31443 + +enum Enum { A, B } +>Enum : Enum +>A : Enum.A +>B : Enum.B + +class ClassWithConvert { +>ClassWithConvert : ClassWithConvert + + constructor(val: T) { } +>val : T + + convert(converter: { to: (v: T) => T; }) { } +>convert : (converter: { to: (v: T) => T; }) => void +>converter : { to: (v: T) => T; } +>to : (v: T) => T +>v : T +} + +function fn(arg: ClassWithConvert, f: () => ClassWithConvert) { } +>fn : (arg: ClassWithConvert, f: () => ClassWithConvert) => void +>arg : ClassWithConvert +>f : () => ClassWithConvert + +fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)); +>fn(new ClassWithConvert(Enum.A), () => new ClassWithConvert(Enum.A)) : void +>fn : (arg: ClassWithConvert, f: () => ClassWithConvert) => void +>new ClassWithConvert(Enum.A) : ClassWithConvert +>ClassWithConvert : typeof ClassWithConvert +>Enum.A : Enum.A +>Enum : typeof Enum +>A : Enum.A +>() => new ClassWithConvert(Enum.A) : () => ClassWithConvert +>new ClassWithConvert(Enum.A) : ClassWithConvert +>ClassWithConvert : typeof ClassWithConvert +>Enum.A : Enum.A +>Enum : typeof Enum +>A : Enum.A + +type Func = (x: T) => T; +>Func : Func +>x : T + +declare function makeFoo(x: T): Func; +>makeFoo : (x: T) => Func +>x : T + +declare function baz(x: Func, y: Func): void; +>baz : (x: Func, y: Func) => void +>x : Func +>y : Func + +baz(makeFoo(Enum.A), makeFoo(Enum.A)); +>baz(makeFoo(Enum.A), makeFoo(Enum.A)) : void +>baz : (x: Func, y: Func) => void +>makeFoo(Enum.A) : Func +>makeFoo : (x: T) => Func +>Enum.A : Enum.A +>Enum : typeof Enum +>A : Enum.A +>makeFoo(Enum.A) : Func +>makeFoo : (x: T) => Func +>Enum.A : Enum.A +>Enum : typeof Enum +>A : Enum.A + From 8c443b148179ebfe954944f8661731b401e388f9 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Thu, 30 May 2019 15:05:53 -0700 Subject: [PATCH 071/111] Stop invalidating resolution when file stays open --- src/compiler/resolutionCache.ts | 5 +++++ src/compiler/watch.ts | 1 + src/server/project.ts | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index b82a5a78d71..ff09efb05ea 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -54,6 +54,7 @@ namespace ts { writeLog(s: string): void; maxNumberOfFilesToIterateForInvalidation?: number; getCurrentProgram(): Program | undefined; + fileIsOpen(filePath: Path): boolean; } interface DirectoryWatchesOfFailedLookup { @@ -713,6 +714,10 @@ namespace ts { if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { return false; } + // prevent saving an open file from over-eagerly triggering invalidation + if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + return; + } // Ignore emits from the program if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { return false; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index f9bf2a8468d..0faf9f0c175 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -691,6 +691,7 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = true; scheduleProgramUpdate(); }; + compilerHost.fileIsOpen = () => false; compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation; compilerHost.getCurrentProgram = getCurrentProgram; compilerHost.writeLog = writeLog; diff --git a/src/server/project.ts b/src/server/project.ts index 20cd5667dd9..e10a0988f71 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -457,6 +457,11 @@ namespace ts.server { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; } + /*@internal*/ + fileIsOpen(filePath: Path) { + return this.projectService.openFiles.has(filePath); + } + /*@internal*/ writeLog(s: string) { this.projectService.logger.info(s); From 1fe9a0ad4ed30abe67418cb58f5dd06eb23391a6 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 30 May 2019 15:20:41 -0700 Subject: [PATCH 072/111] Small fix to user PR script (#31679) --- scripts/open-user-pr.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/open-user-pr.ts b/scripts/open-user-pr.ts index 0a636c267b8..23e4bb951d7 100644 --- a/scripts/open-user-pr.ts +++ b/scripts/open-user-pr.ts @@ -9,7 +9,7 @@ function padNum(number: number) { } const userName = process.env.GH_USERNAME; -const reviewers = process.env.requesting_user ? [process.env.requesting_user] : ["weswigham", "sandersn", "RyanCavanaugh"]; +const reviewers = process.env.REQUESTING_USER ? [process.env.REQUESTING_USER] : ["weswigham", "sandersn", "RyanCavanaugh"]; const now = new Date(); const branchName = `user-update-${process.env.TARGET_FORK}-${now.getFullYear()}${padNum(now.getMonth())}${padNum(now.getDay())}${process.env.TARGET_BRANCH ? "-" + process.env.TARGET_BRANCH : ""}`; const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`; @@ -36,14 +36,14 @@ gh.pulls.create({ head: `${userName}:${branchName}`, base: process.env.TARGET_BRANCH || "master", body: -`${process.env.source_issue ? `This test run was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.source_issue} `+"\n" : ""}Please review the diff and merge if no changes are unexpected. +`${process.env.SOURCE_ISSUE ? `This test run was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.SOURCE_ISSUE} `+"\n" : ""}Please review the diff and merge if no changes are unexpected. You can view the build log [here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary). cc ${reviewers.map(r => "@" + r).join(" ")}`, }).then(async r => { const num = r.data.number; console.log(`Pull request ${num} created.`); - if (!process.env.source_issue) { + if (!process.env.SOURCE_ISSUE) { await gh.pulls.createReviewRequest({ owner: process.env.TARGET_FORK, repo: "TypeScript", @@ -53,7 +53,7 @@ cc ${reviewers.map(r => "@" + r).join(" ")}`, } else { await gh.issues.createComment({ - number: +process.env.source_issue, + number: +process.env.SOURCE_ISSUE, owner: "Microsoft", repo: "TypeScript", body: `The user suite test run you requested has finished and _failed_. I've opened a [PR with the baseline diff from master](${r.data.html_url}).` From 08d8f97bb45697812df434411469c19c80e789ed Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 30 May 2019 16:06:49 -0700 Subject: [PATCH 073/111] Add comment --- src/compiler/checker.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e83137b87e8..4558a284f32 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20857,7 +20857,9 @@ namespace ts { // Inferences made from return types have lower priority than all other inferences. inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); // Create a type mapper for instantiating generic contextual types using the inferences made - // from the return type. + // from the return type. We need a separate inference pass here because (a) instantiation of + // the source type uses the outer context's return mapper (which excludes inferences made from + // outer arguments), and (b) we don't want any further inferences going into this context. const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); From a30cacb562bf5cf8cd7ae8ade77f72421dddc58a Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Thu, 30 May 2019 16:56:27 -0700 Subject: [PATCH 074/111] Add test --- .../unittests/tsserver/resolutionCache.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 33b59e2faae..11b604bb20c 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -975,5 +975,33 @@ export const x = 10;` host.checkTimeoutQueueLength(0); }); }); + + describe("avoid unnecessary invalidation", () => { + it("failed lookup invalidation", () => { + const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`]; + const module1Name = "module1"; + const module2Name = "module2"; + const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; + const file1: File = { + path: `${projectLocation}/src/file1.ts`, + content: fileContent + }; + const { module1, module2 } = getModules(`${projectLocation}/src/node_modules/module1/index.ts`, `${projectLocation}/node_modules/module2/index.ts`); + const files = [module1, module2, file1, configFile, libFile]; + const host = createServerHost(files); + const resolutionTrace = createHostModuleResolutionTrace(host); + const service = createProjectService(host); + service.openClientFile(file1.path); + const project = service.configuredProjects.get(configFile.path)!; + (project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1; + const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); + getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); + verifyTrace(resolutionTrace, expectedTrace); + verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + + host.invokeWatchedDirectoriesRecursiveCallback(projectLocation + "/src", "file1.ts"); + host.checkTimeoutQueueLengthAndRun(0); + }); + }); }); } From 6b92ccaffaa6ccd789b80f5e506c64cc9111a30d Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Thu, 30 May 2019 17:22:12 -0700 Subject: [PATCH 075/111] Respond to CR --- src/compiler/resolutionCache.ts | 2 +- src/compiler/watch.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index ff09efb05ea..6e0384219a1 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -716,7 +716,7 @@ namespace ts { } // prevent saving an open file from over-eagerly triggering invalidation if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { - return; + return false; } // Ignore emits from the program if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0faf9f0c175..dbc78fa5d4e 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -691,7 +691,7 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = true; scheduleProgramUpdate(); }; - compilerHost.fileIsOpen = () => false; + compilerHost.fileIsOpen = returnFalse; compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation; compilerHost.getCurrentProgram = getCurrentProgram; compilerHost.writeLog = writeLog; From cf1bceb9e4a90b2425ff46fee3a9b01c7fbb7c60 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 30 May 2019 17:35:10 -0700 Subject: [PATCH 076/111] Add tests --- tests/cases/compiler/implicitIndexSignatures.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/cases/compiler/implicitIndexSignatures.ts b/tests/cases/compiler/implicitIndexSignatures.ts index 2e36a91bada..2239ad8a8e2 100644 --- a/tests/cases/compiler/implicitIndexSignatures.ts +++ b/tests/cases/compiler/implicitIndexSignatures.ts @@ -43,3 +43,15 @@ function f4() { const v3 = getNumberIndexValue(o1); const v4 = getNumberIndexValue(o2); } + +function f5() { + enum E1 { A, B } + enum E2 { A = "A", B = "B" } + enum E3 { A = 0, B = "B" } + const v1 = getStringIndexValue(E1); + const v2 = getStringIndexValue(E2); + const v3 = getStringIndexValue(E3); + const v4 = getNumberIndexValue(E1); + const v5 = getNumberIndexValue(E2); + const v6 = getNumberIndexValue(E3); +} From 8bd6fd85db1b4952b7d43c7efadbc0f6278ad5ec Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 30 May 2019 17:35:17 -0700 Subject: [PATCH 077/111] Accept new baselines --- .../reference/implicitIndexSignatures.js | 35 +++++++++++ .../reference/implicitIndexSignatures.symbols | 49 +++++++++++++++ .../reference/implicitIndexSignatures.types | 59 +++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/tests/baselines/reference/implicitIndexSignatures.js b/tests/baselines/reference/implicitIndexSignatures.js index 6403f1f1543..a20c63979da 100644 --- a/tests/baselines/reference/implicitIndexSignatures.js +++ b/tests/baselines/reference/implicitIndexSignatures.js @@ -44,6 +44,18 @@ function f4() { const v3 = getNumberIndexValue(o1); const v4 = getNumberIndexValue(o2); } + +function f5() { + enum E1 { A, B } + enum E2 { A = "A", B = "B" } + enum E3 { A = 0, B = "B" } + const v1 = getStringIndexValue(E1); + const v2 = getStringIndexValue(E2); + const v3 = getStringIndexValue(E3); + const v4 = getNumberIndexValue(E1); + const v5 = getNumberIndexValue(E2); + const v6 = getNumberIndexValue(E3); +} //// [implicitIndexSignatures.js] @@ -83,3 +95,26 @@ function f4() { var v3 = getNumberIndexValue(o1); var v4 = getNumberIndexValue(o2); } +function f5() { + var E1; + (function (E1) { + E1[E1["A"] = 0] = "A"; + E1[E1["B"] = 1] = "B"; + })(E1 || (E1 = {})); + var E2; + (function (E2) { + E2["A"] = "A"; + E2["B"] = "B"; + })(E2 || (E2 = {})); + var E3; + (function (E3) { + E3[E3["A"] = 0] = "A"; + E3["B"] = "B"; + })(E3 || (E3 = {})); + var v1 = getStringIndexValue(E1); + var v2 = getStringIndexValue(E2); + var v3 = getStringIndexValue(E3); + var v4 = getNumberIndexValue(E1); + var v5 = getNumberIndexValue(E2); + var v6 = getNumberIndexValue(E3); +} diff --git a/tests/baselines/reference/implicitIndexSignatures.symbols b/tests/baselines/reference/implicitIndexSignatures.symbols index 1c8f06acc11..6f3e68f5058 100644 --- a/tests/baselines/reference/implicitIndexSignatures.symbols +++ b/tests/baselines/reference/implicitIndexSignatures.symbols @@ -168,3 +168,52 @@ function f4() { >o2 : Symbol(o2, Decl(implicitIndexSignatures.ts, 39, 7)) } +function f5() { +>f5 : Symbol(f5, Decl(implicitIndexSignatures.ts, 44, 1)) + + enum E1 { A, B } +>E1 : Symbol(E1, Decl(implicitIndexSignatures.ts, 46, 15)) +>A : Symbol(E1.A, Decl(implicitIndexSignatures.ts, 47, 13)) +>B : Symbol(E1.B, Decl(implicitIndexSignatures.ts, 47, 16)) + + enum E2 { A = "A", B = "B" } +>E2 : Symbol(E2, Decl(implicitIndexSignatures.ts, 47, 20)) +>A : Symbol(E2.A, Decl(implicitIndexSignatures.ts, 48, 13)) +>B : Symbol(E2.B, Decl(implicitIndexSignatures.ts, 48, 22)) + + enum E3 { A = 0, B = "B" } +>E3 : Symbol(E3, Decl(implicitIndexSignatures.ts, 48, 32)) +>A : Symbol(E3.A, Decl(implicitIndexSignatures.ts, 49, 13)) +>B : Symbol(E3.B, Decl(implicitIndexSignatures.ts, 49, 20)) + + const v1 = getStringIndexValue(E1); +>v1 : Symbol(v1, Decl(implicitIndexSignatures.ts, 50, 9)) +>getStringIndexValue : Symbol(getStringIndexValue, Decl(implicitIndexSignatures.ts, 11, 13)) +>E1 : Symbol(E1, Decl(implicitIndexSignatures.ts, 46, 15)) + + const v2 = getStringIndexValue(E2); +>v2 : Symbol(v2, Decl(implicitIndexSignatures.ts, 51, 9)) +>getStringIndexValue : Symbol(getStringIndexValue, Decl(implicitIndexSignatures.ts, 11, 13)) +>E2 : Symbol(E2, Decl(implicitIndexSignatures.ts, 47, 20)) + + const v3 = getStringIndexValue(E3); +>v3 : Symbol(v3, Decl(implicitIndexSignatures.ts, 52, 9)) +>getStringIndexValue : Symbol(getStringIndexValue, Decl(implicitIndexSignatures.ts, 11, 13)) +>E3 : Symbol(E3, Decl(implicitIndexSignatures.ts, 48, 32)) + + const v4 = getNumberIndexValue(E1); +>v4 : Symbol(v4, Decl(implicitIndexSignatures.ts, 53, 9)) +>getNumberIndexValue : Symbol(getNumberIndexValue, Decl(implicitIndexSignatures.ts, 13, 68)) +>E1 : Symbol(E1, Decl(implicitIndexSignatures.ts, 46, 15)) + + const v5 = getNumberIndexValue(E2); +>v5 : Symbol(v5, Decl(implicitIndexSignatures.ts, 54, 9)) +>getNumberIndexValue : Symbol(getNumberIndexValue, Decl(implicitIndexSignatures.ts, 13, 68)) +>E2 : Symbol(E2, Decl(implicitIndexSignatures.ts, 47, 20)) + + const v6 = getNumberIndexValue(E3); +>v6 : Symbol(v6, Decl(implicitIndexSignatures.ts, 55, 9)) +>getNumberIndexValue : Symbol(getNumberIndexValue, Decl(implicitIndexSignatures.ts, 13, 68)) +>E3 : Symbol(E3, Decl(implicitIndexSignatures.ts, 48, 32)) +} + diff --git a/tests/baselines/reference/implicitIndexSignatures.types b/tests/baselines/reference/implicitIndexSignatures.types index afad7bed570..c302edba32f 100644 --- a/tests/baselines/reference/implicitIndexSignatures.types +++ b/tests/baselines/reference/implicitIndexSignatures.types @@ -196,3 +196,62 @@ function f4() { >o2 : { 0: string; 1: string; count: number; } } +function f5() { +>f5 : () => void + + enum E1 { A, B } +>E1 : E1 +>A : E1.A +>B : E1.B + + enum E2 { A = "A", B = "B" } +>E2 : E2 +>A : E2.A +>"A" : "A" +>B : E2.B +>"B" : "B" + + enum E3 { A = 0, B = "B" } +>E3 : E3 +>A : E3.A +>0 : 0 +>B : E3.B +>"B" : "B" + + const v1 = getStringIndexValue(E1); +>v1 : string | E1 +>getStringIndexValue(E1) : string | E1 +>getStringIndexValue : (map: { [x: string]: T; }) => T +>E1 : typeof E1 + + const v2 = getStringIndexValue(E2); +>v2 : E2 +>getStringIndexValue(E2) : E2 +>getStringIndexValue : (map: { [x: string]: T; }) => T +>E2 : typeof E2 + + const v3 = getStringIndexValue(E3); +>v3 : string | E3.A +>getStringIndexValue(E3) : string | E3.A +>getStringIndexValue : (map: { [x: string]: T; }) => T +>E3 : typeof E3 + + const v4 = getNumberIndexValue(E1); +>v4 : string +>getNumberIndexValue(E1) : string +>getNumberIndexValue : (map: { [x: number]: T; }) => T +>E1 : typeof E1 + + const v5 = getNumberIndexValue(E2); +>v5 : unknown +>getNumberIndexValue(E2) : unknown +>getNumberIndexValue : (map: { [x: number]: T; }) => T +>E2 : typeof E2 + + const v6 = getNumberIndexValue(E3); +>v6 : string +>getNumberIndexValue(E3) : string +>getNumberIndexValue : (map: { [x: number]: T; }) => T +>E3 : typeof E3 +} + From bb15df3e435e14a85b4a6dc15c000f7eb6af50af Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 30 May 2019 21:06:51 -0700 Subject: [PATCH 078/111] Fix lint error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0fdb88110eb..8342dbc2291 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14600,7 +14600,7 @@ namespace ts { * with no call or construct signatures. */ function isObjectTypeWithInferableIndex(type: Type) { - return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum| SymbolFlags.ValueModule)) !== 0 && + return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && !typeHasCallOrConstructSignatures(type); } From aaa55923e8f8b03d39c6150ee7fdd9d0af522bec Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 11:03:49 -0700 Subject: [PATCH 079/111] Permit assignment this.xxx when class has index signature --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8305443a19b..5814b2dcfbf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20192,7 +20192,7 @@ namespace ts { markAliasReferenced(parentSymbol, node); } if (!prop) { - const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; + const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; if (!(indexInfo && indexInfo.type)) { if (isJSLiteralType(leftType)) { return anyType; From 59dc85797e22c49555b6bfdef8675c1d7b8c2206 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 11:04:02 -0700 Subject: [PATCH 080/111] Add regression test --- tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts index 898648cea44..b242aa3de60 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts @@ -139,11 +139,12 @@ function fn4() { let y: ReadonlyArray[K] = 'abc'; } -// Repro from #31439 +// Repro from #31439 and #31691 export class c { [x: string]: string; constructor() { + this.a = "b"; this["a"] = "b"; } } From d99b73c6ca8299d9afaa9db43e842da84ad70cd5 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 11:04:22 -0700 Subject: [PATCH 081/111] Accept new baselines --- .../keyofAndIndexedAccess2.errors.txt | 3 +- .../reference/keyofAndIndexedAccess2.js | 6 +- .../reference/keyofAndIndexedAccess2.symbols | 73 ++++++++++--------- .../reference/keyofAndIndexedAccess2.types | 9 ++- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt b/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt index 40ccd13dac4..fffeac41ff7 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccess2.errors.txt @@ -222,11 +222,12 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccess2.ts(108,5): error TS23 let y: ReadonlyArray[K] = 'abc'; } - // Repro from #31439 + // Repro from #31439 and #31691 export class c { [x: string]: string; constructor() { + this.a = "b"; this["a"] = "b"; } } diff --git a/tests/baselines/reference/keyofAndIndexedAccess2.js b/tests/baselines/reference/keyofAndIndexedAccess2.js index 7547a3f84c5..91cf2c89ff2 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess2.js +++ b/tests/baselines/reference/keyofAndIndexedAccess2.js @@ -137,11 +137,12 @@ function fn4() { let y: ReadonlyArray[K] = 'abc'; } -// Repro from #31439 +// Repro from #31439 and #31691 export class c { [x: string]: string; constructor() { + this.a = "b"; this["a"] = "b"; } } @@ -245,9 +246,10 @@ function fn4() { let x = 'abc'; let y = 'abc'; } -// Repro from #31439 +// Repro from #31439 and #31691 export class c { constructor() { + this.a = "b"; this["a"] = "b"; } } diff --git a/tests/baselines/reference/keyofAndIndexedAccess2.symbols b/tests/baselines/reference/keyofAndIndexedAccess2.symbols index 91602f93e1d..4a7721ff801 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess2.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccess2.symbols @@ -503,7 +503,7 @@ function fn4() { >K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 133, 13)) } -// Repro from #31439 +// Repro from #31439 and #31691 export class c { >c : Symbol(c, Decl(keyofAndIndexedAccess2.ts, 136, 1)) @@ -512,6 +512,9 @@ export class c { >x : Symbol(x, Decl(keyofAndIndexedAccess2.ts, 141, 3)) constructor() { + this.a = "b"; +>this : Symbol(c, Decl(keyofAndIndexedAccess2.ts, 136, 1)) + this["a"] = "b"; >this : Symbol(c, Decl(keyofAndIndexedAccess2.ts, 136, 1)) } @@ -520,44 +523,44 @@ export class c { // Repro from #31385 type Foo = { [key: string]: { [K in keyof T]: K }[keyof T] }; ->Foo : Symbol(Foo, Decl(keyofAndIndexedAccess2.ts, 145, 1)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 149, 9)) ->key : Symbol(key, Decl(keyofAndIndexedAccess2.ts, 149, 17)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 149, 34)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 149, 9)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 149, 34)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 149, 9)) +>Foo : Symbol(Foo, Decl(keyofAndIndexedAccess2.ts, 146, 1)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 150, 9)) +>key : Symbol(key, Decl(keyofAndIndexedAccess2.ts, 150, 17)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 150, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 150, 9)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 150, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 150, 9)) type Bar = { [key: string]: { [K in keyof T]: [K] }[keyof T] }; ->Bar : Symbol(Bar, Decl(keyofAndIndexedAccess2.ts, 149, 64)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 151, 9)) ->key : Symbol(key, Decl(keyofAndIndexedAccess2.ts, 151, 17)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 151, 34)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 151, 9)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 151, 34)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 151, 9)) +>Bar : Symbol(Bar, Decl(keyofAndIndexedAccess2.ts, 150, 64)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 152, 9)) +>key : Symbol(key, Decl(keyofAndIndexedAccess2.ts, 152, 17)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 152, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 152, 9)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 152, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 152, 9)) type Baz> = { [K in keyof Q]: T[Q[K]] }; ->Baz : Symbol(Baz, Decl(keyofAndIndexedAccess2.ts, 151, 66)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 153, 9)) ->Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 153, 11)) ->Foo : Symbol(Foo, Decl(keyofAndIndexedAccess2.ts, 145, 1)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 153, 9)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 153, 35)) ->Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 153, 11)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 153, 9)) ->Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 153, 11)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 153, 35)) +>Baz : Symbol(Baz, Decl(keyofAndIndexedAccess2.ts, 152, 66)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 154, 9)) +>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 154, 11)) +>Foo : Symbol(Foo, Decl(keyofAndIndexedAccess2.ts, 146, 1)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 154, 9)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 154, 35)) +>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 154, 11)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 154, 9)) +>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 154, 11)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 154, 35)) type Qux> = { [K in keyof Q]: T[Q[K]["0"]] }; ->Qux : Symbol(Qux, Decl(keyofAndIndexedAccess2.ts, 153, 60)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 155, 9)) ->Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 155, 11)) ->Bar : Symbol(Bar, Decl(keyofAndIndexedAccess2.ts, 149, 64)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 155, 9)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 155, 35)) ->Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 155, 11)) ->T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 155, 9)) ->Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 155, 11)) ->K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 155, 35)) +>Qux : Symbol(Qux, Decl(keyofAndIndexedAccess2.ts, 154, 60)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 156, 9)) +>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 156, 11)) +>Bar : Symbol(Bar, Decl(keyofAndIndexedAccess2.ts, 150, 64)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 156, 9)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 156, 35)) +>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 156, 11)) +>T : Symbol(T, Decl(keyofAndIndexedAccess2.ts, 156, 9)) +>Q : Symbol(Q, Decl(keyofAndIndexedAccess2.ts, 156, 11)) +>K : Symbol(K, Decl(keyofAndIndexedAccess2.ts, 156, 35)) diff --git a/tests/baselines/reference/keyofAndIndexedAccess2.types b/tests/baselines/reference/keyofAndIndexedAccess2.types index 45cf1d4bad5..a6d83154665 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess2.types +++ b/tests/baselines/reference/keyofAndIndexedAccess2.types @@ -499,7 +499,7 @@ function fn4() { >'abc' : "abc" } -// Repro from #31439 +// Repro from #31439 and #31691 export class c { >c : c @@ -508,6 +508,13 @@ export class c { >x : string constructor() { + this.a = "b"; +>this.a = "b" : "b" +>this.a : string +>this : this +>a : string +>"b" : "b" + this["a"] = "b"; >this["a"] = "b" : "b" >this["a"] : string From 7ac5fa783bd41ee5b64517f1ad85402ee0a49233 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 31 May 2019 11:24:54 -0700 Subject: [PATCH 082/111] Refactor and add wildcard scenario --- src/compiler/resolutionCache.ts | 9 +++++---- src/server/editorServices.ts | 8 +++++++- src/testRunner/unittests/tsserver/projects.ts | 20 +++++++++++++++++++ .../unittests/tsserver/resolutionCache.ts | 3 ++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6e0384219a1..fb04d7b7a18 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -699,6 +699,11 @@ namespace ts { // If something to do with folder/file starting with "." in node_modules folder, skip it if (isPathIgnored(fileOrDirectoryPath)) return false; + // prevent saving an open file from over-eagerly triggering invalidation + if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { + return false; + } + // Some file or directory in the watching directory is created // Return early if it does not have any of the watching extension or not the custom failed lookup path const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); @@ -714,10 +719,6 @@ namespace ts { if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { return false; } - // prevent saving an open file from over-eagerly triggering invalidation - if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { - return false; - } // Ignore emits from the program if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { return false; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6bfce902a04..b0a64432633 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1002,7 +1002,13 @@ namespace ts.server { directory, fileOrDirectory => { const fileOrDirectoryPath = this.toPath(fileOrDirectory); - project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + const fileSystemResult = project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + + // don't trigger callback on open, existing files + if (project.fileIsOpen(fileOrDirectoryPath) && fileSystemResult && fileSystemResult.fileExists) { + return; + } + if (isPathIgnored(fileOrDirectoryPath)) return; const configFilename = project.getConfigFilePath(); diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index ba1eae236a5..dcbff895f1d 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1341,6 +1341,26 @@ var x = 10;` } }); + it("no project structure update on directory watch invoke on open file save", () => { + const projectRootPath = "/users/username/projects/project"; + const fileA: File = { + path: `${projectRootPath}/a.ts`, + content: "export const a = 10;" + }; + const config: File = { + path: `${projectRootPath}/tsconfig.json`, + content: "{}" + }; + const files = [fileA, config]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(fileA.path); + checkNumberOfProjects(service, { configuredProjects: 1 }); + + host.invokeWatchedDirectoriesRecursiveCallback(projectRootPath, "a.ts"); + host.checkTimeoutQueueLength(0); + }); + it("handles delayed directory watch invoke on file creation", () => { const projectRootPath = "/users/username/projects/project"; const fileB: File = { diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 11b604bb20c..05cbf8b9388 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -977,7 +977,7 @@ export const x = 10;` }); describe("avoid unnecessary invalidation", () => { - it("failed lookup invalidation", () => { + it("unnecessary lookup invalidation on save", () => { const expectedNonRelativeDirectories = [`${projectLocation}/node_modules`, `${projectLocation}/src`]; const module1Name = "module1"; const module2Name = "module2"; @@ -999,6 +999,7 @@ export const x = 10;` verifyTrace(resolutionTrace, expectedTrace); verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); + // invoke callback to simulate saving host.invokeWatchedDirectoriesRecursiveCallback(projectLocation + "/src", "file1.ts"); host.checkTimeoutQueueLengthAndRun(0); }); From cb2df757d72943eba31e28e3c9e4f230dff7620d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 13:42:15 -0700 Subject: [PATCH 083/111] Add initial edition of cherry-pick script (#31705) --- scripts/open-cherry-pick-pr.ts | 94 ++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 scripts/open-cherry-pick-pr.ts diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts new file mode 100644 index 00000000000..0d8109bafea --- /dev/null +++ b/scripts/open-cherry-pick-pr.ts @@ -0,0 +1,94 @@ +/// +// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally +/// + +import Octokit = require("@octokit/rest"); +import {runSequence} from "./run-sequence"; +import fs = require("fs"); +import path = require("path"); + +const userName = process.env.GH_USERNAME; +const reviewers = process.env.REQUESTING_USER ? [process.env.REQUESTING_USER] : ["weswigham", "RyanCavanaugh"]; +const branchName = `pick/${process.env.SOURCE_ISSUE}/${process.env.TARGET_BRANCH}`; +const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`; + +async function main() { + const currentSha = runSequence([ + ["git", ["rev-parse", "HEAD"]] + ]); + const currentAuthor = runSequence([ + ["git", ["log", "-1", `--pretty="%aN <%aE>"`]] + ]) + let logText = runSequence([ + ["git", ["log", `master..${currentSha}`, `--pretty="%h %s%n%b"`]] + ]); + logText = `Cherry-pick PR #${process.env.SOURCE_ISSUE} into ${process.env.TARGET_BRANCH} + +Component commits: +${logText}` + const logpath = path.join(__dirname, "../", "logmessage.txt"); + runSequence([ + ["git", ["checkout", "-b", "temp-branch"]], + ["git", ["reset", "master", "--soft"]] + ]); + fs.writeFileSync(logpath, logText); + runSequence([ + ["git", ["commit", "-F", logpath, `--author="${currentAuthor}"`]] + ]); + fs.unlinkSync(logpath); + const squashSha = runSequence([ + ["git", ["rev-parse", "HEAD"]] + ]); + runSequence([ + ["git", ["checkout", process.env.TARGET_BRANCH]], // checkout the target branch + ["git", ["checkout", "-b", branchName]], // create a new branch + ["git", ["cherry-pick", squashSha]], // + ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork + ["git", ["push", "--set-upstream", "fork", branchName, "-f"]] // push the branch + ]); + + const gh = new Octokit(); + gh.authenticate({ + type: "token", + token: process.argv[2] + }); + const r = await gh.pulls.create({ + owner: "Microsoft", + repo: "TypeScript", + maintainer_can_modify: true, + title: `🤖 Cherry-pick PR #${process.env.SOURCE_ISSUE} into ${process.env.TARGET_BRANCH}`, + head: `${userName}:${branchName}`, + base: process.env.TARGET_BRANCH, + body: + `This cherry-pick was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.SOURCE_ISSUE} + Please review the diff and merge if no changes are unexpected. + You can view the cherry-pick log [here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary). + + cc ${reviewers.map(r => "@" + r).join(" ")}`, + }); + const num = r.data.number; + console.log(`Pull request ${num} created.`); + + await gh.issues.createComment({ + number: +process.env.SOURCE_ISSUE, + owner: "Microsoft", + repo: "TypeScript", + body: `Hey @${process.env.REQUESTING_USER}, I've opened #${num} for you.` + }); +} + +main().catch(async e => { + console.error(e); + process.exitCode = 1; + const gh = new Octokit(); + gh.authenticate({ + type: "token", + token: process.argv[2] + }); + await gh.issues.createComment({ + number: +process.env.SOURCE_ISSUE, + owner: "Microsoft", + repo: "TypeScript", + body: `Hey @${process.env.REQUESTING_USER}, I couldn't open a PR with the cherry-pick. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary)). You may need to squash and pick this PR into ${process.env.TARGET_BRANCH} manually.` + }); +}); \ No newline at end of file From 9a2957717e97f2c42024d1792ec1a31923e99a2b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 14:12:45 -0700 Subject: [PATCH 084/111] Strictify the cherry-pick pr script --- .gitignore | 1 + scripts/open-cherry-pick-pr.ts | 32 ++++++++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 9d0bb4d0c6f..04c397bb7e3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ scripts/ior.js scripts/authors.js scripts/configurePrerelease.js scripts/open-user-pr.js +scripts/open-cherry-pick-pr.js scripts/processDiagnosticMessages.d.ts scripts/processDiagnosticMessages.js scripts/produceLKG.js diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts index 0d8109bafea..8d8916d4294 100644 --- a/scripts/open-cherry-pick-pr.ts +++ b/scripts/open-cherry-pick-pr.ts @@ -3,7 +3,7 @@ /// import Octokit = require("@octokit/rest"); -import {runSequence} from "./run-sequence"; +const {runSequence} = require("./run-sequence"); import fs = require("fs"); import path = require("path"); @@ -13,6 +13,12 @@ const branchName = `pick/${process.env.SOURCE_ISSUE}/${process.env.TARGET_BRANCH const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`; async function main() { + if (!process.env.TARGET_BRANCH) { + throw new Error("Target branch not specified"); + } + if (!process.env.SOURCE_ISSUE) { + throw new Error("Source issue not specified"); + } const currentSha = runSequence([ ["git", ["rev-parse", "HEAD"]] ]); @@ -80,15 +86,17 @@ ${logText}` main().catch(async e => { console.error(e); process.exitCode = 1; - const gh = new Octokit(); - gh.authenticate({ - type: "token", - token: process.argv[2] - }); - await gh.issues.createComment({ - number: +process.env.SOURCE_ISSUE, - owner: "Microsoft", - repo: "TypeScript", - body: `Hey @${process.env.REQUESTING_USER}, I couldn't open a PR with the cherry-pick. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary)). You may need to squash and pick this PR into ${process.env.TARGET_BRANCH} manually.` - }); + if (process.env.SOURCE_ISSUE) { + const gh = new Octokit(); + gh.authenticate({ + type: "token", + token: process.argv[2] + }); + await gh.issues.createComment({ + number: +process.env.SOURCE_ISSUE, + owner: "Microsoft", + repo: "TypeScript", + body: `Hey @${process.env.REQUESTING_USER}, I couldn't open a PR with the cherry-pick. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary)). You may need to squash and pick this PR into ${process.env.TARGET_BRANCH} manually.` + }); + } }); \ No newline at end of file From 8813ceb3ceb87ce275d2a55daf6dc33e296bcf83 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 14:24:49 -0700 Subject: [PATCH 085/111] Cant use stdio inherit and stash a result away --- scripts/run-sequence.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/run-sequence.js b/scripts/run-sequence.js index ef7a384af4c..f014cde1b91 100644 --- a/scripts/run-sequence.js +++ b/scripts/run-sequence.js @@ -5,12 +5,13 @@ const cp = require("child_process"); * @param {[string, string[]][]} tasks * @param {cp.SpawnSyncOptions} opts */ -function runSequence(tasks, opts = { timeout: 100000, shell: true, stdio: "inherit" }) { +function runSequence(tasks, opts = { timeout: 100000, shell: true }) { let lastResult; for (const task of tasks) { console.log(`${task[0]} ${task[1].join(" ")}`); const result = cp.spawnSync(task[0], task[1], opts); if (result.status !== 0) throw new Error(`${task[0]} ${task[1].join(" ")} failed: ${result.stderr && result.stderr.toString()}`); + console.log(result.stdout && result.stdout.toString()); lastResult = result; } return lastResult && lastResult.stdout && lastResult.stdout.toString(); From 619648e8c27092eb691be3ade5fde9ce49fb18a4 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 14:36:39 -0700 Subject: [PATCH 086/111] Trim whitespace, fetch origin master --- scripts/open-cherry-pick-pr.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts index 8d8916d4294..2bad1438d31 100644 --- a/scripts/open-cherry-pick-pr.ts +++ b/scripts/open-cherry-pick-pr.ts @@ -24,14 +24,17 @@ async function main() { ]); const currentAuthor = runSequence([ ["git", ["log", "-1", `--pretty="%aN <%aE>"`]] - ]) + ]); + runSequence([ + ["git", ["fetch", "origin", "master"]] + ]); let logText = runSequence([ - ["git", ["log", `master..${currentSha}`, `--pretty="%h %s%n%b"`]] + ["git", ["log", `origin/master..${currentSha.trim()}`, `--pretty="%h %s%n%b"`]] ]); logText = `Cherry-pick PR #${process.env.SOURCE_ISSUE} into ${process.env.TARGET_BRANCH} Component commits: -${logText}` +${logText.trim()}` const logpath = path.join(__dirname, "../", "logmessage.txt"); runSequence([ ["git", ["checkout", "-b", "temp-branch"]], @@ -39,7 +42,7 @@ ${logText}` ]); fs.writeFileSync(logpath, logText); runSequence([ - ["git", ["commit", "-F", logpath, `--author="${currentAuthor}"`]] + ["git", ["commit", "-F", logpath, `--author="${currentAuthor.trim()}"`]] ]); fs.unlinkSync(logpath); const squashSha = runSequence([ @@ -48,7 +51,7 @@ ${logText}` runSequence([ ["git", ["checkout", process.env.TARGET_BRANCH]], // checkout the target branch ["git", ["checkout", "-b", branchName]], // create a new branch - ["git", ["cherry-pick", squashSha]], // + ["git", ["cherry-pick", squashSha.trim()]], // ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork ["git", ["push", "--set-upstream", "fork", branchName, "-f"]] // push the branch ]); From 2cd3500197aee5aa2d474254f6995c9504ee649f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 14:45:06 -0700 Subject: [PATCH 087/111] origin/master because ref is fetched not pulled --- scripts/open-cherry-pick-pr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts index 2bad1438d31..90ba25d5fb2 100644 --- a/scripts/open-cherry-pick-pr.ts +++ b/scripts/open-cherry-pick-pr.ts @@ -38,7 +38,7 @@ ${logText.trim()}` const logpath = path.join(__dirname, "../", "logmessage.txt"); runSequence([ ["git", ["checkout", "-b", "temp-branch"]], - ["git", ["reset", "master", "--soft"]] + ["git", ["reset", "origin/master", "--soft"]] ]); fs.writeFileSync(logpath, logText); runSequence([ From d3b6ba557ac97837ed1c419887dec39cc9acb834 Mon Sep 17 00:00:00 2001 From: Benjamin Lichtman Date: Fri, 31 May 2019 14:46:15 -0700 Subject: [PATCH 088/111] Change test instead of behavior --- src/server/editorServices.ts | 4 ++-- src/testRunner/unittests/tsserver/projects.ts | 8 ++++---- src/testRunner/unittests/tsserver/resolutionCache.ts | 2 +- src/testRunner/unittests/tsserver/syntaxOperations.ts | 5 +++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b0a64432633..b868286ea51 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1002,10 +1002,10 @@ namespace ts.server { directory, fileOrDirectory => { const fileOrDirectoryPath = this.toPath(fileOrDirectory); - const fileSystemResult = project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); // don't trigger callback on open, existing files - if (project.fileIsOpen(fileOrDirectoryPath) && fileSystemResult && fileSystemResult.fileExists) { + if (project.fileIsOpen(fileOrDirectoryPath)) { return; } diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index dcbff895f1d..409ce5c525f 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -1343,7 +1343,7 @@ var x = 10;` it("no project structure update on directory watch invoke on open file save", () => { const projectRootPath = "/users/username/projects/project"; - const fileA: File = { + const file1: File = { path: `${projectRootPath}/a.ts`, content: "export const a = 10;" }; @@ -1351,13 +1351,13 @@ var x = 10;` path: `${projectRootPath}/tsconfig.json`, content: "{}" }; - const files = [fileA, config]; + const files = [file1, config]; const host = createServerHost(files); const service = createProjectService(host); - service.openClientFile(fileA.path); + service.openClientFile(file1.path); checkNumberOfProjects(service, { configuredProjects: 1 }); - host.invokeWatchedDirectoriesRecursiveCallback(projectRootPath, "a.ts"); + host.modifyFile(file1.path, file1.content, { invokeFileDeleteCreateAsPartInsteadOfChange: true }); host.checkTimeoutQueueLength(0); }); diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 05cbf8b9388..768111f3bfe 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -1000,7 +1000,7 @@ export const x = 10;` verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); // invoke callback to simulate saving - host.invokeWatchedDirectoriesRecursiveCallback(projectLocation + "/src", "file1.ts"); + host.modifyFile(file1.path, file1.content, { invokeFileDeleteCreateAsPartInsteadOfChange: true }); host.checkTimeoutQueueLengthAndRun(0); }); }); diff --git a/src/testRunner/unittests/tsserver/syntaxOperations.ts b/src/testRunner/unittests/tsserver/syntaxOperations.ts index 66a962d3625..d3127355191 100644 --- a/src/testRunner/unittests/tsserver/syntaxOperations.ts +++ b/src/testRunner/unittests/tsserver/syntaxOperations.ts @@ -60,13 +60,14 @@ describe("Test Suite 1", () => { const navBarResultUnitTest1 = navBarFull(session, unitTest1); host.deleteFile(unitTest1.path); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); + host.checkTimeoutQueueLengthAndRun(0); + checkProjectActualFiles(project, expectedFilesWithUnitTest1); session.executeCommandSeq({ command: protocol.CommandTypes.Close, arguments: { file: unitTest1.path } }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(project, expectedFilesWithoutUnitTest1); const unitTest1WithChangedContent: File = { From 22348d8a0cec9a0a2483176d6b07bdd5fcf35e8b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 14:46:33 -0700 Subject: [PATCH 089/111] Reverse log order --- scripts/open-cherry-pick-pr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts index 90ba25d5fb2..a757322596d 100644 --- a/scripts/open-cherry-pick-pr.ts +++ b/scripts/open-cherry-pick-pr.ts @@ -29,7 +29,7 @@ async function main() { ["git", ["fetch", "origin", "master"]] ]); let logText = runSequence([ - ["git", ["log", `origin/master..${currentSha.trim()}`, `--pretty="%h %s%n%b"`]] + ["git", ["log", `origin/master..${currentSha.trim()}`, `--pretty="%h %s%n%b"`, "--reverse"]] ]); logText = `Cherry-pick PR #${process.env.SOURCE_ISSUE} into ${process.env.TARGET_BRANCH} From a23ae4c7428ea6d7dcac012136d1a16aacb6ee76 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 14:54:36 -0700 Subject: [PATCH 090/111] Dedent message so its not code-blocked --- scripts/open-cherry-pick-pr.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts index a757322596d..8bdeb05e4a2 100644 --- a/scripts/open-cherry-pick-pr.ts +++ b/scripts/open-cherry-pick-pr.ts @@ -70,10 +70,10 @@ ${logText.trim()}` base: process.env.TARGET_BRANCH, body: `This cherry-pick was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.SOURCE_ISSUE} - Please review the diff and merge if no changes are unexpected. - You can view the cherry-pick log [here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary). +Please review the diff and merge if no changes are unexpected. +You can view the cherry-pick log [here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary). - cc ${reviewers.map(r => "@" + r).join(" ")}`, +cc ${reviewers.map(r => "@" + r).join(" ")}`, }); const num = r.data.number; console.log(`Pull request ${num} created.`); From eb5c6970a3f5a329f0a797ae0b873849b92a7710 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Fri, 31 May 2019 15:56:04 -0700 Subject: [PATCH 091/111] Update user baselines (#31699) --- tests/baselines/reference/user/uglify-js.log | 68 ++++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/baselines/reference/user/uglify-js.log b/tests/baselines/reference/user/uglify-js.log index 3c98006c52f..038bb1174cf 100644 --- a/tests/baselines/reference/user/uglify-js.log +++ b/tests/baselines/reference/user/uglify-js.log @@ -27,41 +27,41 @@ node_modules/uglify-js/lib/compress.js(2044,59): error TS2554: Expected 0 argume node_modules/uglify-js/lib/compress.js(2082,53): error TS2345: Argument of type 'any[]' is not assignable to parameter of type '[number, number, ...never[]]'. Type 'any[]' is missing the following properties from type '[number, number, ...never[]]': 0, 1 node_modules/uglify-js/lib/compress.js(2230,34): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(2942,42): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3395,38): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3408,33): error TS2322: Type '"f"' is not assignable to type 'boolean'. -node_modules/uglify-js/lib/compress.js(3542,33): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3595,29): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3612,29): error TS2339: Property 'parent' does not exist on type 'TreeTransformer'. -node_modules/uglify-js/lib/compress.js(3637,75): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3711,63): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3832,12): error TS2339: Property 'push' does not exist on type 'TreeTransformer'. -node_modules/uglify-js/lib/compress.js(3897,38): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(3918,24): error TS2339: Property 'parent' does not exist on type 'TreeTransformer'. -node_modules/uglify-js/lib/compress.js(3928,28): error TS2339: Property 'parent' does not exist on type 'TreeTransformer'. -node_modules/uglify-js/lib/compress.js(4097,21): error TS2403: Subsequent variable declarations must have the same type. Variable 'defs' must be of type 'Dictionary & { set: (key: any, val: any) => Dictionary & { set: ...; add: (key: any, val: any) => Dictionary & { set: ...; add: ...; get: (key: any) => any; del: (key: any) => Dictionary & { set: ...; ... 8 more ...; toObject: () => any; }; ... 5 more ...; toObject: () => any; }; ... 7 more ...; toObject: () => any;...', but here has type 'any'. -node_modules/uglify-js/lib/compress.js(4149,17): error TS2447: The '|=' operator is not allowed for boolean types. Consider using '||' instead. -node_modules/uglify-js/lib/compress.js(4210,45): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(4320,33): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(4618,17): error TS2403: Subsequent variable declarations must have the same type. Variable 'body' must be of type 'any[]', but here has type 'any'. -node_modules/uglify-js/lib/compress.js(4702,37): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(4910,57): error TS2345: Argument of type 'any[]' is not assignable to parameter of type '[RegExp, (string | undefined)?]'. +node_modules/uglify-js/lib/compress.js(2949,42): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3402,38): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3415,33): error TS2322: Type '"f"' is not assignable to type 'boolean'. +node_modules/uglify-js/lib/compress.js(3549,33): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3602,29): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3619,29): error TS2339: Property 'parent' does not exist on type 'TreeTransformer'. +node_modules/uglify-js/lib/compress.js(3644,75): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3718,63): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3839,12): error TS2339: Property 'push' does not exist on type 'TreeTransformer'. +node_modules/uglify-js/lib/compress.js(3914,38): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(3935,24): error TS2339: Property 'parent' does not exist on type 'TreeTransformer'. +node_modules/uglify-js/lib/compress.js(3945,28): error TS2339: Property 'parent' does not exist on type 'TreeTransformer'. +node_modules/uglify-js/lib/compress.js(4114,21): error TS2403: Subsequent variable declarations must have the same type. Variable 'defs' must be of type 'Dictionary & { set: (key: any, val: any) => Dictionary & { set: ...; add: (key: any, val: any) => Dictionary & { set: ...; add: ...; get: (key: any) => any; del: (key: any) => Dictionary & { set: ...; ... 8 more ...; toObject: () => any; }; ... 5 more ...; toObject: () => any; }; ... 7 more ...; toObject: () => any;...', but here has type 'any'. +node_modules/uglify-js/lib/compress.js(4166,17): error TS2447: The '|=' operator is not allowed for boolean types. Consider using '||' instead. +node_modules/uglify-js/lib/compress.js(4229,45): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(4340,33): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(4638,17): error TS2403: Subsequent variable declarations must have the same type. Variable 'body' must be of type 'any[]', but here has type 'any'. +node_modules/uglify-js/lib/compress.js(4722,37): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(4930,57): error TS2345: Argument of type 'any[]' is not assignable to parameter of type '[RegExp, (string | undefined)?]'. Property '0' is missing in type 'any[]' but required in type '[RegExp, (string | undefined)?]'. -node_modules/uglify-js/lib/compress.js(5074,45): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(5081,25): error TS2403: Subsequent variable declarations must have the same type. Variable 'code' must be of type 'string', but here has type '{ get: () => string; toString: () => string; indent: () => void; indentation: () => number; current_width: () => number; should_break: () => boolean; has_parens: () => boolean; newline: () => void; print: (str: any) => void; ... 24 more ...; parent: (n: any) => any; }'. -node_modules/uglify-js/lib/compress.js(5085,36): error TS2532: Object is possibly 'undefined'. -node_modules/uglify-js/lib/compress.js(5090,41): error TS2339: Property 'get' does not exist on type 'string'. -node_modules/uglify-js/lib/compress.js(5594,18): error TS2454: Variable 'is_strict_comparison' is used before being assigned. -node_modules/uglify-js/lib/compress.js(6105,25): error TS2367: This condition will always return 'false' since the types 'boolean' and '"f"' have no overlap. -node_modules/uglify-js/lib/compress.js(6132,47): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(6205,39): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(6277,39): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(6283,41): error TS2554: Expected 0 arguments, but got 1. -node_modules/uglify-js/lib/compress.js(6728,43): error TS2454: Variable 'property' is used before being assigned. -node_modules/uglify-js/lib/compress.js(6743,25): error TS2403: Subsequent variable declarations must have the same type. Variable 'value' must be of type 'number', but here has type 'any'. -node_modules/uglify-js/lib/compress.js(6746,46): error TS2339: Property 'has_side_effects' does not exist on type 'number'. -node_modules/uglify-js/lib/compress.js(6752,25): error TS2403: Subsequent variable declarations must have the same type. Variable 'value' must be of type 'number', but here has type 'any'. -node_modules/uglify-js/lib/compress.js(6790,34): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(5094,45): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(5101,25): error TS2403: Subsequent variable declarations must have the same type. Variable 'code' must be of type 'string', but here has type '{ get: () => string; toString: () => string; indent: () => void; indentation: () => number; current_width: () => number; should_break: () => boolean; has_parens: () => boolean; newline: () => void; print: (str: any) => void; ... 24 more ...; parent: (n: any) => any; }'. +node_modules/uglify-js/lib/compress.js(5105,36): error TS2532: Object is possibly 'undefined'. +node_modules/uglify-js/lib/compress.js(5110,41): error TS2339: Property 'get' does not exist on type 'string'. +node_modules/uglify-js/lib/compress.js(5623,18): error TS2454: Variable 'is_strict_comparison' is used before being assigned. +node_modules/uglify-js/lib/compress.js(6134,25): error TS2367: This condition will always return 'false' since the types 'boolean' and '"f"' have no overlap. +node_modules/uglify-js/lib/compress.js(6161,47): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(6234,39): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(6306,39): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(6312,41): error TS2554: Expected 0 arguments, but got 1. +node_modules/uglify-js/lib/compress.js(6757,43): error TS2454: Variable 'property' is used before being assigned. +node_modules/uglify-js/lib/compress.js(6772,25): error TS2403: Subsequent variable declarations must have the same type. Variable 'value' must be of type 'number', but here has type 'any'. +node_modules/uglify-js/lib/compress.js(6775,46): error TS2339: Property 'has_side_effects' does not exist on type 'number'. +node_modules/uglify-js/lib/compress.js(6781,25): error TS2403: Subsequent variable declarations must have the same type. Variable 'value' must be of type 'number', but here has type 'any'. +node_modules/uglify-js/lib/compress.js(6819,34): error TS2554: Expected 0 arguments, but got 1. node_modules/uglify-js/lib/minify.js(167,75): error TS2339: Property 'compress' does not exist on type 'Compressor'. node_modules/uglify-js/lib/mozilla-ast.js(566,33): error TS2554: Expected 0 arguments, but got 1. node_modules/uglify-js/lib/output.js(235,25): error TS2554: Expected 0 arguments, but got 2. From 41ce98b440565de472a1f31909237be529d132d3 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 31 May 2019 16:11:08 -0700 Subject: [PATCH 092/111] Propagate saved variance flags from cached comparisons (#31688) * Propegate saved variance flags from cached comparisons * Propegate variance a bit more selectively * Add test * Remove now-redundant code * Fix misspelling and remove unneeded branch --- src/compiler/checker.ts | 28 +++- .../identicalTypesNoDifferByCheckOrder.js | 55 ++++++++ ...identicalTypesNoDifferByCheckOrder.symbols | 127 ++++++++++++++++++ .../identicalTypesNoDifferByCheckOrder.types | 88 ++++++++++++ .../identicalTypesNoDifferByCheckOrder.ts | 38 ++++++ 5 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/identicalTypesNoDifferByCheckOrder.js create mode 100644 tests/baselines/reference/identicalTypesNoDifferByCheckOrder.symbols create mode 100644 tests/baselines/reference/identicalTypesNoDifferByCheckOrder.types create mode 100644 tests/cases/compiler/identicalTypesNoDifferByCheckOrder.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5814b2dcfbf..f79b80be9fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12981,6 +12981,18 @@ namespace ts { return result; } + function propagateSidebandVarianceFlags(typeArguments: readonly Type[], variances: VarianceFlags[]) { + for (let i = 0; i < variances.length; i++) { + const v = variances[i]; + if (v & VarianceFlags.Unmeasurable) { + instantiateType(typeArguments[i], reportUnmeasurableMarkers); + } + if (v & VarianceFlags.Unreliable) { + instantiateType(typeArguments[i], reportUnreliableMarkers); + } + } + } + // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are @@ -12998,6 +13010,16 @@ namespace ts { // as a failure, and should be updated as a reported failure by the bottom of this function. } else { + if (outofbandVarianceMarkerHandler) { + // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component + if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && + source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { + propagateSidebandVarianceFlags(source.aliasTypeArguments, getAliasVariances(source.aliasSymbol)); + } + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && length((source).typeArguments)) { + propagateSidebandVarianceFlags((source).typeArguments!, getVariances((source).target)); + } + } return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } } @@ -14070,12 +14092,6 @@ namespace ts { if (unreliable) { variance |= VarianceFlags.Unreliable; } - const covariantID = getRelationKey(typeWithSub, typeWithSuper, assignableRelation); - const contravariantID = getRelationKey(typeWithSuper, typeWithSub, assignableRelation); - // We delete the results of these checks, as we want them to actually be run, see the `Unmeasurable` variance we cache, - // And then fall back to a structural result. - assignableRelation.delete(covariantID); - assignableRelation.delete(contravariantID); } variances.push(variance); } diff --git a/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.js b/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.js new file mode 100644 index 00000000000..a88b29ce254 --- /dev/null +++ b/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.js @@ -0,0 +1,55 @@ +//// [identicalTypesNoDifferByCheckOrder.ts] +interface SomeProps { + x?: string; + y?: number; + renderAs?: FunctionComponent1 +} + +type SomePropsX = Required> & Omit; + +interface SomePropsClone { + x?: string; + y?: number; + renderAs?: FunctionComponent2 +} + +type SomePropsCloneX = Required> & Omit; + +type Validator = {(): boolean, opt?: T}; +type WeakValidationMap = {[K in keyof T]?: null extends T[K] ? Validator : Validator}; + +interface FunctionComponent1

{ + (props: P & { children?: unknown }): void; + propTypes?: WeakValidationMap

; +} + +interface FunctionComponent2

{ + (props: P & { children?: unknown }): void; + propTypes?: WeakValidationMap

; +} + +function needsComponentOfSomeProps3(...x: SomePropsClone[]): void {} +const comp3: FunctionComponent2 = null as any; +needsComponentOfSomeProps3({ renderAs: comp3 }); + +function needsComponentOfSomeProps2(...x: SomeProps[]): void {} +const comp2: FunctionComponent1 = null as any; +needsComponentOfSomeProps2({ renderAs: comp2 }); + +//// [identicalTypesNoDifferByCheckOrder.js] +function needsComponentOfSomeProps3() { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } +} +var comp3 = null; +needsComponentOfSomeProps3({ renderAs: comp3 }); +function needsComponentOfSomeProps2() { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } +} +var comp2 = null; +needsComponentOfSomeProps2({ renderAs: comp2 }); diff --git a/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.symbols b/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.symbols new file mode 100644 index 00000000000..6850276d340 --- /dev/null +++ b/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.symbols @@ -0,0 +1,127 @@ +=== tests/cases/compiler/identicalTypesNoDifferByCheckOrder.ts === +interface SomeProps { +>SomeProps : Symbol(SomeProps, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 0)) + + x?: string; +>x : Symbol(SomeProps.x, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 21)) + + y?: number; +>y : Symbol(SomeProps.y, Decl(identicalTypesNoDifferByCheckOrder.ts, 1, 15)) + + renderAs?: FunctionComponent1 +>renderAs : Symbol(SomeProps.renderAs, Decl(identicalTypesNoDifferByCheckOrder.ts, 2, 15)) +>FunctionComponent1 : Symbol(FunctionComponent1, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 120)) +>SomeProps : Symbol(SomeProps, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 0)) +} + +type SomePropsX = Required> & Omit; +>SomePropsX : Symbol(SomePropsX, Decl(identicalTypesNoDifferByCheckOrder.ts, 4, 1)) +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>SomeProps : Symbol(SomeProps, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 0)) +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>SomeProps : Symbol(SomeProps, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 0)) + +interface SomePropsClone { +>SomePropsClone : Symbol(SomePropsClone, Decl(identicalTypesNoDifferByCheckOrder.ts, 6, 72)) + + x?: string; +>x : Symbol(SomePropsClone.x, Decl(identicalTypesNoDifferByCheckOrder.ts, 8, 26)) + + y?: number; +>y : Symbol(SomePropsClone.y, Decl(identicalTypesNoDifferByCheckOrder.ts, 9, 15)) + + renderAs?: FunctionComponent2 +>renderAs : Symbol(SomePropsClone.renderAs, Decl(identicalTypesNoDifferByCheckOrder.ts, 10, 15)) +>FunctionComponent2 : Symbol(FunctionComponent2, Decl(identicalTypesNoDifferByCheckOrder.ts, 22, 1)) +>SomeProps : Symbol(SomeProps, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 0)) +} + +type SomePropsCloneX = Required> & Omit; +>SomePropsCloneX : Symbol(SomePropsCloneX, Decl(identicalTypesNoDifferByCheckOrder.ts, 12, 1)) +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --)) +>SomePropsClone : Symbol(SomePropsClone, Decl(identicalTypesNoDifferByCheckOrder.ts, 6, 72)) +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>SomePropsClone : Symbol(SomePropsClone, Decl(identicalTypesNoDifferByCheckOrder.ts, 6, 72)) + +type Validator = {(): boolean, opt?: T}; +>Validator : Symbol(Validator, Decl(identicalTypesNoDifferByCheckOrder.ts, 14, 87)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 16, 15)) +>opt : Symbol(opt, Decl(identicalTypesNoDifferByCheckOrder.ts, 16, 33)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 16, 15)) + +type WeakValidationMap = {[K in keyof T]?: null extends T[K] ? Validator : Validator}; +>WeakValidationMap : Symbol(WeakValidationMap, Decl(identicalTypesNoDifferByCheckOrder.ts, 16, 43)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 23)) +>K : Symbol(K, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 30)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 23)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 23)) +>K : Symbol(K, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 30)) +>Validator : Symbol(Validator, Decl(identicalTypesNoDifferByCheckOrder.ts, 14, 87)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 23)) +>K : Symbol(K, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 30)) +>Validator : Symbol(Validator, Decl(identicalTypesNoDifferByCheckOrder.ts, 14, 87)) +>T : Symbol(T, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 23)) +>K : Symbol(K, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 30)) + +interface FunctionComponent1

{ +>FunctionComponent1 : Symbol(FunctionComponent1, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 120)) +>P : Symbol(P, Decl(identicalTypesNoDifferByCheckOrder.ts, 19, 29)) + + (props: P & { children?: unknown }): void; +>props : Symbol(props, Decl(identicalTypesNoDifferByCheckOrder.ts, 20, 5)) +>P : Symbol(P, Decl(identicalTypesNoDifferByCheckOrder.ts, 19, 29)) +>children : Symbol(children, Decl(identicalTypesNoDifferByCheckOrder.ts, 20, 17)) + + propTypes?: WeakValidationMap

; +>propTypes : Symbol(FunctionComponent1.propTypes, Decl(identicalTypesNoDifferByCheckOrder.ts, 20, 46)) +>WeakValidationMap : Symbol(WeakValidationMap, Decl(identicalTypesNoDifferByCheckOrder.ts, 16, 43)) +>P : Symbol(P, Decl(identicalTypesNoDifferByCheckOrder.ts, 19, 29)) +} + +interface FunctionComponent2

{ +>FunctionComponent2 : Symbol(FunctionComponent2, Decl(identicalTypesNoDifferByCheckOrder.ts, 22, 1)) +>P : Symbol(P, Decl(identicalTypesNoDifferByCheckOrder.ts, 24, 29)) + + (props: P & { children?: unknown }): void; +>props : Symbol(props, Decl(identicalTypesNoDifferByCheckOrder.ts, 25, 5)) +>P : Symbol(P, Decl(identicalTypesNoDifferByCheckOrder.ts, 24, 29)) +>children : Symbol(children, Decl(identicalTypesNoDifferByCheckOrder.ts, 25, 17)) + + propTypes?: WeakValidationMap

; +>propTypes : Symbol(FunctionComponent2.propTypes, Decl(identicalTypesNoDifferByCheckOrder.ts, 25, 46)) +>WeakValidationMap : Symbol(WeakValidationMap, Decl(identicalTypesNoDifferByCheckOrder.ts, 16, 43)) +>P : Symbol(P, Decl(identicalTypesNoDifferByCheckOrder.ts, 24, 29)) +} + +function needsComponentOfSomeProps3(...x: SomePropsClone[]): void {} +>needsComponentOfSomeProps3 : Symbol(needsComponentOfSomeProps3, Decl(identicalTypesNoDifferByCheckOrder.ts, 27, 1)) +>x : Symbol(x, Decl(identicalTypesNoDifferByCheckOrder.ts, 29, 36)) +>SomePropsClone : Symbol(SomePropsClone, Decl(identicalTypesNoDifferByCheckOrder.ts, 6, 72)) + +const comp3: FunctionComponent2 = null as any; +>comp3 : Symbol(comp3, Decl(identicalTypesNoDifferByCheckOrder.ts, 30, 5)) +>FunctionComponent2 : Symbol(FunctionComponent2, Decl(identicalTypesNoDifferByCheckOrder.ts, 22, 1)) +>SomePropsCloneX : Symbol(SomePropsCloneX, Decl(identicalTypesNoDifferByCheckOrder.ts, 12, 1)) + +needsComponentOfSomeProps3({ renderAs: comp3 }); +>needsComponentOfSomeProps3 : Symbol(needsComponentOfSomeProps3, Decl(identicalTypesNoDifferByCheckOrder.ts, 27, 1)) +>renderAs : Symbol(renderAs, Decl(identicalTypesNoDifferByCheckOrder.ts, 31, 28)) +>comp3 : Symbol(comp3, Decl(identicalTypesNoDifferByCheckOrder.ts, 30, 5)) + +function needsComponentOfSomeProps2(...x: SomeProps[]): void {} +>needsComponentOfSomeProps2 : Symbol(needsComponentOfSomeProps2, Decl(identicalTypesNoDifferByCheckOrder.ts, 31, 48)) +>x : Symbol(x, Decl(identicalTypesNoDifferByCheckOrder.ts, 33, 36)) +>SomeProps : Symbol(SomeProps, Decl(identicalTypesNoDifferByCheckOrder.ts, 0, 0)) + +const comp2: FunctionComponent1 = null as any; +>comp2 : Symbol(comp2, Decl(identicalTypesNoDifferByCheckOrder.ts, 34, 5)) +>FunctionComponent1 : Symbol(FunctionComponent1, Decl(identicalTypesNoDifferByCheckOrder.ts, 17, 120)) +>SomePropsX : Symbol(SomePropsX, Decl(identicalTypesNoDifferByCheckOrder.ts, 4, 1)) + +needsComponentOfSomeProps2({ renderAs: comp2 }); +>needsComponentOfSomeProps2 : Symbol(needsComponentOfSomeProps2, Decl(identicalTypesNoDifferByCheckOrder.ts, 31, 48)) +>renderAs : Symbol(renderAs, Decl(identicalTypesNoDifferByCheckOrder.ts, 35, 28)) +>comp2 : Symbol(comp2, Decl(identicalTypesNoDifferByCheckOrder.ts, 34, 5)) + diff --git a/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.types b/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.types new file mode 100644 index 00000000000..e4c691c6b95 --- /dev/null +++ b/tests/baselines/reference/identicalTypesNoDifferByCheckOrder.types @@ -0,0 +1,88 @@ +=== tests/cases/compiler/identicalTypesNoDifferByCheckOrder.ts === +interface SomeProps { + x?: string; +>x : string | undefined + + y?: number; +>y : number | undefined + + renderAs?: FunctionComponent1 +>renderAs : FunctionComponent1 | undefined +} + +type SomePropsX = Required> & Omit; +>SomePropsX : SomePropsX + +interface SomePropsClone { + x?: string; +>x : string | undefined + + y?: number; +>y : number | undefined + + renderAs?: FunctionComponent2 +>renderAs : FunctionComponent2 | undefined +} + +type SomePropsCloneX = Required> & Omit; +>SomePropsCloneX : SomePropsCloneX + +type Validator = {(): boolean, opt?: T}; +>Validator : Validator +>opt : T | undefined + +type WeakValidationMap = {[K in keyof T]?: null extends T[K] ? Validator : Validator}; +>WeakValidationMap : WeakValidationMap +>null : null +>null : null + +interface FunctionComponent1

{ + (props: P & { children?: unknown }): void; +>props : P & { children?: unknown; } +>children : unknown + + propTypes?: WeakValidationMap

; +>propTypes : WeakValidationMap

| undefined +} + +interface FunctionComponent2

{ + (props: P & { children?: unknown }): void; +>props : P & { children?: unknown; } +>children : unknown + + propTypes?: WeakValidationMap

; +>propTypes : WeakValidationMap

| undefined +} + +function needsComponentOfSomeProps3(...x: SomePropsClone[]): void {} +>needsComponentOfSomeProps3 : (...x: SomePropsClone[]) => void +>x : SomePropsClone[] + +const comp3: FunctionComponent2 = null as any; +>comp3 : FunctionComponent2 +>null as any : any +>null : null + +needsComponentOfSomeProps3({ renderAs: comp3 }); +>needsComponentOfSomeProps3({ renderAs: comp3 }) : void +>needsComponentOfSomeProps3 : (...x: SomePropsClone[]) => void +>{ renderAs: comp3 } : { renderAs: FunctionComponent2; } +>renderAs : FunctionComponent2 +>comp3 : FunctionComponent2 + +function needsComponentOfSomeProps2(...x: SomeProps[]): void {} +>needsComponentOfSomeProps2 : (...x: SomeProps[]) => void +>x : SomeProps[] + +const comp2: FunctionComponent1 = null as any; +>comp2 : FunctionComponent1 +>null as any : any +>null : null + +needsComponentOfSomeProps2({ renderAs: comp2 }); +>needsComponentOfSomeProps2({ renderAs: comp2 }) : void +>needsComponentOfSomeProps2 : (...x: SomeProps[]) => void +>{ renderAs: comp2 } : { renderAs: FunctionComponent1; } +>renderAs : FunctionComponent1 +>comp2 : FunctionComponent1 + diff --git a/tests/cases/compiler/identicalTypesNoDifferByCheckOrder.ts b/tests/cases/compiler/identicalTypesNoDifferByCheckOrder.ts new file mode 100644 index 00000000000..fbdf373a407 --- /dev/null +++ b/tests/cases/compiler/identicalTypesNoDifferByCheckOrder.ts @@ -0,0 +1,38 @@ +// @strictNullChecks: true + +interface SomeProps { + x?: string; + y?: number; + renderAs?: FunctionComponent1 +} + +type SomePropsX = Required> & Omit; + +interface SomePropsClone { + x?: string; + y?: number; + renderAs?: FunctionComponent2 +} + +type SomePropsCloneX = Required> & Omit; + +type Validator = {(): boolean, opt?: T}; +type WeakValidationMap = {[K in keyof T]?: null extends T[K] ? Validator : Validator}; + +interface FunctionComponent1

{ + (props: P & { children?: unknown }): void; + propTypes?: WeakValidationMap

; +} + +interface FunctionComponent2

{ + (props: P & { children?: unknown }): void; + propTypes?: WeakValidationMap

; +} + +function needsComponentOfSomeProps3(...x: SomePropsClone[]): void {} +const comp3: FunctionComponent2 = null as any; +needsComponentOfSomeProps3({ renderAs: comp3 }); + +function needsComponentOfSomeProps2(...x: SomeProps[]): void {} +const comp2: FunctionComponent1 = null as any; +needsComponentOfSomeProps2({ renderAs: comp2 }); \ No newline at end of file From 4fc5f6a5ff4f4894cb2868b952e9c7e55685a951 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 16:40:22 -0700 Subject: [PATCH 093/111] When binding pattern contextually types x || y, x contextually types y --- src/compiler/checker.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5814b2dcfbf..bf83323741e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18435,11 +18435,13 @@ namespace ts { } return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive; case SyntaxKind.BarBarToken: - // When an || expression has a contextual type, the operands are contextually typed by that type. When an || - // expression has no contextual type, the right operand is contextually typed by the type of the left operand, - // except for the special case of Javascript declarations of the form `namespace.prop = namespace.prop || {}` + // When an || expression has a contextual type, the operands are contextually typed by that type, except + // when that type originates in a binding pattern, the right operand is contextually typed by the type of + // the left operand. When an || expression has no contextual type, the right operand is contextually typed + // by the type of the left operand, except for the special case of Javascript declarations of the form + // `namespace.prop = namespace.prop || {}`. const type = getContextualType(binaryExpression, contextFlags); - return !type && node === right && !isDefaultedExpandoInitializer(binaryExpression) ? + return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ? getTypeOfExpression(left) : type; case SyntaxKind.AmpersandAmpersandToken: case SyntaxKind.CommaToken: From 5c7823c8b8d463ec0e3274741d357ea972b775b2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 16:52:11 -0700 Subject: [PATCH 094/111] Accept new baselines --- .../reference/initializedDestructuringAssignmentTypes.types | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/initializedDestructuringAssignmentTypes.types b/tests/baselines/reference/initializedDestructuringAssignmentTypes.types index bb30a395cff..333fc5d92cc 100644 --- a/tests/baselines/reference/initializedDestructuringAssignmentTypes.types +++ b/tests/baselines/reference/initializedDestructuringAssignmentTypes.types @@ -9,7 +9,7 @@ const [, a = ''] = ''.match('') || []; >'' : "" >match : (regexp: string | RegExp) => RegExpMatchArray >'' : "" ->[] : [undefined?, ""?] +>[] : undefined[] a.toFixed() >a.toFixed() : any From d0795afb48f06cf5088d6331dbcf2700da0e9734 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 16:53:46 -0700 Subject: [PATCH 095/111] Add regression tests --- .../compiler/destructuringAssignmentWithDefault.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/cases/compiler/destructuringAssignmentWithDefault.ts b/tests/cases/compiler/destructuringAssignmentWithDefault.ts index 45ade402eb8..ba00ea7ceee 100644 --- a/tests/cases/compiler/destructuringAssignmentWithDefault.ts +++ b/tests/cases/compiler/destructuringAssignmentWithDefault.ts @@ -2,3 +2,15 @@ const a: { x?: number } = { }; let x = 0; ({x = 1} = a); + +// Repro from #26235 + +function f1(options?: { color?: string, width?: number }) { + let { color, width } = options || {}; + ({ color, width } = options || {}); +} + +function f2(options?: [string?, number?]) { + let [str, num] = options || []; + [str, num] = options || []; +} From 0895fd6bdc723f1e69097499fa2173c0f1091c5e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 31 May 2019 16:53:53 -0700 Subject: [PATCH 096/111] Accept new baselines --- .../destructuringAssignmentWithDefault.js | 23 +++++++++ ...destructuringAssignmentWithDefault.symbols | 34 ++++++++++++++ .../destructuringAssignmentWithDefault.types | 47 +++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.js b/tests/baselines/reference/destructuringAssignmentWithDefault.js index df4addf0269..4eedee050bd 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.js +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.js @@ -2,6 +2,18 @@ const a: { x?: number } = { }; let x = 0; ({x = 1} = a); + +// Repro from #26235 + +function f1(options?: { color?: string, width?: number }) { + let { color, width } = options || {}; + ({ color, width } = options || {}); +} + +function f2(options?: [string?, number?]) { + let [str, num] = options || []; + [str, num] = options || []; +} //// [destructuringAssignmentWithDefault.js] @@ -9,3 +21,14 @@ var _a; var a = {}; var x = 0; (_a = a.x, x = _a === void 0 ? 1 : _a); +// Repro from #26235 +function f1(options) { + var _a; + var _b = options || {}, color = _b.color, width = _b.width; + (_a = options || {}, color = _a.color, width = _a.width); +} +function f2(options) { + var _a; + var _b = options || [], str = _b[0], num = _b[1]; + _a = options || [], str = _a[0], num = _a[1]; +} diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.symbols b/tests/baselines/reference/destructuringAssignmentWithDefault.symbols index b011834c4f0..071740ebdde 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.symbols +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.symbols @@ -10,3 +10,37 @@ let x = 0; >x : Symbol(x, Decl(destructuringAssignmentWithDefault.ts, 2, 2)) >a : Symbol(a, Decl(destructuringAssignmentWithDefault.ts, 0, 5)) +// Repro from #26235 + +function f1(options?: { color?: string, width?: number }) { +>f1 : Symbol(f1, Decl(destructuringAssignmentWithDefault.ts, 2, 14)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 6, 12)) +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 6, 23)) +>width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 6, 39)) + + let { color, width } = options || {}; +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 7, 9)) +>width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 7, 16)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 6, 12)) + + ({ color, width } = options || {}); +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 8, 6)) +>width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 8, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 6, 12)) +} + +function f2(options?: [string?, number?]) { +>f2 : Symbol(f2, Decl(destructuringAssignmentWithDefault.ts, 9, 1)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 11, 12)) + + let [str, num] = options || []; +>str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 12, 9)) +>num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 12, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 11, 12)) + + [str, num] = options || []; +>str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 12, 9)) +>num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 12, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 11, 12)) +} + diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.types b/tests/baselines/reference/destructuringAssignmentWithDefault.types index 6347ccd0188..34f377774b5 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.types +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.types @@ -16,3 +16,50 @@ let x = 0; >1 : 1 >a : { x?: number | undefined; } +// Repro from #26235 + +function f1(options?: { color?: string, width?: number }) { +>f1 : (options?: { color?: string | undefined; width?: number | undefined; } | undefined) => void +>options : { color?: string | undefined; width?: number | undefined; } | undefined +>color : string | undefined +>width : number | undefined + + let { color, width } = options || {}; +>color : string | undefined +>width : number | undefined +>options || {} : { color?: string | undefined; width?: number | undefined; } +>options : { color?: string | undefined; width?: number | undefined; } | undefined +>{} : {} + + ({ color, width } = options || {}); +>({ color, width } = options || {}) : { color?: string | undefined; width?: number | undefined; } +>{ color, width } = options || {} : { color?: string | undefined; width?: number | undefined; } +>{ color, width } : { color: string | undefined; width: number | undefined; } +>color : string | undefined +>width : number | undefined +>options || {} : { color?: string | undefined; width?: number | undefined; } +>options : { color?: string | undefined; width?: number | undefined; } | undefined +>{} : {} +} + +function f2(options?: [string?, number?]) { +>f2 : (options?: [(string | undefined)?, (number | undefined)?] | undefined) => void +>options : [(string | undefined)?, (number | undefined)?] | undefined + + let [str, num] = options || []; +>str : string | undefined +>num : number | undefined +>options || [] : [(string | undefined)?, (number | undefined)?] +>options : [(string | undefined)?, (number | undefined)?] | undefined +>[] : [] + + [str, num] = options || []; +>[str, num] = options || [] : [(string | undefined)?, (number | undefined)?] +>[str, num] : [string | undefined, number | undefined] +>str : string | undefined +>num : number | undefined +>options || [] : [(string | undefined)?, (number | undefined)?] +>options : [(string | undefined)?, (number | undefined)?] | undefined +>[] : [] +} + From d548f6af70fced8f029b4d8fef85713774893099 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 1 Jun 2019 09:45:58 -0700 Subject: [PATCH 097/111] Consider object literals in unions to have properties of type 'undefined' --- src/compiler/checker.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bf83323741e..781f24f5d5e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8016,10 +8016,13 @@ namespace ts { else if (isUnion) { const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String)); if (indexInfo) { - checkFlags |= indexInfo.isReadonly ? CheckFlags.Readonly : 0; - checkFlags |= CheckFlags.WritePartial; + checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); } + else if (isObjectLiteralType(type)) { + checkFlags |= CheckFlags.WritePartial; + indexTypes = append(indexTypes, undefinedType); + } else { checkFlags |= CheckFlags.ReadPartial; } @@ -20181,7 +20184,8 @@ namespace ts { let propType: Type; const leftType = checkNonNullExpression(left); const parentSymbol = getNodeLinks(left).resolvedSymbol; - const apparentType = getApparentType(getWidenedType(leftType)); + // We widen array literals to get type any[] instead of undefined[] in non-strict mode + const apparentType = getApparentType(isEmptyArrayLiteralType(leftType) ? getWidenedType(leftType) : leftType); if (isTypeAny(apparentType) || apparentType === silentNeverType) { if (isIdentifier(left) && parentSymbol) { markAliasReferenced(parentSymbol, node); From 86040e069932260ede34c400160a44034c262f6b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 1 Jun 2019 10:36:53 -0700 Subject: [PATCH 098/111] Add more tests --- .../destructuringAssignmentWithDefault.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/cases/compiler/destructuringAssignmentWithDefault.ts b/tests/cases/compiler/destructuringAssignmentWithDefault.ts index ba00ea7ceee..76b0e71c373 100644 --- a/tests/cases/compiler/destructuringAssignmentWithDefault.ts +++ b/tests/cases/compiler/destructuringAssignmentWithDefault.ts @@ -8,9 +8,25 @@ let x = 0; function f1(options?: { color?: string, width?: number }) { let { color, width } = options || {}; ({ color, width } = options || {}); + let x1 = (options || {}).color; + let x2 = (options || {})["color"]; } function f2(options?: [string?, number?]) { let [str, num] = options || []; [str, num] = options || []; + let x1 = (options || {})[0]; +} + +function f3(options?: { color: string, width: number }) { + let { color, width } = options || {}; + ({ color, width } = options || {}); + let x1 = (options || {}).color; + let x2 = (options || {})["color"]; +} + +function f4(options?: [string, number]) { + let [str, num] = options || []; + [str, num] = options || []; + let x1 = (options || {})[0]; } From 48d343b9852acd673c38ad0c43a93aada3935e79 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 1 Jun 2019 10:37:02 -0700 Subject: [PATCH 099/111] Accept new baselines --- .../destructuringAssignmentWithDefault.js | 32 ++++++ ...destructuringAssignmentWithDefault.symbols | 80 +++++++++++++-- .../destructuringAssignmentWithDefault.types | 99 +++++++++++++++++++ 3 files changed, 203 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.js b/tests/baselines/reference/destructuringAssignmentWithDefault.js index 4eedee050bd..b2b0c31654b 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.js +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.js @@ -8,11 +8,27 @@ let x = 0; function f1(options?: { color?: string, width?: number }) { let { color, width } = options || {}; ({ color, width } = options || {}); + let x1 = (options || {}).color; + let x2 = (options || {})["color"]; } function f2(options?: [string?, number?]) { let [str, num] = options || []; [str, num] = options || []; + let x1 = (options || {})[0]; +} + +function f3(options?: { color: string, width: number }) { + let { color, width } = options || {}; + ({ color, width } = options || {}); + let x1 = (options || {}).color; + let x2 = (options || {})["color"]; +} + +function f4(options?: [string, number]) { + let [str, num] = options || []; + [str, num] = options || []; + let x1 = (options || {})[0]; } @@ -26,9 +42,25 @@ function f1(options) { var _a; var _b = options || {}, color = _b.color, width = _b.width; (_a = options || {}, color = _a.color, width = _a.width); + var x1 = (options || {}).color; + var x2 = (options || {})["color"]; } function f2(options) { var _a; var _b = options || [], str = _b[0], num = _b[1]; _a = options || [], str = _a[0], num = _a[1]; + var x1 = (options || {})[0]; +} +function f3(options) { + var _a; + var _b = options || {}, color = _b.color, width = _b.width; + (_a = options || {}, color = _a.color, width = _a.width); + var x1 = (options || {}).color; + var x2 = (options || {})["color"]; +} +function f4(options) { + var _a; + var _b = options || [], str = _b[0], num = _b[1]; + _a = options || [], str = _a[0], num = _a[1]; + var x1 = (options || {})[0]; } diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.symbols b/tests/baselines/reference/destructuringAssignmentWithDefault.symbols index 071740ebdde..eef4bdcd97a 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.symbols +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.symbols @@ -27,20 +27,84 @@ function f1(options?: { color?: string, width?: number }) { >color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 8, 6)) >width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 8, 13)) >options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 6, 12)) + + let x1 = (options || {}).color; +>x1 : Symbol(x1, Decl(destructuringAssignmentWithDefault.ts, 9, 7)) +>(options || {}).color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 6, 23)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 6, 12)) +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 6, 23)) + + let x2 = (options || {})["color"]; +>x2 : Symbol(x2, Decl(destructuringAssignmentWithDefault.ts, 10, 7)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 6, 12)) +>"color" : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 6, 23)) } function f2(options?: [string?, number?]) { ->f2 : Symbol(f2, Decl(destructuringAssignmentWithDefault.ts, 9, 1)) ->options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 11, 12)) +>f2 : Symbol(f2, Decl(destructuringAssignmentWithDefault.ts, 11, 1)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 13, 12)) let [str, num] = options || []; ->str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 12, 9)) ->num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 12, 13)) ->options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 11, 12)) +>str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 14, 9)) +>num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 14, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 13, 12)) [str, num] = options || []; ->str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 12, 9)) ->num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 12, 13)) ->options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 11, 12)) +>str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 14, 9)) +>num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 14, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 13, 12)) + + let x1 = (options || {})[0]; +>x1 : Symbol(x1, Decl(destructuringAssignmentWithDefault.ts, 16, 7)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 13, 12)) +>0 : Symbol(0) +} + +function f3(options?: { color: string, width: number }) { +>f3 : Symbol(f3, Decl(destructuringAssignmentWithDefault.ts, 17, 1)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 19, 12)) +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 19, 23)) +>width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 19, 38)) + + let { color, width } = options || {}; +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 20, 9)) +>width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 20, 16)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 19, 12)) + + ({ color, width } = options || {}); +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 21, 6)) +>width : Symbol(width, Decl(destructuringAssignmentWithDefault.ts, 21, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 19, 12)) + + let x1 = (options || {}).color; +>x1 : Symbol(x1, Decl(destructuringAssignmentWithDefault.ts, 22, 7)) +>(options || {}).color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 19, 23)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 19, 12)) +>color : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 19, 23)) + + let x2 = (options || {})["color"]; +>x2 : Symbol(x2, Decl(destructuringAssignmentWithDefault.ts, 23, 7)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 19, 12)) +>"color" : Symbol(color, Decl(destructuringAssignmentWithDefault.ts, 19, 23)) +} + +function f4(options?: [string, number]) { +>f4 : Symbol(f4, Decl(destructuringAssignmentWithDefault.ts, 24, 1)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 26, 12)) + + let [str, num] = options || []; +>str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 27, 9)) +>num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 27, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 26, 12)) + + [str, num] = options || []; +>str : Symbol(str, Decl(destructuringAssignmentWithDefault.ts, 27, 9)) +>num : Symbol(num, Decl(destructuringAssignmentWithDefault.ts, 27, 13)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 26, 12)) + + let x1 = (options || {})[0]; +>x1 : Symbol(x1, Decl(destructuringAssignmentWithDefault.ts, 29, 7)) +>options : Symbol(options, Decl(destructuringAssignmentWithDefault.ts, 26, 12)) +>0 : Symbol(0) } diff --git a/tests/baselines/reference/destructuringAssignmentWithDefault.types b/tests/baselines/reference/destructuringAssignmentWithDefault.types index 34f377774b5..379bfe8b3bd 100644 --- a/tests/baselines/reference/destructuringAssignmentWithDefault.types +++ b/tests/baselines/reference/destructuringAssignmentWithDefault.types @@ -40,6 +40,24 @@ function f1(options?: { color?: string, width?: number }) { >options || {} : { color?: string | undefined; width?: number | undefined; } >options : { color?: string | undefined; width?: number | undefined; } | undefined >{} : {} + + let x1 = (options || {}).color; +>x1 : string | undefined +>(options || {}).color : string | undefined +>(options || {}) : { color?: string | undefined; width?: number | undefined; } +>options || {} : { color?: string | undefined; width?: number | undefined; } +>options : { color?: string | undefined; width?: number | undefined; } | undefined +>{} : {} +>color : string | undefined + + let x2 = (options || {})["color"]; +>x2 : string | undefined +>(options || {})["color"] : string | undefined +>(options || {}) : { color?: string | undefined; width?: number | undefined; } +>options || {} : { color?: string | undefined; width?: number | undefined; } +>options : { color?: string | undefined; width?: number | undefined; } | undefined +>{} : {} +>"color" : "color" } function f2(options?: [string?, number?]) { @@ -61,5 +79,86 @@ function f2(options?: [string?, number?]) { >options || [] : [(string | undefined)?, (number | undefined)?] >options : [(string | undefined)?, (number | undefined)?] | undefined >[] : [] + + let x1 = (options || {})[0]; +>x1 : string | undefined +>(options || {})[0] : string | undefined +>(options || {}) : [(string | undefined)?, (number | undefined)?] | {} +>options || {} : [(string | undefined)?, (number | undefined)?] | {} +>options : [(string | undefined)?, (number | undefined)?] | undefined +>{} : {} +>0 : 0 +} + +function f3(options?: { color: string, width: number }) { +>f3 : (options?: { color: string; width: number; } | undefined) => void +>options : { color: string; width: number; } | undefined +>color : string +>width : number + + let { color, width } = options || {}; +>color : string | undefined +>width : number | undefined +>options || {} : { color: string; width: number; } | {} +>options : { color: string; width: number; } | undefined +>{} : {} + + ({ color, width } = options || {}); +>({ color, width } = options || {}) : { color: string; width: number; } | {} +>{ color, width } = options || {} : { color: string; width: number; } | {} +>{ color, width } : { color: string | undefined; width: number | undefined; } +>color : string | undefined +>width : number | undefined +>options || {} : { color: string; width: number; } | {} +>options : { color: string; width: number; } | undefined +>{} : {} + + let x1 = (options || {}).color; +>x1 : string | undefined +>(options || {}).color : string | undefined +>(options || {}) : { color: string; width: number; } | {} +>options || {} : { color: string; width: number; } | {} +>options : { color: string; width: number; } | undefined +>{} : {} +>color : string | undefined + + let x2 = (options || {})["color"]; +>x2 : string | undefined +>(options || {})["color"] : string | undefined +>(options || {}) : { color: string; width: number; } | {} +>options || {} : { color: string; width: number; } | {} +>options : { color: string; width: number; } | undefined +>{} : {} +>"color" : "color" +} + +function f4(options?: [string, number]) { +>f4 : (options?: [string, number] | undefined) => void +>options : [string, number] | undefined + + let [str, num] = options || []; +>str : string | undefined +>num : number | undefined +>options || [] : [] | [string, number] +>options : [string, number] | undefined +>[] : [] + + [str, num] = options || []; +>[str, num] = options || [] : [] | [string, number] +>[str, num] : [string | undefined, number | undefined] +>str : string | undefined +>num : number | undefined +>options || [] : [] | [string, number] +>options : [string, number] | undefined +>[] : [] + + let x1 = (options || {})[0]; +>x1 : string | undefined +>(options || {})[0] : string | undefined +>(options || {}) : [string, number] | {} +>options || {} : [string, number] | {} +>options : [string, number] | undefined +>{} : {} +>0 : 0 } From 30333e19e22e2fd6a1366289a4d92f1975946d7c Mon Sep 17 00:00:00 2001 From: csigs Date: Mon, 3 Jun 2019 16:10:14 +0000 Subject: [PATCH 100/111] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 140 +++++++++--------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl index 0078e878dbc..6720bf0c191 100644 --- a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -2881,7 +2881,7 @@ - + @@ -3667,7 +3667,7 @@ - + @@ -3685,7 +3685,7 @@ - + @@ -3694,7 +3694,7 @@ - + @@ -4369,7 +4369,7 @@ - + @@ -4399,7 +4399,7 @@ - + @@ -5242,7 +5242,7 @@ - + @@ -5251,7 +5251,7 @@ - + @@ -5935,7 +5935,7 @@ - + @@ -5971,7 +5971,7 @@ - + @@ -5980,7 +5980,7 @@ - + @@ -5998,7 +5998,7 @@ - + @@ -6007,7 +6007,7 @@ - + @@ -6016,7 +6016,7 @@ - + @@ -6025,7 +6025,7 @@ - + @@ -6043,7 +6043,7 @@ - + @@ -6052,7 +6052,7 @@ - + @@ -6061,7 +6061,7 @@ - + @@ -6070,7 +6070,7 @@ - + @@ -6079,7 +6079,7 @@ - + @@ -6088,7 +6088,7 @@ - + @@ -6106,7 +6106,7 @@ - + @@ -6115,7 +6115,7 @@ - + @@ -6133,7 +6133,7 @@ - + @@ -6142,7 +6142,7 @@ - + @@ -6169,7 +6169,7 @@ - + @@ -6178,7 +6178,7 @@ - + @@ -6187,7 +6187,7 @@ - + @@ -6196,7 +6196,7 @@ - + @@ -6577,7 +6577,7 @@ - + @@ -6586,7 +6586,7 @@ - + @@ -6595,7 +6595,7 @@ - + @@ -6676,7 +6676,7 @@ - + @@ -6685,7 +6685,7 @@ - + @@ -6703,7 +6703,7 @@ - + @@ -6712,7 +6712,7 @@ - + @@ -6730,7 +6730,7 @@ - + @@ -6739,7 +6739,7 @@ - + @@ -6757,7 +6757,7 @@ - + @@ -6766,7 +6766,7 @@ - + @@ -7090,7 +7090,7 @@ - + @@ -7099,7 +7099,7 @@ - + @@ -7108,7 +7108,7 @@ - + @@ -7117,7 +7117,7 @@ - + @@ -7147,7 +7147,7 @@ - + @@ -7156,7 +7156,7 @@ - + @@ -7165,7 +7165,7 @@ - + @@ -7174,7 +7174,7 @@ - + @@ -7183,7 +7183,7 @@ - + @@ -7192,7 +7192,7 @@ - + @@ -7210,7 +7210,7 @@ - + @@ -7219,7 +7219,7 @@ - + @@ -7237,7 +7237,7 @@ - + @@ -7246,7 +7246,7 @@ - + @@ -7264,7 +7264,7 @@ - + @@ -7273,7 +7273,7 @@ - + @@ -7291,7 +7291,7 @@ - + @@ -7300,7 +7300,7 @@ - + @@ -8692,7 +8692,7 @@ - + @@ -8701,7 +8701,7 @@ - + @@ -8710,7 +8710,7 @@ - + @@ -8719,7 +8719,7 @@ - + @@ -8728,7 +8728,7 @@ - + @@ -8737,7 +8737,7 @@ - + @@ -8746,7 +8746,7 @@ - + @@ -8755,7 +8755,7 @@ - + @@ -8764,7 +8764,7 @@ - + @@ -8839,7 +8839,7 @@ - + @@ -9676,7 +9676,7 @@ - + @@ -9685,7 +9685,7 @@ - + From 49c44f650a8b4880d459b423c1c4d25e05e3af6d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 3 Jun 2019 15:24:35 -0700 Subject: [PATCH 101/111] Add script for pack response postback (#31748) --- package.json | 3 ++ scripts/post-vsts-artifact-comment.js | 64 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 scripts/post-vsts-artifact-comment.js diff --git a/package.json b/package.json index ae67f3166b5..965c0c3bb32 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,13 @@ "@types/mocha": "latest", "@types/ms": "latest", "@types/node": "8.5.5", + "@types/node-fetch": "^2.3.4", "@types/q": "latest", "@types/source-map-support": "latest", "@types/through2": "latest", "@types/travis-fold": "latest", "@types/xml2js": "^0.4.0", + "azure-devops-node-api": "^8.0.0", "browser-resolve": "^1.11.2", "browserify": "latest", "chai": "latest", @@ -74,6 +76,7 @@ "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", "ms": "latest", + "node-fetch": "^2.6.0", "plugin-error": "latest", "pretty-hrtime": "^1.0.3", "prex": "^0.4.3", diff --git a/scripts/post-vsts-artifact-comment.js b/scripts/post-vsts-artifact-comment.js new file mode 100644 index 00000000000..6d84294bcfe --- /dev/null +++ b/scripts/post-vsts-artifact-comment.js @@ -0,0 +1,64 @@ +// @ts-check +/// +// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally +const Octokit = require("@octokit/rest"); +const ado = require("azure-devops-node-api"); +const { default: fetch } = require("node-fetch"); + +async function main() { + if (!process.env.SOURCE_ISSUE) { + throw new Error("No source issue specified"); + } + if (!process.env.BUILD_BUILDID) { + throw new Error("No build ID specified"); + } + // The pipelines API does _not_ make getting the direct URL to a specific file _within_ an artifact trivial + const cli = new ado.WebApi("https://typescript.visualstudio.com/defaultcollection", ado.getHandlerFromToken("")); // Empty token, anon auth + const build = await cli.getBuildApi(); + const artifact = await build.getArtifact("typescript", +process.env.BUILD_BUILDID, "tgz"); + const updatedUrl = new URL(artifact.resource.url); + updatedUrl.search = `artifactName=tgz&fileId=${artifact.resource.data}&fileName=manifest`; + const resp = await (await fetch(`${updatedUrl}`)).json(); + const file = resp.items[0]; + const tgzUrl = new URL(artifact.resource.url); + tgzUrl.search = `artifactName=tgz&fileId=${file.blob.id}&fileName=${file.path}`; + const link = "" + tgzUrl; + const gh = new Octokit(); + gh.authenticate({ + type: "token", + token: process.argv[2] + }); + await gh.issues.createComment({ + number: +process.env.SOURCE_ISSUE, + owner: "Microsoft", + repo: "TypeScript", + body: `Hey @${process.env.REQUESTING_USER}, I've packed this into [an installable tgz](${link}). You can install it for testing by referencing it in your \`package.json\` like so: +\`\`\` +{ + "devDependencies": { + "typescript": "${link}" + } +} +\`\`\` +and then running \`npm install\`. +` + }); +} + +main().catch(async e => { + console.error(e); + process.exitCode = 1; + if (process.env.SOURCE_ISSUE) { + const gh = new Octokit(); + gh.authenticate({ + type: "token", + token: process.argv[2] + }); + await gh.issues.createComment({ + number: +process.env.SOURCE_ISSUE, + owner: "Microsoft", + repo: "TypeScript", + body: `Hey @${process.env.REQUESTING_USER}, something went wrong when looking for the build artifact. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary)).` + }); + } +}); \ No newline at end of file From 9da05243ff7ba9a2776d5a52118d680348b1454d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 3 Jun 2019 16:06:35 -0700 Subject: [PATCH 102/111] Rewire experimental update script to handle PR triggers --- scripts/update-experimental-branches.js | 110 +++++++++++++----------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index c112cf1a367..8af4df95cb9 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -1,66 +1,74 @@ // @ts-check /// const Octokit = require("@octokit/rest"); -const {runSequence} = require("./run-sequence"); +const { runSequence } = require("./run-sequence"); + +// The first is used by bot-based kickoffs, the second by automatic triggers +const triggeredPR = process.env.SOURCE_ISSUE || process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER; /** - * This program should be invoked as `node ./scripts/update-experimental-branches [Branch2] [...]` + * This program should be invoked as `node ./scripts/update-experimental-branches [PR2] [...]` + * The order PR numbers are passed controls the order in which they are merged together. + * TODO: the following is racey - if two experiment-enlisted PRs trigger simultaneously and witness one another in an unupdated state, they'll both produce + * a new experimental branch, but each will be missing a change from the other. There's no _great_ way to fix this beyond setting the maximum concurrency + * of this task to 1 (so only one job is allowed to update experiments at a time). */ async function main() { - const branchesRaw = process.argv[3]; - const branches = process.argv.slice(3); - if (!branches.length) { - throw new Error(`No experimental branches, aborting...`); + const prnums = process.argv.slice(3); + if (!prnums.length) { + return; // No enlisted PRs, nothing to update } - console.log(`Performing experimental branch updating and merging for branches ${branchesRaw}`); + if (!prnums.some(n => n === triggeredPR)) { + return; // Only have work to do for enlisted PRs + } + console.log(`Performing experimental branch updating and merging for pull requests ${prnums.join(", ")}`); + + const userName = process.env.GH_USERNAME; + const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`; + + // Forcibly cleanup workspace + runSequence([ + ["git", ["clean", "-fdx"]], + ["git", ["checkout", "."]], + ["git", ["checkout", "master"]], + ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork + ["git", ["fetch", "origin", "master:master"]], + ]); const gh = new Octokit(); gh.authenticate({ type: "token", token: process.argv[2] }); - - // Fetch all relevant refs - runSequence([ - ["git", ["fetch", "origin", "master:master", ...branches.map(b => `${b}:${b}`)]] - ]) - - // Forcibly cleanup workspace - runSequence([ - ["git", ["clean", "-fdx"]], - ["git", ["checkout", "."]], - ["git", ["checkout", "master"]], - ]); - - // Update branches - for (const branch of branches) { - // Checkout, then get the merge base - const mergeBase = runSequence([ - ["git", ["checkout", branch]], - ["git", ["merge-base", branch, "master"]], - ]); - // Simulate the merge and abort if there are conflicts - const mergeTree = runSequence([ - ["git", ["merge-tree", mergeBase, branch, "master"]] - ]); - if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker - const res = await gh.pulls.list({owner: "Microsoft", repo: "TypeScript", base: branch}); - if (res && res.data && res.data[0]) { - const pr = res.data[0]; - await gh.issues.createComment({ - owner: "Microsoft", - repo: "TypeScript", - number: pr.number, - body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.` - }); + for (const numRaw of prnums) { + const num = +numRaw; + if (num) { + // PR number rather than branch name - lookup info + const inputPR = await gh.pulls.get({ owner: "Microsoft", repo: "TypeScript", number: num }); + // GH calculates the rebaseable-ness of a PR into its target, so we can just use that here + if (!inputPR.data.rebaseable) { + if (+triggeredPR === num) { + await gh.issues.createComment({ + owner: "Microsoft", + repo: "TypeScript", + number: num, + body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.` + }); + throw new Error(`Merge conflict detected in PR ${num} with master`); + } + return; // A PR is currently in conflict, give up } - throw new Error(`Merge conflict detected on branch ${branch} with master`); + runSequence([ + ["git", ["fetch", "origin", `pull/${num}/head:${num}`]], + ["git", ["checkout", `${num}`]], + ["git", ["rebase", "master"]], + ["git", ["push", "-f", "-u", "fork", `${num}`]], // Keep a rebased copy of this branch in our fork + ]); + + } + else { + throw new Error(`Invalid PR number: ${numRaw}`); } - // Merge is good - apply a rebase and (force) push - runSequence([ - ["git", ["rebase", "master"]], - ["git", ["push", "-f", "-u", "origin", branch]], - ]); } // Return to `master` and make a new `experimental` branch @@ -71,17 +79,17 @@ async function main() { ]); // Merge each branch into `experimental` (which, if there is a conflict, we now know is from inter-experiment conflict) - for (const branch of branches) { + for (const branch of prnums) { // Find the merge base const mergeBase = runSequence([ ["git", ["merge-base", branch, "experimental"]], ]); // Simulate the merge and abort if there are conflicts const mergeTree = runSequence([ - ["git", ["merge-tree", mergeBase, branch, "experimental"]] + ["git", ["merge-tree", mergeBase.trim(), branch, "experimental"]] ]); if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker - throw new Error(`Merge conflict detected on branch ${branch} with other experiment`); + throw new Error(`Merge conflict detected involving PR ${branch} with other experiment`); } // Merge (always producing a merge commit) runSequence([ @@ -90,7 +98,7 @@ async function main() { } // Every branch merged OK, force push the replacement `experimental` branch runSequence([ - ["git", ["push", "-f", "-u", "origin", "experimental"]], + ["git", ["push", "-f", "-u", "fork", "experimental"]], ]); } From 38da682de762bed1a48421a1dbc88a4f07a94ad5 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 14:35:00 -0700 Subject: [PATCH 103/111] Unify contextual signature type parameter assignment (#31574) * Unify conditional signature type assignment * Moonomorphism --- src/compiler/checker.ts | 39 ++++++-- ...ckToBindingPatternForTypeInference.symbols | 1 + ...mpareAgainstUninstantiatedTypeParameter.js | 67 ++++++++++++++ ...AgainstUninstantiatedTypeParameter.symbols | 88 ++++++++++++++++++ ...reAgainstUninstantiatedTypeParameter.types | 92 +++++++++++++++++++ ...mpareAgainstUninstantiatedTypeParameter.ts | 33 +++++++ 6 files changed, 313 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js create mode 100644 tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols create mode 100644 tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types create mode 100644 tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ed6768cb3d..817364ba832 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5231,7 +5231,7 @@ namespace ts { } } // Use contextual parameter type if one is available - const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); + const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration, /*forCache*/ true); if (type) { return addOptionality(type, isOptional); } @@ -11522,6 +11522,7 @@ namespace ts { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type return isContextSensitiveFunctionLikeDeclaration(node); case SyntaxKind.ObjectLiteralExpression: return some((node).properties, isContextSensitive); @@ -11555,6 +11556,9 @@ namespace ts { } function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { + if (isFunctionDeclaration(node) && (!isInJSFile(node) || !getTypeForDeclarationFromJSDocComment(node))) { + return false; + } // Functions with type parameters are not context sensitive. if (node.typeParameters) { return false; @@ -18264,7 +18268,7 @@ namespace ts { } // Return contextual type of parameter or undefined if no contextual type is available - function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { + function getContextuallyTypedParameterType(parameter: ParameterDeclaration, forCache: boolean): Type | undefined { const func = parameter.parent; if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { return undefined; @@ -18285,8 +18289,21 @@ namespace ts { links.resolvedSignature = cached; return type; } - const contextualSignature = getContextualSignature(func); + let contextualSignature = getContextualSignature(func); if (contextualSignature) { + if (forCache) { + // Calling the below guarantees the types are primed and assigned in the same way + // as when the parameter is reached via `checkFunctionExpressionOrObjectLiteralMethod`. + // This should prevent any uninstantiated inference variables in the contextual signature + // from leaking, and should lock in cached parameter types via `assignContextualParameterTypes` + // which we will then immediately use the results of below. + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(func); + const type = getTypeOfSymbol(getMergedSymbol(func.symbol)); + if (isTypeAny(type)) { + return type; + } + contextualSignature = getSignaturesOfType(type, SignatureKind.Call)[0]; + } const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? getRestTypeAtPosition(contextualSignature, index) : @@ -18301,7 +18318,7 @@ namespace ts { } switch (declaration.kind) { case SyntaxKind.Parameter: - return getContextuallyTypedParameterType(declaration); + return getContextuallyTypedParameterType(declaration, /*forCache*/ false); case SyntaxKind.BindingElement: return getContextualTypeForBindingElement(declaration); // By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent @@ -23079,12 +23096,18 @@ namespace ts { checkGrammarForGenerator(node); } - const links = getNodeLinks(node); const type = getTypeOfSymbol(getMergedSymbol(node.symbol)); if (isTypeAny(type)) { return type; } + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + + return type; + } + + function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) { + const links = getNodeLinks(node); // Check if function expression is contextually typed and assign parameter types if so. if (!(links.flags & NodeCheckFlags.ContextChecked)) { const contextualSignature = getContextualSignature(node); @@ -23094,6 +23117,10 @@ namespace ts { if (!(links.flags & NodeCheckFlags.ContextChecked)) { links.flags |= NodeCheckFlags.ContextChecked; if (contextualSignature) { + const type = getTypeOfSymbol(getMergedSymbol(node.symbol)); + if (isTypeAny(type)) { + return; + } const signature = getSignaturesOfType(type, SignatureKind.Call)[0]; if (isContextSensitive(node)) { const inferenceContext = getInferenceContext(node); @@ -23114,8 +23141,6 @@ namespace ts { checkSignatureDeclaration(node); } } - - return type; } function getReturnOrPromisedType(node: FunctionLikeDeclaration | MethodSignature, functionFlags: FunctionFlags) { diff --git a/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols b/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols index f57b234f39f..4f450e21c24 100644 --- a/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols +++ b/tests/baselines/reference/fallbackToBindingPatternForTypeInference.symbols @@ -18,6 +18,7 @@ trans(([b,c]) => 'foo'); trans(({d: [e,f]}) => 'foo'); >trans : Symbol(trans, Decl(fallbackToBindingPatternForTypeInference.ts, 0, 0)) +>d : Symbol(d) >e : Symbol(e, Decl(fallbackToBindingPatternForTypeInference.ts, 3, 12)) >f : Symbol(f, Decl(fallbackToBindingPatternForTypeInference.ts, 3, 14)) diff --git a/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js new file mode 100644 index 00000000000..c3dd7d42112 --- /dev/null +++ b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js @@ -0,0 +1,67 @@ +//// [inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts] +class ClassA { + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { + + } +} +export interface ValueInterface { + func?: (row: TValueClass) => any; + value?: string; +} +export interface SettingsInterface { + values?: (row: TClass) => ValueInterface[], +} +class ConcreteClass { + theName = 'myClass'; +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); + +var thisIsOk = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); + +//// [inferenceDoesntCompareAgainstUninstantiatedTypeParameter.js] +"use strict"; +exports.__esModule = true; +var ClassA = /** @class */ (function () { + function ClassA(entity, settings) { + this.entity = entity; + this.settings = settings; + } + return ClassA; +}()); +var ConcreteClass = /** @class */ (function () { + function ConcreteClass() { + this.theName = 'myClass'; + } + return ConcreteClass; +}()); +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { + values: function (o) { return [ + { + value: o.theName, + func: function (x) { return 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'; } + } + ]; } +}); +var thisIsOk = new ClassA(new ConcreteClass(), { + values: function (o) { return [ + { + value: o.theName, + func: function (x) { return 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj'; } + } + ]; } +}); diff --git a/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols new file mode 100644 index 00000000000..a47f3db5b55 --- /dev/null +++ b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.symbols @@ -0,0 +1,88 @@ +=== tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts === +class ClassA { +>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0)) +>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13)) + + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { +>entity : Symbol(ClassA.entity, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 1, 16)) +>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13)) +>settings : Symbol(ClassA.settings, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 1, 46)) +>SettingsInterface : Symbol(SettingsInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 8, 1)) +>TEntityClass : Symbol(TEntityClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 13)) + + } +} +export interface ValueInterface { +>ValueInterface : Symbol(ValueInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 4, 1)) +>TValueClass : Symbol(TValueClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 5, 32)) + + func?: (row: TValueClass) => any; +>func : Symbol(ValueInterface.func, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 5, 46)) +>row : Symbol(row, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 6, 12)) +>TValueClass : Symbol(TValueClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 5, 32)) + + value?: string; +>value : Symbol(ValueInterface.value, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 6, 37)) +} +export interface SettingsInterface { +>SettingsInterface : Symbol(SettingsInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 8, 1)) +>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35)) + + values?: (row: TClass) => ValueInterface[], +>values : Symbol(SettingsInterface.values, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 44)) +>row : Symbol(row, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 10, 14)) +>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35)) +>ValueInterface : Symbol(ValueInterface, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 4, 1)) +>TClass : Symbol(TClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 9, 35)) +} +class ConcreteClass { +>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1)) + + theName = 'myClass'; +>theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21)) +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { +>thisGetsTheFalseError : Symbol(thisGetsTheFalseError, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 16, 3)) +>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0)) +>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1)) + + values: o => [ +>values : Symbol(values, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 16, 61)) +>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 17, 11)) + { + value: o.theName, +>value : Symbol(value, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 18, 9)) +>o.theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21)) +>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 17, 11)) +>theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21)) + + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' +>func : Symbol(func, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 19, 29)) +>x : Symbol(x, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 20, 17)) + } + ] +}); + +var thisIsOk = new ClassA(new ConcreteClass(), { +>thisIsOk : Symbol(thisIsOk, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 25, 3)) +>ClassA : Symbol(ClassA, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 0, 0)) +>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1)) +>ConcreteClass : Symbol(ConcreteClass, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 11, 1)) + + values: o => [ +>values : Symbol(values, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 25, 63)) +>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 26, 11)) + { + value: o.theName, +>value : Symbol(value, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 27, 9)) +>o.theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21)) +>o : Symbol(o, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 26, 11)) +>theName : Symbol(ConcreteClass.theName, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 12, 21)) + + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' +>func : Symbol(func, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 28, 29)) +>x : Symbol(x, Decl(inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts, 29, 17)) + } + ] +}); diff --git a/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types new file mode 100644 index 00000000000..bda9aae9a4e --- /dev/null +++ b/tests/baselines/reference/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.types @@ -0,0 +1,92 @@ +=== tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts === +class ClassA { +>ClassA : ClassA + + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { +>entity : TEntityClass +>settings : SettingsInterface + + } +} +export interface ValueInterface { + func?: (row: TValueClass) => any; +>func : (row: TValueClass) => any +>row : TValueClass + + value?: string; +>value : string +} +export interface SettingsInterface { + values?: (row: TClass) => ValueInterface[], +>values : (row: TClass) => ValueInterface[] +>row : TClass +} +class ConcreteClass { +>ConcreteClass : ConcreteClass + + theName = 'myClass'; +>theName : string +>'myClass' : "myClass" +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { +>thisGetsTheFalseError : ClassA +>new ClassA(new ConcreteClass(), { values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]}) : ClassA +>ClassA : typeof ClassA +>new ConcreteClass() : ConcreteClass +>ConcreteClass : typeof ConcreteClass +>{ values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]} : { values: (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]; } + + values: o => [ +>values : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[] +>o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[] +>o : ConcreteClass +>[ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : { value: string; func: (x: ConcreteClass) => string; }[] + { +>{ value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } : { value: string; func: (x: ConcreteClass) => string; } + + value: o.theName, +>value : string +>o.theName : string +>o : ConcreteClass +>theName : string + + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' +>func : (x: ConcreteClass) => string +>x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : (x: ConcreteClass) => string +>x : ConcreteClass +>'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : "asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj" + } + ] +}); + +var thisIsOk = new ClassA(new ConcreteClass(), { +>thisIsOk : ClassA +>new ClassA(new ConcreteClass(), { values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]}) : ClassA +>ClassA : typeof ClassA +>new ConcreteClass() : ConcreteClass +>ConcreteClass : typeof ConcreteClass +>{ values: o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ]} : { values: (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[]; } + + values: o => [ +>values : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[] +>o => [ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : (o: ConcreteClass) => { value: string; func: (x: ConcreteClass) => string; }[] +>o : ConcreteClass +>[ { value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } ] : { value: string; func: (x: ConcreteClass) => string; }[] + { +>{ value: o.theName, func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' } : { value: string; func: (x: ConcreteClass) => string; } + + value: o.theName, +>value : string +>o.theName : string +>o : ConcreteClass +>theName : string + + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' +>func : (x: ConcreteClass) => string +>x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : (x: ConcreteClass) => string +>x : ConcreteClass +>'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' : "asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj" + } + ] +}); diff --git a/tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts b/tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts new file mode 100644 index 00000000000..98c0f25ee7b --- /dev/null +++ b/tests/cases/compiler/inferenceDoesntCompareAgainstUninstantiatedTypeParameter.ts @@ -0,0 +1,33 @@ +class ClassA { + constructor(private entity?: TEntityClass, public settings?: SettingsInterface) { + + } +} +export interface ValueInterface { + func?: (row: TValueClass) => any; + value?: string; +} +export interface SettingsInterface { + values?: (row: TClass) => ValueInterface[], +} +class ConcreteClass { + theName = 'myClass'; +} + +var thisGetsTheFalseError = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); + +var thisIsOk = new ClassA(new ConcreteClass(), { + values: o => [ + { + value: o.theName, + func: x => 'asdfkjhgfdfghjkjhgfdfghjklkjhgfdfghjklkjhgfghj' + } + ] +}); \ No newline at end of file From ec4ca6b6199b26fde721d0e8ef332d6d61a93913 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 14:58:22 -0700 Subject: [PATCH 104/111] Slightly reorder experimental sync commands --- scripts/update-experimental-branches.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index 8af4df95cb9..7e395ae995b 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -30,9 +30,9 @@ async function main() { runSequence([ ["git", ["clean", "-fdx"]], ["git", ["checkout", "."]], + ["git", ["fetch", "-fu", "origin", "master:master"]], ["git", ["checkout", "master"]], ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork - ["git", ["fetch", "origin", "master:master"]], ]); const gh = new Octokit(); From 16030663ffe41310fcdb6cb7183ddc717fbddd10 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 15:18:00 -0700 Subject: [PATCH 105/111] Dont clean - pipeline should already be clean and a clean will clean node_modules --- scripts/update-experimental-branches.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index 7e395ae995b..49131a145c6 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -28,7 +28,6 @@ async function main() { // Forcibly cleanup workspace runSequence([ - ["git", ["clean", "-fdx"]], ["git", ["checkout", "."]], ["git", ["fetch", "-fu", "origin", "master:master"]], ["git", ["checkout", "master"]], From bddcf10eb8c8e9e8d6017ee5895db0eb154c098c Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 15:25:33 -0700 Subject: [PATCH 106/111] Fix deprecation warnings in experiment sync script --- scripts/update-experimental-branches.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index 49131a145c6..ddbf7a43827 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -34,23 +34,21 @@ async function main() { ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork ]); - const gh = new Octokit(); - gh.authenticate({ - type: "token", - token: process.argv[2] + const gh = new Octokit({ + auth: process.argv[2] }); for (const numRaw of prnums) { const num = +numRaw; if (num) { // PR number rather than branch name - lookup info - const inputPR = await gh.pulls.get({ owner: "Microsoft", repo: "TypeScript", number: num }); + const inputPR = await gh.pulls.get({ owner: "Microsoft", repo: "TypeScript", pull_number: num }); // GH calculates the rebaseable-ness of a PR into its target, so we can just use that here if (!inputPR.data.rebaseable) { if (+triggeredPR === num) { await gh.issues.createComment({ owner: "Microsoft", repo: "TypeScript", - number: num, + issue_number: num, body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.` }); throw new Error(`Merge conflict detected in PR ${num} with master`); From dcf2fa930d7172ea6e8a541d439dcebea7836c00 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 15:28:52 -0700 Subject: [PATCH 107/111] merge -> rebase in experiment sync script text --- scripts/update-experimental-branches.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index ddbf7a43827..4a496bbf783 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -49,9 +49,9 @@ async function main() { owner: "Microsoft", repo: "TypeScript", issue_number: num, - body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.` + body: `This PR is configured as an experiment, and currently has rebase conflicts with master - please rebase onto master and fix the conflicts.` }); - throw new Error(`Merge conflict detected in PR ${num} with master`); + throw new Error(`Rebase conflict detected in PR ${num} with master`); } return; // A PR is currently in conflict, give up } From 96a250502c2d11879d20741fe1864e18144ecd7e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 16:12:21 -0700 Subject: [PATCH 108/111] Deleting a branch that does not exist does not work (we should never download the ref anyway) --- scripts/update-experimental-branches.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index 4a496bbf783..07b089433ea 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -71,7 +71,6 @@ async function main() { // Return to `master` and make a new `experimental` branch runSequence([ ["git", ["checkout", "master"]], - ["git", ["branch", "-D", "experimental"]], ["git", ["checkout", "-b", "experimental"]], ]); From e6fde9e8094ca8553fcbb24ca34ca77f12349c3e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 5 Jun 2019 16:24:02 -0700 Subject: [PATCH 109/111] Taking typos out one line at a time --- scripts/update-experimental-branches.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js index 07b089433ea..b734c8a6fd8 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -84,7 +84,7 @@ async function main() { const mergeTree = runSequence([ ["git", ["merge-tree", mergeBase.trim(), branch, "experimental"]] ]); - if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker + if (mergeTree.indexOf(`===${"="}===`) >= 0) { // 7 equals is the center of the merge conflict marker throw new Error(`Merge conflict detected involving PR ${branch} with other experiment`); } // Merge (always producing a merge commit) From 3139cf2f857483bc9b5b74d9c101379e27d2fa9f Mon Sep 17 00:00:00 2001 From: Joey Watts Date: Thu, 6 Jun 2019 01:24:25 -0400 Subject: [PATCH 110/111] Move class property transformation into new transformer. (#30467) Signed-off-by: Joseph Watts --- src/compiler/binder.ts | 21 +- src/compiler/transformer.ts | 1 + src/compiler/transformers/classFields.ts | 491 +++++++++++++++++ src/compiler/transformers/ts.ts | 493 ++++++------------ src/compiler/transformers/utilities.ts | 80 +++ src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 1 + ...tPrivateSymbolCausesVarDeclarationEmit2.js | 4 +- .../decoratorsOnComputedProperties.js | 169 +++--- .../reference/systemModuleTargetES6.js | 2 +- ...arationEmitUniqueSymbolPartialStatement.js | 2 +- 11 files changed, 819 insertions(+), 446 deletions(-) create mode 100644 src/compiler/transformers/classFields.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4540a4409d6..660b19a5872 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3229,8 +3229,7 @@ namespace ts { // A ClassDeclaration is ES6 syntax. transformFlags = subtreeFlags | TransformFlags.AssertES2015; - // A class with a parameter property assignment, property initializer, computed property name, or decorator is - // TypeScript syntax. + // A class with a parameter property assignment or decorator is TypeScript syntax. // An exported declaration may be TypeScript syntax, but is handled by the visitor // for a namespace declaration. if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax) @@ -3247,8 +3246,7 @@ namespace ts { // A ClassExpression is ES6 syntax. let transformFlags = subtreeFlags | TransformFlags.AssertES2015; - // A class with a parameter property assignment, property initializer, or decorator is - // TypeScript syntax. + // A class with a parameter property assignment or decorator is TypeScript syntax. if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax || node.typeParameters) { transformFlags |= TransformFlags.AssertTypeScript; @@ -3338,7 +3336,6 @@ namespace ts { || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.typeParameters || node.type - || (node.name && isComputedPropertyName(node.name)) // While computed method names aren't typescript, the TS transform must visit them to emit property declarations correctly || !node.body) { transformFlags |= TransformFlags.AssertTypeScript; } @@ -3369,7 +3366,6 @@ namespace ts { if (node.decorators || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type - || (node.name && isComputedPropertyName(node.name)) // While computed accessor names aren't typescript, the TS transform must visit them to emit property declarations correctly || !node.body) { transformFlags |= TransformFlags.AssertTypeScript; } @@ -3384,12 +3380,15 @@ namespace ts { } function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) { - // A PropertyDeclaration is TypeScript syntax. - let transformFlags = subtreeFlags | TransformFlags.AssertTypeScript; + let transformFlags = subtreeFlags | TransformFlags.ContainsClassFields; - // If the PropertyDeclaration has an initializer or a computed name, we need to inform its ancestor - // so that it handle the transformation. - if (node.initializer || isComputedPropertyName(node.name)) { + // Decorators, TypeScript-specific modifiers, and type annotations are TypeScript syntax. + if (some(node.decorators) || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type) { + transformFlags |= TransformFlags.AssertTypeScript; + } + + // Hoisted variables related to class properties should live within the TypeScript class wrapper. + if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 1c1df9bb0c2..d520571e6df 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -44,6 +44,7 @@ namespace ts { addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); transformers.push(transformTypeScript); + transformers.push(transformClassFields); if (jsx === JsxEmit.React) { transformers.push(transformJsx); diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts new file mode 100644 index 00000000000..575e1d13346 --- /dev/null +++ b/src/compiler/transformers/classFields.ts @@ -0,0 +1,491 @@ +/*@internal*/ +namespace ts { + const enum ClassPropertySubstitutionFlags { + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the class name. + */ + ClassAliases = 1 << 0, + } + /** + * Transforms ECMAScript Class Syntax. + * TypeScript parameter property syntax is transformed in the TypeScript transformer. + * For now, this transforms public field declarations using TypeScript class semantics + * (where the declarations get elided and initializers are transformed as assignments in the constructor). + * Eventually, this transform will change to the ECMAScript semantics (with Object.defineProperty). + */ + export function transformClassFields(context: TransformationContext) { + const { + hoistVariableDeclaration, + endLexicalEnvironment, + resumeLexicalEnvironment + } = context; + const resolver = context.getEmitResolver(); + + const previousOnSubstituteNode = context.onSubstituteNode; + context.onSubstituteNode = onSubstituteNode; + + let enabledSubstitutions: ClassPropertySubstitutionFlags; + + let classAliases: Identifier[]; + + /** + * Tracks what computed name expressions originating from elided names must be inlined + * at the next execution site, in document order + */ + let pendingExpressions: Expression[] | undefined; + + /** + * Tracks what computed name expression statements and static property initializers must be + * emitted at the next execution site, in document order (for decorated classes). + */ + let pendingStatements: Statement[] | undefined; + + return chainBundle(transformSourceFile); + + function transformSourceFile(node: SourceFile) { + if (node.isDeclarationFile) { + return node; + } + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + return visited; + } + + function visitor(node: Node): VisitResult { + if (!(node.transformFlags & TransformFlags.ContainsClassFields)) return node; + + switch (node.kind) { + case SyntaxKind.ClassExpression: + return visitClassExpression(node as ClassExpression); + case SyntaxKind.ClassDeclaration: + return visitClassDeclaration(node as ClassDeclaration); + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + } + return visitEachChild(node, visitor, context); + } + + /** + * Visits the members of a class that has fields. + * + * @param node The node to visit. + */ + function classElementVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.Constructor: + // Constructors for classes using class fields are transformed in + // `visitClassDeclaration` or `visitClassExpression`. + return undefined; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + // Visit the name of the member (if it's a computed property name). + return visitEachChild(node, classElementVisitor, context); + + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration(node as PropertyDeclaration); + + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName(node as ComputedPropertyName); + + default: + return node; + } + } + + function visitVariableStatement(node: VariableStatement) { + const savedPendingStatements = pendingStatements; + pendingStatements = []; + + const visitedNode = visitEachChild(node, visitor, context); + const statement = some(pendingStatements) ? + [visitedNode, ...pendingStatements] : + visitedNode; + + pendingStatements = savedPendingStatements; + return statement; + } + + function visitComputedPropertyName(name: ComputedPropertyName) { + let node = visitEachChild(name, visitor, context); + if (some(pendingExpressions)) { + const expressions = pendingExpressions; + expressions.push(name.expression); + pendingExpressions = []; + node = updateComputedPropertyName( + node, + inlineExpressions(expressions) + ); + } + return node; + } + + function visitPropertyDeclaration(node: PropertyDeclaration) { + Debug.assert(!some(node.decorators)); + // Create a temporary variable to store a computed property name (if necessary). + // If it's not inlineable, then we emit an expression after the class which assigns + // the property name to the temporary variable. + const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer); + if (expr && !isSimpleInlineableExpression(expr)) { + (pendingExpressions || (pendingExpressions = [])).push(expr); + } + return undefined; + } + + function visitClassDeclaration(node: ClassDeclaration) { + if (!forEach(node.members, isPropertyDeclaration)) { + return visitEachChild(node, visitor, context); + } + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + + const statements: Statement[] = [ + updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + transformClassMembers(node, isDerivedClass) + ) + ]; + + // Write any pending expressions from elided or moved computed property names + if (some(pendingExpressions)) { + statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!))); + } + pendingExpressions = savedPendingExpressions; + + // Emit static property assignment. Because classDeclaration is lexically evaluated, + // it is safe to emit static property assignment after classDeclaration + // From ES6 specification: + // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using + // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + if (some(staticProperties)) { + addInitializedPropertyStatements(statements, staticProperties, getInternalName(node)); + } + + return statements; + } + + function visitClassExpression(node: ClassExpression): Expression { + if (!forEach(node.members, isPropertyDeclaration)) { + return visitEachChild(node, visitor, context); + } + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + + // If this class expression is a transformation of a decorated class declaration, + // then we want to output the pendingExpressions as statements, not as inlined + // expressions with the class statement. + // + // In this case, we use pendingStatements to produce the same output as the + // class declaration transformation. The VariableStatement visitor will insert + // these statements after the class expression variable statement. + const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node)); + + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + + const classExpression = updateClassExpression( + node, + node.modifiers, + node.name, + node.typeParameters, + visitNodes(node.heritageClauses, visitor, isHeritageClause), + transformClassMembers(node, isDerivedClass) + ); + + if (some(staticProperties) || some(pendingExpressions)) { + if (isDecoratedClassDeclaration) { + Debug.assertDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); + + // Write any pending expressions from elided or moved computed property names + if (pendingStatements && pendingExpressions && some(pendingExpressions)) { + pendingStatements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); + } + pendingExpressions = savedPendingExpressions; + + if (pendingStatements && some(staticProperties)) { + addInitializedPropertyStatements(pendingStatements, staticProperties, getInternalName(node)); + } + return classExpression; + } + else { + const expressions: Expression[] = []; + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); + if (isClassWithConstructorReference) { + // record an alias as the class name is not in scope for statics. + enableSubstitutionForClassAliases(); + const alias = getSynthesizedClone(temp); + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; + } + + // To preserve the behavior of the old emitter, we explicitly indent + // the body of a class with static initializers. + setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); + expressions.push(startOnNewLine(createAssignment(temp, classExpression))); + // Add any pending expressions leftover from elided or relocated computed property names + addRange(expressions, map(pendingExpressions, startOnNewLine)); + addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); + expressions.push(startOnNewLine(temp)); + + pendingExpressions = savedPendingExpressions; + return inlineExpressions(expressions); + } + } + + pendingExpressions = savedPendingExpressions; + return classExpression; + } + + function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const members: ClassElement[] = []; + const constructor = transformConstructor(node, isDerivedClass); + if (constructor) { + members.push(constructor); + } + addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); + return setTextRange(createNodeArray(members), /*location*/ node.members); + } + + function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); + const containsPropertyInitializer = forEach(node.members, isInitializedProperty); + if (!containsPropertyInitializer) { + return constructor; + } + const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); + const body = transformConstructorBody(node, constructor, isDerivedClass); + if (!body) { + return undefined; + } + return startOnNewLine( + setOriginalNode( + setTextRange( + createConstructor( + /*decorators*/ undefined, + /*modifiers*/ undefined, + parameters, + body + ), + constructor || node + ), + constructor + ) + ); + } + + function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + const properties = getInitializedProperties(node, /*isStatic*/ false); + + // Only generate synthetic constructor when there are property initializers to move. + if (!constructor && !some(properties)) { + return visitFunctionBody(/*node*/ undefined, visitor, context); + } + + resumeLexicalEnvironment(); + + let indexOfFirstStatement = 0; + let statements: Statement[] = []; + + if (!constructor && isDerivedClass) { + // Add a synthetic `super` call: + // + // super(...arguments); + // + statements.push( + createExpressionStatement( + createCall( + createSuper(), + /*typeArguments*/ undefined, + [createSpread(createIdentifier("arguments"))] + ) + ) + ); + } + + if (constructor) { + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); + } + + // Add the property initializers. Transforms this: + // + // public x = 1; + // + // Into this: + // + // constructor() { + // this.x = 1; + // } + // + addInitializedPropertyStatements(statements, properties, createThis()); + + // Add existing statements, skipping the initial super call. + if (constructor) { + addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); + } + + statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); + + return setTextRange( + createBlock( + setTextRange( + createNodeArray(statements), + /*location*/ constructor ? constructor.body!.statements : node.members + ), + /*multiLine*/ true + ), + /*location*/ constructor ? constructor.body : undefined + ); + } + + /** + * Generates assignment statements for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { + for (const property of properties) { + const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + setOriginalNode(statement, property); + statements.push(statement); + } + } + + /** + * Generates assignment expressions for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) { + const expressions: Expression[] = []; + for (const property of properties) { + const expression = transformInitializedProperty(property, receiver); + startOnNewLine(expression); + setSourceMapRange(expression, moveRangePastModifiers(property)); + setCommentRange(expression, property); + setOriginalNode(expression, property); + expressions.push(expression); + } + + return expressions; + } + + /** + * Transforms a property initializer into an assignment statement. + * + * @param property The property declaration. + * @param receiver The object receiving the property assignment. + */ + function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { + // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) + const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) + ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name)) + : property.name; + const initializer = visitNode(property.initializer!, visitor, isExpression); + const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); + + return createAssignment(memberAccess, initializer); + } + + function enableSubstitutionForClassAliases() { + if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { + enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; + + // We need to enable substitutions for identifiers. This allows us to + // substitute class names inside of a class declaration. + context.enableSubstitution(SyntaxKind.Identifier); + + // Keep track of class aliases. + classAliases = []; + } + } + + /** + * Hooks node substitutions. + * + * @param hint The context for the emitter. + * @param node The node to substitute. + */ + function onSubstituteNode(hint: EmitHint, node: Node) { + node = previousOnSubstituteNode(hint, node); + if (hint === EmitHint.Expression) { + return substituteExpression(node as Expression); + } + return node; + } + + function substituteExpression(node: Expression) { + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier(node as Identifier); + } + return node; + } + + function substituteExpressionIdentifier(node: Identifier): Expression { + return trySubstituteClassAlias(node) || node; + } + + function trySubstituteClassAlias(node: Identifier): Expression | undefined { + if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + // Also, when emitting statics for class expressions, we must substitute a class alias for + // constructor references in static property initializers. + const declaration = resolver.getReferencedValueDeclaration(node); + if (declaration) { + const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 + if (classAlias) { + const clone = getSynthesizedClone(classAlias); + setSourceMapRange(clone, node); + setCommentRange(clone, node); + return clone; + } + } + } + } + + return undefined; + } + + + /** + * If the name is a computed property, this function transforms it, then either returns an expression which caches the + * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations + * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) + */ + function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { + if (isComputedPropertyName(name)) { + const expression = visitNode(name.expression, visitor, isExpression); + const innerExpression = skipPartiallyEmittedExpressions(expression); + const inlinable = isSimpleInlineableExpression(innerExpression); + const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); + if (!alreadyTransformed && !inlinable && shouldHoist) { + const generatedName = getGeneratedNameForNode(name); + hoistVariableDeclaration(generatedName); + return createAssignment(generatedName, expression); + } + return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; + } + } + } + +} diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index a70fb820e79..adfe16c73f6 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -64,6 +64,7 @@ namespace ts { let currentLexicalScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentNameScope: ClassDeclaration | undefined; let currentScopeFirstDeclarationsOfName: UnderscoreEscapedMap | undefined; + let currentClassHasParameterProperties: boolean | undefined; /** * Keeps track of whether expression substitution has been enabled for specific edge cases. @@ -83,12 +84,6 @@ namespace ts { */ let applicableSubstitutions: TypeScriptSubstitutionFlags; - /** - * Tracks what computed name expressions originating from elided names must be inlined - * at the next execution site, in document order - */ - let pendingExpressions: Expression[] | undefined; - return transformSourceFileOrBundle; function transformSourceFileOrBundle(node: SourceFile | Bundle) { @@ -136,6 +131,7 @@ namespace ts { const savedCurrentScope = currentLexicalScope; const savedCurrentNameScope = currentNameScope; const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName; + const savedCurrentClassHasParameterProperties = currentClassHasParameterProperties; // Handle state changes before visiting a node. onBeforeVisitNode(node); @@ -149,6 +145,7 @@ namespace ts { currentLexicalScope = savedCurrentScope; currentNameScope = savedCurrentNameScope; + currentClassHasParameterProperties = savedCurrentClassHasParameterProperties; return visited; } @@ -321,6 +318,9 @@ namespace ts { return undefined; case SyntaxKind.PropertyDeclaration: + // Property declarations are not TypeScript syntax, but they must be visited + // for the decorator transformation. + return visitPropertyDeclaration(node as PropertyDeclaration); case SyntaxKind.IndexSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: @@ -437,7 +437,6 @@ namespace ts { // - decorators // - optional `implements` heritage clause // - parameter property assignments in the constructor - // - property declarations // - index signatures // - method overload signatures return visitClassDeclaration(node); @@ -449,7 +448,6 @@ namespace ts { // - decorators // - optional `implements` heritage clause // - parameter property assignments in the constructor - // - property declarations // - index signatures // - method overload signatures return visitClassExpression(node); @@ -611,10 +609,6 @@ namespace ts { if (!isClassLikeDeclarationWithTypeScriptSyntax(node) && !(currentNamespace && hasModifier(node, ModifierFlags.Export))) { return visitEachChild(node, visitor, context); } - - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - const staticProperties = getInitializedProperties(node, /*isStatic*/ true); const facts = getClassFacts(node, staticProperties); @@ -624,25 +618,11 @@ namespace ts { const name = node.name || (facts & ClassFacts.NeedsName ? getGeneratedNameForNode(node) : undefined); const classStatement = facts & ClassFacts.HasConstructorDecorators - ? createClassDeclarationHeadWithDecorators(node, name, facts) + ? createClassDeclarationHeadWithDecorators(node, name) : createClassDeclarationHeadWithoutDecorators(node, name, facts); let statements: Statement[] = [classStatement]; - // Write any pending expressions from elided or moved computed property names - if (some(pendingExpressions)) { - statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!))); - } - pendingExpressions = savedPendingExpressions; - - // Emit static property assignment. Because classDeclaration is lexically evaluated, - // it is safe to emit static property assignment after classDeclaration - // From ES6 specification: - // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using - // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. - if (facts & ClassFacts.HasStaticInitializedProperties) { - addInitializedPropertyStatements(statements, staticProperties, facts & ClassFacts.UseImmediatelyInvokedFunctionExpression ? getInternalName(node) : getLocalName(node)); - } // Write any decorators of the node. addClassElementDecorationStatements(statements, node, /*isStatic*/ false); @@ -745,7 +725,7 @@ namespace ts { name, /*typeParameters*/ undefined, visitNodes(node.heritageClauses, visitor, isHeritageClause), - transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0) + transformClassMembers(node) ); // To better align with the old emitter, we should not emit a trailing source map @@ -765,7 +745,7 @@ namespace ts { * Transforms a decorated class declaration and appends the resulting statements. If * the class requires an alias to avoid issues with double-binding, the alias is returned. */ - function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined, facts: ClassFacts) { + function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined) { // When we emit an ES6 class that has a class decorator, we must tailor the // emit to certain specific cases. // @@ -860,7 +840,7 @@ namespace ts { // ${members} // } const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0); + const members = transformClassMembers(node); const classExpression = createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members); setOriginalNode(classExpression, node); setTextRange(classExpression, location); @@ -888,49 +868,19 @@ namespace ts { return visitEachChild(node, visitor, context); } - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - - const staticProperties = getInitializedProperties(node, /*isStatic*/ true); - const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = transformClassMembers(node, some(heritageClauses, c => c.token === SyntaxKind.ExtendsKeyword)); + const members = transformClassMembers(node); const classExpression = createClassExpression( /*modifiers*/ undefined, node.name, /*typeParameters*/ undefined, - heritageClauses, + node.heritageClauses, members ); setOriginalNode(classExpression, node); setTextRange(classExpression, node); - if (some(staticProperties) || some(pendingExpressions)) { - const expressions: Expression[] = []; - const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; - const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); - if (isClassWithConstructorReference) { - // record an alias as the class name is not in scope for statics. - enableSubstitutionForClassAliases(); - const alias = getSynthesizedClone(temp); - alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; - classAliases[getOriginalNodeId(node)] = alias; - } - - // To preserve the behavior of the old emitter, we explicitly indent - // the body of a class with static initializers. - setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); - expressions.push(startOnNewLine(createAssignment(temp, classExpression))); - // Add any pending expressions leftover from elided or relocated computed property names - addRange(expressions, map(pendingExpressions, startOnNewLine)); - pendingExpressions = savedPendingExpressions; - addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); - expressions.push(startOnNewLine(temp)); - return inlineExpressions(expressions); - } - - pendingExpressions = savedPendingExpressions; return classExpression; } @@ -938,61 +888,81 @@ namespace ts { * Transforms the members of a class. * * @param node The current class. - * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'. */ - function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + function transformClassMembers(node: ClassDeclaration | ClassExpression) { const members: ClassElement[] = []; - const constructor = transformConstructor(node, isDerivedClass); - if (constructor) { - members.push(constructor); - } - - addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); - return setTextRange(createNodeArray(members), /*location*/ node.members); - } - - /** - * Transforms (or creates) a constructor for a class. - * - * @param node The current class. - * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'. - */ - function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { - // Check if we have property assignment inside class declaration. - // If there is a property assignment, we need to emit constructor whether users define it or not - // If there is no property assignment, we can omit constructor if users do not define it + const existingMembers = visitNodes(node.members, classElementVisitor, isClassElement); const constructor = getFirstConstructorWithBody(node); - const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty); - const hasParameterPropertyAssignments = constructor && - constructor.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax && - forEach(constructor.parameters, isParameterWithPropertyAssignment); + const parametersWithPropertyAssignments = + constructor && hasTypeScriptClassSyntax(constructor) + ? filter(constructor.parameters, isParameterPropertyDeclaration) + : undefined; + if (some(parametersWithPropertyAssignments) && constructor) { + currentClassHasParameterProperties = true; - // If the class does not contain nodes that require a synthesized constructor, - // accept the current constructor if it exists. - if (!hasInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { - return visitEachChild(constructor, visitor, context); - } - - const parameters = transformConstructorParameters(constructor); - const body = transformConstructorBody(node, constructor, isDerivedClass); - - // constructor(${parameters}) { - // ${body} - // } - return startOnNewLine( - setOriginalNode( - setTextRange( - createConstructor( + // Create property declarations for constructor parameter properties. + addRange( + members, + parametersWithPropertyAssignments.map(param => + createProperty( /*decorators*/ undefined, /*modifiers*/ undefined, - parameters, - body + param.name, + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ) + ) + ); + + const parameters = transformConstructorParameters(constructor); + const body = transformConstructorBody(node.members, constructor, parametersWithPropertyAssignments); + members.push(startOnNewLine( + setOriginalNode( + setTextRange( + createConstructor( + /*decorators*/ undefined, + /*modifiers*/ undefined, + parameters, + body + ), + constructor ), - constructor || node - ), - constructor - ) - ); + constructor + ) + )); + addRange( + members, + visitNodes( + existingMembers, + member => { + if (isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) { + const updated = updateProperty( + member, + member.decorators, + member.modifiers, + member.name, + member.questionToken, + member.type, + /*initializer*/ undefined + ); + setCommentRange(updated, node); + setSourceMapRange(updated, node); + return updated; + } + return member; + }, + isClassElement + ) + ); + } + else { + if (constructor) { + members.push(visitEachChild(constructor, visitor, context)); + } + addRange(members, existingMembers); + } + return setTextRange(createNodeArray(members), /*location*/ node.members); } /** @@ -1001,7 +971,7 @@ namespace ts { * * @param constructor The constructor declaration. */ - function transformConstructorParameters(constructor: ConstructorDeclaration | undefined) { + function transformConstructorParameters(constructor: ConstructorDeclaration) { // The ES2015 spec specifies in 14.5.14. Runtime Semantics: ClassDefinitionEvaluation: // If constructor is empty, then // If ClassHeritag_eopt is present and protoParent is not null, then @@ -1022,70 +992,54 @@ namespace ts { } /** - * Transforms (or creates) a constructor body for a class with parameter property - * assignments or instance property initializers. + * Transforms (or creates) a constructor body for a class with parameter property assignments. * * @param node The current class. * @param constructor The current class constructor. - * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'. */ - function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + function transformConstructorBody(members: NodeArray, constructor: ConstructorDeclaration, propertyAssignments: ReadonlyArray) { let statements: Statement[] = []; let indexOfFirstStatement = 0; resumeLexicalEnvironment(); - if (constructor) { - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements); + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); - // Add parameters with property assignments. Transforms this: - // - // constructor (public x, public y) { - // } - // - // Into this: - // - // constructor (x, y) { - // this.x = x; - // this.y = y; - // } - // - const propertyAssignments = getParametersWithPropertyAssignments(constructor); - addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment)); - } - else if (isDerivedClass) { - // Add a synthetic `super` call: - // - // super(...arguments); - // - statements.push( - createExpressionStatement( - createCall( - createSuper(), - /*typeArguments*/ undefined, - [createSpread(createIdentifier("arguments"))] - ) - ) - ); - } - - // Add the property initializers. Transforms this: + // Add parameters with property assignments. Transforms this: // - // public x = 1; + // constructor (public x, public y) { + // } // // Into this: // - // constructor() { - // this.x = 1; + // constructor (x, y) { + // this.x = x; + // this.y = y; // } // - const properties = getInitializedProperties(node, /*isStatic*/ false); - addInitializedPropertyStatements(statements, properties, createThis()); + addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment)); - if (constructor) { - // The class already had a constructor, so we should add the existing statements, skipping the initial super call. - addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); - } + // Get property initializers. + const classBodyProperties = members.filter(member => isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) as PropertyDeclaration[]; + addRange(statements, classBodyProperties.map( + prop => { + const name = prop.name; + const lhs = (!isComputedPropertyName(name) || isSimpleInlineableExpression(name.expression)) ? + createMemberAccessForPropertyName(createThis(), name, prop) : + createElementAccess(createThis(), getGeneratedNameForNode(name)); + const initializerNode = createExpressionStatement( + createAssignment(lhs, prop.initializer!) + ); + setOriginalNode(initializerNode, prop); + setTextRange(initializerNode, prop); + setCommentRange(initializerNode, prop); + setSourceMapRange(initializerNode, prop); + return initializerNode; + } + )); + + // Add the existing statements, skipping the initial super call. + addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); // End the lexical environment. statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); @@ -1093,7 +1047,7 @@ namespace ts { createBlock( setTextRange( createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : node.members + /*location*/ constructor ? constructor.body!.statements : members ), /*multiLine*/ true ), @@ -1101,61 +1055,16 @@ namespace ts { ); } - /** - * Adds super call and preceding prologue directives into the list of statements. - * - * @param ctor The constructor node. - * @returns index of the statement that follows super call - */ - function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[]): number { - if (ctor.body) { - const statements = ctor.body.statements; - // add prologue directives to the list (if any) - const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor); - if (index === statements.length) { - // list contains nothing but prologue directives (or empty) - exit - return index; - } - - const statement = statements[index]; - if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { - result.push(visitNode(statement, visitor, isStatement)); - return index + 1; - } - - return index; - } - - return 0; - } - - /** - * Gets all parameters of a constructor that should be transformed into property assignments. - * - * @param node The constructor node. - */ - function getParametersWithPropertyAssignments(node: ConstructorDeclaration): ReadonlyArray { - return filter(node.parameters, isParameterWithPropertyAssignment); - } - - /** - * Determines whether a parameter should be transformed into a property assignment. - * - * @param parameter The parameter node. - */ - function isParameterWithPropertyAssignment(parameter: ParameterDeclaration) { - return hasModifier(parameter, ModifierFlags.ParameterPropertyModifier) - && isIdentifier(parameter.name); - } - /** * Transforms a parameter into a property assignment statement. * * @param node The parameter declaration. */ - function transformParameterWithPropertyAssignment(node: ParameterDeclaration) { - Debug.assert(isIdentifier(node.name)); - const name = node.name as Identifier; + function transformParameterWithPropertyAssignment(node: ParameterPropertyDeclaration) { + const name = node.name; + if (!isIdentifier(name)) { + return undefined; + } const propertyName = getMutableClone(name); setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap); @@ -1184,99 +1093,6 @@ namespace ts { ); } - /** - * Gets all property declarations with initializers on either the static or instance side of a class. - * - * @param node The class node. - * @param isStatic A value indicating whether to get properties from the static or instance side of the class. - */ - function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray { - return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty); - } - - /** - * Gets a value indicating whether a class element is a static property declaration with an initializer. - * - * @param member The class element node. - */ - function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration { - return isInitializedProperty(member, /*isStatic*/ true); - } - - /** - * Gets a value indicating whether a class element is an instance property declaration with an initializer. - * - * @param member The class element node. - */ - function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration { - return isInitializedProperty(member, /*isStatic*/ false); - } - - /** - * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. - * - * @param member The class element node. - * @param isStatic A value indicating whether the member should be a static or instance member. - */ - function isInitializedProperty(member: ClassElement, isStatic: boolean) { - return member.kind === SyntaxKind.PropertyDeclaration - && isStatic === hasModifier(member, ModifierFlags.Static) - && (member).initializer !== undefined; - } - - /** - * Generates assignment statements for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { - for (const property of properties) { - const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - setOriginalNode(statement, property); - statements.push(statement); - } - } - - /** - * Generates assignment expressions for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) { - const expressions: Expression[] = []; - for (const property of properties) { - const expression = transformInitializedProperty(property, receiver); - startOnNewLine(expression); - setSourceMapRange(expression, moveRangePastModifiers(property)); - setCommentRange(expression, property); - setOriginalNode(expression, property); - expressions.push(expression); - } - - return expressions; - } - - /** - * Transforms a property initializer into an assignment statement. - * - * @param property The property declaration. - * @param receiver The object receiving the property assignment. - */ - function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { - // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) - const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) - ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name)) - : property.name; - const initializer = visitNode(property.initializer!, visitor, isExpression); - const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); - - return createAssignment(memberAccess, initializer); - } - /** * Gets either the static or instance members of a class that are decorated, or have * parameters that are decorated. @@ -2144,16 +1960,6 @@ namespace ts { : createIdentifier("BigInt"); } - /** - * A simple inlinable expression is an expression which can be copied into multiple locations - * without risk of repeating any sideeffects and whose value could not possibly change between - * any such locations - */ - function isSimpleInlineableExpression(expression: Expression) { - return !isIdentifier(expression) && isSimpleCopiableExpression(expression) || - isWellKnownSymbolSyntactically(expression); - } - /** * Gets an expression that represents a property name. For a computed property, a * name is generated for the node. @@ -2175,26 +1981,6 @@ namespace ts { } } - /** - * If the name is a computed property, this function transforms it, then either returns an expression which caches the - * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations - * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) - * @param omitSimple Should expressions with no observable side-effects be elided? (ie, the expression is not hoisted for a decorator or initializer and is a literal) - */ - function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean, omitSimple: boolean): Expression | undefined { - if (isComputedPropertyName(name)) { - const expression = visitNode(name.expression, visitor, isExpression); - const innerExpression = skipPartiallyEmittedExpressions(expression); - const inlinable = isSimpleInlineableExpression(innerExpression); - if (!inlinable && shouldHoist) { - const generatedName = getGeneratedNameForNode(name); - hoistVariableDeclaration(generatedName); - return createAssignment(generatedName, expression); - } - return (omitSimple && (inlinable || isIdentifier(innerExpression))) ? undefined : expression; - } - } - /** * Visits the property name of a class element, for use when emitting property * initializers. For a computed property on a node with decorators, a temporary @@ -2204,18 +1990,20 @@ namespace ts { */ function visitPropertyNameOfClassElement(member: ClassElement): PropertyName { const name = member.name!; - let expr = getPropertyNameExpressionIfNeeded(name, some(member.decorators), /*omitSimple*/ false); - if (expr) { // expr only exists if `name` is a computed property name - // Inline any pending expressions from previous elided or relocated computed property name expressions in order to preserve execution order - if (some(pendingExpressions)) { - expr = inlineExpressions([...pendingExpressions, expr]); - pendingExpressions.length = 0; + // Computed property names need to be transformed into a hoisted variable when they are used more than once. + // The names are used more than once when: + // - the property is non-static and its initializer is moved to the constructor (when there are parameter property assignments). + // - the property has a decorator. + if (isComputedPropertyName(name) && ((!hasStaticModifier(member) && currentClassHasParameterProperties) || some(member.decorators))) { + const expression = visitNode(name.expression, visitor, isExpression); + const innerExpression = skipPartiallyEmittedExpressions(expression); + if (!isSimpleInlineableExpression(innerExpression)) { + const generatedName = getGeneratedNameForNode(name); + hoistVariableDeclaration(generatedName); + return updateComputedPropertyName(name, createAssignment(generatedName, expression)); } - return updateComputedPropertyName(name as ComputedPropertyName, expr); - } - else { - return name; } + return visitNode(name, visitor, isPropertyName); } /** @@ -2261,12 +2049,23 @@ namespace ts { return !nodeIsMissing(node.body); } - function visitPropertyDeclaration(node: PropertyDeclaration): undefined { - const expr = getPropertyNameExpressionIfNeeded(node.name, some(node.decorators) || !!node.initializer, /*omitSimple*/ true); - if (expr && !isSimpleInlineableExpression(expr)) { - (pendingExpressions || (pendingExpressions = [])).push(expr); + function visitPropertyDeclaration(node: PropertyDeclaration) { + const updated = updateProperty( + node, + /*decorators*/ undefined, + visitNodes(node.modifiers, visitor, isModifier), + visitPropertyNameOfClassElement(node), + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + visitNode(node.initializer, visitor) + ); + if (updated !== node) { + // While we emit the source map for the node after skipping decorators and modifiers, + // we need to emit the comments for the original range. + setCommentRange(updated, node); + setSourceMapRange(updated, moveRangePastDecorators(node)); } - return undefined; + return updated; } function visitConstructor(node: ConstructorDeclaration) { diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index 09239d7765f..f5f8a298a49 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -240,6 +240,47 @@ namespace ts { isIdentifier(expression); } + /** + * A simple inlinable expression is an expression which can be copied into multiple locations + * without risk of repeating any sideeffects and whose value could not possibly change between + * any such locations + */ + export function isSimpleInlineableExpression(expression: Expression) { + return !isIdentifier(expression) && isSimpleCopiableExpression(expression) || + isWellKnownSymbolSyntactically(expression); + } + + /** + * Adds super call and preceding prologue directives into the list of statements. + * + * @param ctor The constructor node. + * @param result The list of statements. + * @param visitor The visitor to apply to each node added to the result array. + * @returns index of the statement that follows super call + */ + export function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number { + if (ctor.body) { + const statements = ctor.body.statements; + // add prologue directives to the list (if any) + const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor); + if (index === statements.length) { + // list contains nothing but prologue directives (or empty) - exit + return index; + } + + const statement = statements[index]; + if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { + result.push(visitNode(statement, visitor, isStatement)); + return index + 1; + } + + return index; + } + + return 0; + } + + /** * @param input Template string input strings * @param args Names which need to be made file-level unique @@ -255,4 +296,43 @@ namespace ts { return result; }; } + + /** + * Gets all property declarations with initializers on either the static or instance side of a class. + * + * @param node The class node. + * @param isStatic A value indicating whether to get properties from the static or instance side of the class. + */ + export function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray { + return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty); + } + + /** + * Gets a value indicating whether a class element is a static property declaration with an initializer. + * + * @param member The class element node. + */ + export function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { + return isInitializedProperty(member) && hasStaticModifier(member); + } + + /** + * Gets a value indicating whether a class element is an instance property declaration with an initializer. + * + * @param member The class element node. + */ + export function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { + return isInitializedProperty(member) && !hasStaticModifier(member); + } + + /** + * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. + * + * @param member The class element node. + * @param isStatic A value indicating whether the member should be a static or instance member. + */ + export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { + return member.kind === SyntaxKind.PropertyDeclaration + && (member).initializer !== undefined; + } } \ No newline at end of file diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index fdc0ff2969f..f5e16b0d127 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -30,6 +30,7 @@ "transformers/utilities.ts", "transformers/destructuring.ts", "transformers/ts.ts", + "transformers/classFields.ts", "transformers/es2017.ts", "transformers/es2018.ts", "transformers/es2019.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a7ee1c5745f..31fb4984507 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5197,6 +5197,7 @@ namespace ts { ContainsYield = 1 << 17, ContainsHoistedDeclarationOrCompletion = 1 << 18, ContainsDynamicImport = 1 << 19, + ContainsClassFields = 1 << 20, // Please leave this as 1 << 29. // It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system. diff --git a/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js b/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js index ab986aba1e8..970590c136b 100644 --- a/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js +++ b/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js @@ -34,8 +34,8 @@ var C = /** @class */ (function () { } return C; }()); -_a = a_1.x; exports.C = C; +_a = a_1.x; //// [c.js] "use strict"; var __extends = (this && this.__extends) || (function () { @@ -64,8 +64,8 @@ var D = /** @class */ (function (_super) { } return D; }(b_1.C)); -_a = a_1.x; exports.D = D; +_a = a_1.x; //// [a.d.ts] diff --git a/tests/baselines/reference/decoratorsOnComputedProperties.js b/tests/baselines/reference/decoratorsOnComputedProperties.js index 2af6b460c05..3f0444b310a 100644 --- a/tests/baselines/reference/decoratorsOnComputedProperties.js +++ b/tests/baselines/reference/decoratorsOnComputedProperties.js @@ -196,7 +196,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; -var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21; +var _a, _b, _c, _d; +var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21; function x(o, k) { } let i = 0; function foo() { return ++i + ""; } @@ -209,11 +210,11 @@ class A { this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_b] = null; - this[_d] = null; + this[_f] = null; + this[_h] = null; } } -foo(), _a = foo(), _b = foo(), _c = fieldNameB, _d = fieldNameC; +foo(), _e = foo(), _f = foo(), _g = fieldNameB, _h = fieldNameC; __decorate([ x ], A.prototype, "property", void 0); @@ -228,42 +229,42 @@ __decorate([ ], A.prototype, Symbol.iterator, void 0); __decorate([ x -], A.prototype, _a, void 0); +], A.prototype, _e, void 0); __decorate([ x -], A.prototype, _b, void 0); +], A.prototype, _f, void 0); __decorate([ x -], A.prototype, _c, void 0); +], A.prototype, _g, void 0); __decorate([ x -], A.prototype, _d, void 0); -void (_j = class B { +], A.prototype, _h, void 0); +void (_a = class B { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_f] = null; - this[_h] = null; + this[_k] = null; + this[_m] = null; } }, foo(), - _e = foo(), - _f = foo(), - _g = fieldNameB, - _h = fieldNameC, - _j); + _j = foo(), + _k = foo(), + _l = fieldNameB, + _m = fieldNameC, + _a); class C { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_l] = null; - this[_o] = null; + this[_p] = null; + this[_r] = null; } - [(foo(), _k = foo(), _l = foo(), _m = fieldNameB, _o = fieldNameC, "some" + "method")]() { } + [(foo(), _o = foo(), _p = foo(), _q = fieldNameB, _r = fieldNameC, "some" + "method")]() { } } __decorate([ x @@ -277,28 +278,28 @@ __decorate([ __decorate([ x ], C.prototype, Symbol.iterator, void 0); -__decorate([ - x -], C.prototype, _k, void 0); -__decorate([ - x -], C.prototype, _l, void 0); -__decorate([ - x -], C.prototype, _m, void 0); __decorate([ x ], C.prototype, _o, void 0); +__decorate([ + x +], C.prototype, _p, void 0); +__decorate([ + x +], C.prototype, _q, void 0); +__decorate([ + x +], C.prototype, _r, void 0); void class D { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_q] = null; - this[_s] = null; + this[_t] = null; + this[_v] = null; } - [(foo(), _p = foo(), _q = foo(), _r = fieldNameB, _s = fieldNameC, "some" + "method")]() { } + [(foo(), _s = foo(), _t = foo(), _u = fieldNameB, _v = fieldNameC, "some" + "method")]() { } }; class E { constructor() { @@ -306,12 +307,12 @@ class E { this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_u] = null; - this[_w] = null; + this[_x] = null; + this[_z] = null; } - [(foo(), _t = foo(), _u = foo(), "some" + "method")]() { } + [(foo(), _w = foo(), _x = foo(), "some" + "method")]() { } } -_v = fieldNameB, _w = fieldNameC; +_y = fieldNameB, _z = fieldNameC; __decorate([ x ], E.prototype, "property", void 0); @@ -324,45 +325,45 @@ __decorate([ __decorate([ x ], E.prototype, Symbol.iterator, void 0); -__decorate([ - x -], E.prototype, _t, void 0); -__decorate([ - x -], E.prototype, _u, void 0); -__decorate([ - x -], E.prototype, _v, void 0); __decorate([ x ], E.prototype, _w, void 0); -void (_1 = class F { +__decorate([ + x +], E.prototype, _x, void 0); +__decorate([ + x +], E.prototype, _y, void 0); +__decorate([ + x +], E.prototype, _z, void 0); +void (_b = class F { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_y] = null; - this[_0] = null; + this[_1] = null; + this[_3] = null; } - [(foo(), _x = foo(), _y = foo(), "some" + "method")]() { } + [(foo(), _0 = foo(), _1 = foo(), "some" + "method")]() { } }, - _z = fieldNameB, - _0 = fieldNameC, - _1); + _2 = fieldNameB, + _3 = fieldNameC, + _b); class G { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_3] = null; this[_5] = null; + this[_7] = null; } - [(foo(), _2 = foo(), _3 = foo(), "some" + "method")]() { } - [(_4 = fieldNameB, "some" + "method2")]() { } + [(foo(), _4 = foo(), _5 = foo(), "some" + "method")]() { } + [(_6 = fieldNameB, "some" + "method2")]() { } } -_5 = fieldNameC; +_7 = fieldNameC; __decorate([ x ], G.prototype, "property", void 0); @@ -375,45 +376,45 @@ __decorate([ __decorate([ x ], G.prototype, Symbol.iterator, void 0); -__decorate([ - x -], G.prototype, _2, void 0); -__decorate([ - x -], G.prototype, _3, void 0); __decorate([ x ], G.prototype, _4, void 0); __decorate([ x ], G.prototype, _5, void 0); -void (_10 = class H { +__decorate([ + x +], G.prototype, _6, void 0); +__decorate([ + x +], G.prototype, _7, void 0); +void (_c = class H { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_7] = null; this[_9] = null; + this[_11] = null; } - [(foo(), _6 = foo(), _7 = foo(), "some" + "method")]() { } - [(_8 = fieldNameB, "some" + "method2")]() { } + [(foo(), _8 = foo(), _9 = foo(), "some" + "method")]() { } + [(_10 = fieldNameB, "some" + "method2")]() { } }, - _9 = fieldNameC, - _10); + _11 = fieldNameC, + _c); class I { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_12] = null; - this[_15] = null; + this[_13] = null; + this[_16] = null; } - [(foo(), _11 = foo(), _12 = foo(), _13 = "some" + "method")]() { } - [(_14 = fieldNameB, "some" + "method2")]() { } + [(foo(), _12 = foo(), _13 = foo(), _14 = "some" + "method")]() { } + [(_15 = fieldNameB, "some" + "method2")]() { } } -_15 = fieldNameC; +_16 = fieldNameC; __decorate([ x ], I.prototype, "property", void 0); @@ -426,32 +427,32 @@ __decorate([ __decorate([ x ], I.prototype, Symbol.iterator, void 0); -__decorate([ - x -], I.prototype, _11, void 0); __decorate([ x ], I.prototype, _12, void 0); __decorate([ x -], I.prototype, _13, null); +], I.prototype, _13, void 0); __decorate([ x -], I.prototype, _14, void 0); +], I.prototype, _14, null); __decorate([ x ], I.prototype, _15, void 0); -void (_21 = class J { +__decorate([ + x +], I.prototype, _16, void 0); +void (_d = class J { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_17] = null; - this[_20] = null; + this[_18] = null; + this[_21] = null; } - [(foo(), _16 = foo(), _17 = foo(), _18 = "some" + "method")]() { } - [(_19 = fieldNameB, "some" + "method2")]() { } + [(foo(), _17 = foo(), _18 = foo(), _19 = "some" + "method")]() { } + [(_20 = fieldNameB, "some" + "method2")]() { } }, - _20 = fieldNameC, - _21); + _21 = fieldNameC, + _d); diff --git a/tests/baselines/reference/systemModuleTargetES6.js b/tests/baselines/reference/systemModuleTargetES6.js index 943e57ff742..0df2835683e 100644 --- a/tests/baselines/reference/systemModuleTargetES6.js +++ b/tests/baselines/reference/systemModuleTargetES6.js @@ -35,8 +35,8 @@ System.register([], function (exports_1, context_1) { MyClass2 = class MyClass2 { static getInstance() { return MyClass2.value; } }; - MyClass2.value = 42; exports_1("MyClass2", MyClass2); + MyClass2.value = 42; } }; }); diff --git a/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js b/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js index 21871ff2304..872008368c6 100644 --- a/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js +++ b/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js @@ -16,8 +16,8 @@ var Foo = /** @class */ (function () { } return Foo; }()); -_a = key; exports.Foo = Foo; +_a = key; //// [variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.d.ts] From 2fdf7b5d3b319a105b85203026a7b677a8e69ee7 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Thu, 6 Jun 2019 10:13:07 -0700 Subject: [PATCH 111/111] Update user baselines (#31793) --- .../baselines/reference/user/chrome-devtools-frontend.log | 8 ++++++++ tests/baselines/reference/user/prettier.log | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/tests/baselines/reference/user/chrome-devtools-frontend.log b/tests/baselines/reference/user/chrome-devtools-frontend.log index 6b6b46ab180..7c51aef5159 100644 --- a/tests/baselines/reference/user/chrome-devtools-frontend.log +++ b/tests/baselines/reference/user/chrome-devtools-frontend.log @@ -3012,7 +3012,15 @@ node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighth node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65285,7): error TS2339: Property 'description' does not exist on type 'Error'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65310,1): error TS2323: Cannot redeclare exported variable '__esModule'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65379,1): error TS2323: Cannot redeclare exported variable '__esModule'. +node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65443,1): error TS2322: Type '{ line: number; column: number; }' is not assignable to type '{}'. + Property 'line' does not exist on type '{}'. +node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65464,1): error TS2322: Type '{ line: number; column: number; }' is not assignable to type '{}'. + Property 'line' does not exist on type '{}'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65468,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'entry' must be of type '{ multiLine: boolean; slice: any[]; range: number[]; loc: { start: { line: number; column: number; }; end: {}; }; }', but here has type '{ multiLine: boolean; slice: any[]; range: any[]; loc: { start: { line: number; column: number; }; end: {}; }; }'. +node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65507,1): error TS2322: Type '{ line: number; column: number; }' is not assignable to type '{}'. + Property 'line' does not exist on type '{}'. +node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65529,1): error TS2322: Type '{ line: number; column: number; }' is not assignable to type '{}'. + Property 'line' does not exist on type '{}'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65533,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'entry' must be of type '{ multiLine: boolean; slice: number[]; range: any[]; loc: { start: { line: number; column: number; }; end: {}; }; }', but here has type '{ multiLine: boolean; slice: any[]; range: any[]; loc: { start: { line: number; column: number; }; end: {}; }; }'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(65576,5): error TS2403: Subsequent variable declarations must have the same type. Variable 'comment' must be of type '{ multiLine: boolean; slice: any[]; range: number[]; loc: { start: { line: number; column: number; }; end: {}; }; }[]', but here has type '{ multiLine: boolean; slice: number[]; range: any[]; loc: { start: { line: number; column: number; }; end: {}; }; }[]'. node_modules/chrome-devtools-frontend/front_end/audits2_worker/lighthouse/lighthouse-background.js(66529,1): error TS2323: Cannot redeclare exported variable '__esModule'. diff --git a/tests/baselines/reference/user/prettier.log b/tests/baselines/reference/user/prettier.log index 6ee6e91cb6b..2082b66ee59 100644 --- a/tests/baselines/reference/user/prettier.log +++ b/tests/baselines/reference/user/prettier.log @@ -160,6 +160,13 @@ src/language-js/printer-estree.js(1892,20): error TS2345: Argument of type '{ ty src/language-js/printer-estree.js(1894,18): error TS2345: Argument of type '"while ("' is not assignable to parameter of type '{ type: string; id: any; contents: any; break: boolean; expandedStates: any; }'. src/language-js/printer-estree.js(1903,9): error TS2345: Argument of type '")"' is not assignable to parameter of type '{ type: string; id: any; contents: any; break: boolean; expandedStates: any; }'. src/language-js/printer-estree.js(2426,28): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/language-js/printer-estree.js(2446,13): error TS2345: Argument of type '{ hasLineBreak: boolean; cells: never[]; }[]' is not assignable to parameter of type '{ cells: any; } | ConcatArray<{ cells: any; }>'. + Type '{ hasLineBreak: boolean; cells: never[]; }[]' is not assignable to type 'ConcatArray<{ cells: any; }>'. + Types of property 'slice' are incompatible. + Type '(start?: number | undefined, end?: number | undefined) => { hasLineBreak: boolean; cells: never[]; }[]' is not assignable to type '(start?: number | undefined, end?: number | undefined) => { cells: any; }[]'. + Type '{ hasLineBreak: boolean; cells: never[]; }[]' is not assignable to type '{ cells: any; }[]'. + Type '{ hasLineBreak: boolean; cells: never[]; }' is not assignable to type '{ cells: any; }'. + Property 'hasLineBreak' does not exist on type '{ cells: any; }'. src/language-js/printer-estree.js(3513,11): error TS2345: Argument of type 'never[] | { type: string; parts: any; }' is not assignable to parameter of type 'ConcatArray'. Type '{ type: string; parts: any; }' is not assignable to type 'ConcatArray'. src/language-js/printer-estree.js(4031,23): error TS2532: Object is possibly 'undefined'.