From 64d29c67fe51060a937f6bdd1a1d522b74f66ac8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 5 Jun 2024 14:01:49 -0700 Subject: [PATCH] Some refactoring so we arent checking undefined on program or asserting it so much --- src/compiler/builder.ts | 335 ++++++++++++------ src/compiler/builderPublic.ts | 115 +++++- src/compiler/watch.ts | 4 +- src/compiler/watchUtilities.ts | 7 +- src/testRunner/unittests/helpers/baseline.ts | 7 +- src/testRunner/unittests/helpers/tsc.ts | 3 +- .../unittests/tscWatch/incremental.ts | 25 +- 7 files changed, 352 insertions(+), 144 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 21a45dc75c16d..b8a644788408a 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -252,6 +252,17 @@ export interface BuilderProgramState extends BuilderState, ReusableBuilderProgra seenProgramEmit: BuilderFileEmit | undefined; } +interface BuilderProgramStateWithDefinedProgram extends BuilderProgramState { + program: Program; +} +function isBuilderProgramStateWithDefinedProgram(state: ReusableBuilderProgramState): state is BuilderProgramStateWithDefinedProgram { + return state.program !== undefined; +} +function toBuilderProgramStateWithDefinedProgram(state: ReusableBuilderProgramState): BuilderProgramStateWithDefinedProgram { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); + return state; +} + /** @internal */ export type SavedBuildProgramEmitState = & Pick< @@ -289,7 +300,10 @@ export function getBuilderFileEmit(options: CompilerOptions) { * * @internal */ -export function getPendingEmitKind(optionsOrEmitKind: CompilerOptions | BuilderFileEmit, oldOptionsOrEmitKind: CompilerOptions | BuilderFileEmit | undefined): BuilderFileEmit { +export function getPendingEmitKind( + optionsOrEmitKind: CompilerOptions | BuilderFileEmit, + oldOptionsOrEmitKind: CompilerOptions | BuilderFileEmit | undefined, +): BuilderFileEmit { const oldEmitKind = oldOptionsOrEmitKind && (isNumber(oldOptionsOrEmitKind) ? oldOptionsOrEmitKind : getBuilderFileEmit(oldOptionsOrEmitKind)); const emitKind = isNumber(optionsOrEmitKind) ? optionsOrEmitKind : getBuilderFileEmit(optionsOrEmitKind); if (oldEmitKind === emitKind) return BuilderFileEmit.None; @@ -303,7 +317,10 @@ export function getPendingEmitKind(optionsOrEmitKind: CompilerOptions | BuilderF return result; } -function hasSameKeys(map1: ReadonlyCollection | undefined, map2: ReadonlyCollection | undefined): boolean { +function hasSameKeys( + map1: ReadonlyCollection | undefined, + map2: ReadonlyCollection | undefined, +): boolean { // Has same size and every key is present in both maps return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); } @@ -311,7 +328,10 @@ function hasSameKeys(map1: ReadonlyCollection | undefined, map2: Readonl /** * Create the state so that we can iterate on changedFiles/affected files */ -function createBuilderProgramState(newProgram: Program, oldState: Readonly | undefined): BuilderProgramState { +function createBuilderProgramState( + newProgram: Program, + oldState: Readonly | undefined, +): BuilderProgramState { const state = BuilderState.create(newProgram, oldState, /*disableUseFileVersionAsSignature*/ false) as BuilderProgramState; state.program = newProgram; const compilerOptions = newProgram.getCompilerOptions(); @@ -489,7 +509,11 @@ function createBuilderProgramState(newProgram: Program, oldState: Readonly { if (isString(diag.messageText)) return diag; @@ -534,7 +561,11 @@ function convertOrRepopulateDiagnosticMessageChainArray convertOrRepopulateDiagnosticMessageChain(chain, sourceFile, newProgram, repopulateInfo)); } -function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], diagnosticFilePath: Path, newProgram: Program): readonly Diagnostic[] { +function convertToDiagnostics( + diagnostics: readonly ReusableDiagnostic[], + diagnosticFilePath: Path, + newProgram: Program, +): readonly Diagnostic[] { if (!diagnostics.length) return emptyArray; let buildInfoDirectory: string | undefined; return diagnostics.map(diagnostic => { @@ -558,7 +589,12 @@ function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], diagno } } -function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, diagnosticFilePath: Path, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { +function convertToDiagnosticRelatedInformation( + diagnostic: ReusableDiagnosticRelatedInformation, + diagnosticFilePath: Path, + newProgram: Program, + toPath: (path: string) => Path, +): DiagnosticRelatedInformation { const { file } = diagnostic; const sourceFile = file !== false ? newProgram.getSourceFileByPath(file ? toPath(file) : diagnosticFilePath) : @@ -580,7 +616,9 @@ function releaseCache(state: BuilderProgramState) { state.program = undefined; } -function backupBuilderProgramEmitState(state: Readonly): SavedBuildProgramEmitState { +function backupBuilderProgramEmitState( + state: Readonly, +): SavedBuildProgramEmitState { const outFilePath = state.compilerOptions.outFile; // Only in --out changeFileSet is kept around till emit Debug.assert(!state.changedFilesSet.size || outFilePath); @@ -599,7 +637,10 @@ function backupBuilderProgramEmitState(state: Readonly): Sa }; } -function restoreBuilderProgramEmitState(state: BuilderProgramState, savedEmitState: SavedBuildProgramEmitState) { +function restoreBuilderProgramEmitState( + state: BuilderProgramState, + savedEmitState: SavedBuildProgramEmitState, +) { state.affectedFilesPendingEmit = savedEmitState.affectedFilesPendingEmit; state.seenEmittedFiles = savedEmitState.seenEmittedFiles; state.seenProgramEmit = savedEmitState.seenProgramEmit; @@ -631,7 +672,7 @@ function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, s * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained */ function getNextAffectedFile( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, cancellationToken: CancellationToken | undefined, host: BuilderProgramHost, ): SourceFile | Program | undefined { @@ -645,7 +686,11 @@ function getNextAffectedFile( if (!seenAffectedFiles.has(affectedFile.resolvedPath)) { // Set the next affected file as seen and remove the cached semantic diagnostics state.affectedFilesIndex = affectedFilesIndex; - addToAffectedFilesPendingEmit(state, affectedFile.resolvedPath, getBuilderFileEmit(state.compilerOptions)); + addToAffectedFilesPendingEmit( + state, + affectedFile.resolvedPath, + getBuilderFileEmit(state.compilerOptions), + ); handleDtsMayChangeOfAffectedFile( state, affectedFile, @@ -674,14 +719,13 @@ function getNextAffectedFile( // With --out or --outFile all outputs go into single file // so operations are performed directly on program, return program - const program = Debug.checkDefined(state.program); - const compilerOptions = program.getCompilerOptions(); - if (compilerOptions.outFile) return program; + const compilerOptions = state.program.getCompilerOptions(); + if (compilerOptions.outFile) return state.program; // Get next batch of affected files state.affectedFiles = BuilderState.getFilesAffectedByWithOldState( state, - program, + state.program, nextKey.value, cancellationToken, host, @@ -715,11 +759,14 @@ function clearAffectedFilesPendingEmit(state: BuilderProgramState, emitOnlyDtsFi /** * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet */ -function getNextAffectedFilePendingEmit(state: BuilderProgramState, emitOnlyDtsFiles: boolean | undefined) { +function getNextAffectedFilePendingEmit( + state: BuilderProgramStateWithDefinedProgram, + emitOnlyDtsFiles: boolean | undefined, +) { if (!state.affectedFilesPendingEmit?.size) return undefined; return forEachEntry(state.affectedFilesPendingEmit, (emitKind, path) => { - const affectedFile = state.program!.getSourceFileByPath(path); - if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program!)) { + const affectedFile = state.program.getSourceFileByPath(path); + if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program)) { state.affectedFilesPendingEmit!.delete(path); return undefined; } @@ -730,11 +777,11 @@ function getNextAffectedFilePendingEmit(state: BuilderProgramState, emitOnlyDtsF }); } -function getNextPendingEmitDiagnosticsFile(state: BuilderProgramState) { +function getNextPendingEmitDiagnosticsFile(state: BuilderProgramStateWithDefinedProgram) { if (!state.emitDiagnosticsPerFile?.size) return undefined; return forEachEntry(state.emitDiagnosticsPerFile, (diagnostics, path) => { - const affectedFile = state.program!.getSourceFileByPath(path); - if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program!)) { + const affectedFile = state.program.getSourceFileByPath(path); + if (!affectedFile || !sourceFileMayBeEmitted(affectedFile, state.program)) { state.emitDiagnosticsPerFile!.delete(path); return undefined; } @@ -743,14 +790,13 @@ function getNextPendingEmitDiagnosticsFile(state: BuilderProgramState) { }); } -function removeDiagnosticsOfLibraryFiles(state: BuilderProgramState) { +function removeDiagnosticsOfLibraryFiles(state: BuilderProgramStateWithDefinedProgram) { if (!state.cleanedDiagnosticsOfLibFiles) { state.cleanedDiagnosticsOfLibFiles = true; - const program = Debug.checkDefined(state.program); - const options = program.getCompilerOptions(); - forEach(program.getSourceFiles(), f => - program.isSourceFileDefaultLibrary(f) && - !skipTypeChecking(f, options, program) && + const options = state.program.getCompilerOptions(); + forEach(state.program.getSourceFiles(), f => + state.program.isSourceFileDefaultLibrary(f) && + !skipTypeChecking(f, options, state.program) && removeSemanticDiagnosticsOf(state, f.resolvedPath)); } } @@ -760,7 +806,7 @@ function removeDiagnosticsOfLibraryFiles(state: BuilderProgramState) { * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change */ function handleDtsMayChangeOfAffectedFile( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, host: BuilderProgramHost, @@ -775,7 +821,7 @@ function handleDtsMayChangeOfAffectedFile( // To avoid this, ensure that we update the signature for any affected file in this scenario. BuilderState.updateShapeSignature( state, - Debug.checkDefined(state.program), + state.program, affectedFile, cancellationToken, host, @@ -796,7 +842,7 @@ function handleDtsMayChangeOfAffectedFile( * Also we need to make sure signature is updated for these files */ function handleDtsMayChangeOf( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, path: Path, invalidateJsFiles: boolean, cancellationToken: CancellationToken | undefined, @@ -805,8 +851,7 @@ function handleDtsMayChangeOf( removeSemanticDiagnosticsOf(state, path); if (!state.changedFilesSet.has(path)) { - const program = Debug.checkDefined(state.program); - const sourceFile = program.getSourceFileByPath(path); + const sourceFile = state.program.getSourceFileByPath(path); if (sourceFile) { // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file @@ -815,7 +860,7 @@ function handleDtsMayChangeOf( // But we avoid expensive full shape computation, as using file version as shape is enough for correctness. BuilderState.updateShapeSignature( state, - program, + state.program, sourceFile, cancellationToken, host, @@ -823,10 +868,18 @@ function handleDtsMayChangeOf( ); // If not dts emit, nothing more to do if (invalidateJsFiles) { - addToAffectedFilesPendingEmit(state, path, getBuilderFileEmit(state.compilerOptions)); + addToAffectedFilesPendingEmit( + state, + path, + getBuilderFileEmit(state.compilerOptions), + ); } else if (getEmitDeclarations(state.compilerOptions)) { - addToAffectedFilesPendingEmit(state, path, state.compilerOptions.declarationMap ? BuilderFileEmit.AllDts : BuilderFileEmit.Dts); + addToAffectedFilesPendingEmit( + state, + path, + state.compilerOptions.declarationMap ? BuilderFileEmit.AllDts : BuilderFileEmit.Dts, + ); } } } @@ -852,7 +905,7 @@ function isChangedSignature(state: BuilderProgramState, path: Path) { } function handleDtsMayChangeOfGlobalScope( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, filePath: Path, invalidateJsFiles: boolean, cancellationToken: CancellationToken | undefined, @@ -860,16 +913,19 @@ function handleDtsMayChangeOfGlobalScope( ): boolean { if (!state.fileInfos.get(filePath)?.affectsGlobalScope) return false; // Every file needs to be handled - BuilderState.getAllFilesExcludingDefaultLibraryFile(state, state.program!, /*firstSourceFile*/ undefined) - .forEach(file => - handleDtsMayChangeOf( - state, - file.resolvedPath, - invalidateJsFiles, - cancellationToken, - host, - ) - ); + BuilderState.getAllFilesExcludingDefaultLibraryFile( + state, + state.program, + /*firstSourceFile*/ undefined, + ).forEach(file => + handleDtsMayChangeOf( + state, + file.resolvedPath, + invalidateJsFiles, + cancellationToken, + host, + ) + ); removeDiagnosticsOfLibraryFiles(state); return true; } @@ -878,7 +934,7 @@ function handleDtsMayChangeOfGlobalScope( * Iterate on referencing modules that export entities from affected file and delete diagnostics and add pending emit */ function handleDtsMayChangeOfReferencingExportOfAffectedFile( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, host: BuilderProgramHost, @@ -909,7 +965,7 @@ function handleDtsMayChangeOfReferencingExportOfAffectedFile( ) return; handleDtsMayChangeOf(state, currentPath, /*invalidateJsFiles*/ false, cancellationToken, host); if (isChangedSignature(state, currentPath)) { - const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!; + const currentSourceFile = state.program.getSourceFileByPath(currentPath)!; queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); } } @@ -922,7 +978,7 @@ function handleDtsMayChangeOfReferencingExportOfAffectedFile( affectedFile.symbol.exports, exported => { if ((exported.flags & SymbolFlags.ConstEnum) !== 0) return true; - const aliased = skipAlias(exported, state.program!.getTypeChecker()); + const aliased = skipAlias(exported, state.program.getTypeChecker()); if (aliased === exported) return false; return (aliased.flags & SymbolFlags.ConstEnum) !== 0 && some(aliased.declarations, d => getSourceFileOfNode(d) === affectedFile); @@ -949,7 +1005,7 @@ function handleDtsMayChangeOfReferencingExportOfAffectedFile( * return true when all work is done and we can exit handling dts emit and semantic diagnostics */ function handleDtsMayChangeOfFileAndExportsOfFile( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, filePath: Path, invalidateJsFiles: boolean, seenFileAndExportsOfFile: Set, @@ -980,14 +1036,14 @@ function handleDtsMayChangeOfFileAndExportsOfFile( * bindAndCheckDiagnostics (from cache) and program diagnostics */ function getSemanticDiagnosticsOfFile( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, sourceFile: SourceFile, cancellationToken: CancellationToken | undefined, semanticDiagnosticsPerFile?: BuilderProgramState["semanticDiagnosticsPerFile"], ): readonly Diagnostic[] { return concatenate( getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken, semanticDiagnosticsPerFile), - Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile), + state.program.getProgramDiagnostics(sourceFile), ); } @@ -996,7 +1052,7 @@ function getSemanticDiagnosticsOfFile( * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set */ function getBinderAndCheckerDiagnosticsOfFile( - state: BuilderProgramState, + state: BuilderProgramStateWithDefinedProgram, sourceFile: SourceFile, cancellationToken: CancellationToken | undefined, semanticDiagnosticsPerFile: BuilderProgramState["semanticDiagnosticsPerFile"] | undefined, @@ -1010,7 +1066,7 @@ function getBinderAndCheckerDiagnosticsOfFile( } // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); + const diagnostics = state.program.getBindAndCheckDiagnostics(sourceFile, cancellationToken); semanticDiagnosticsPerFile.set(path, diagnostics); return filterSemanticDiagnostics(diagnostics, state.compilerOptions); } @@ -1126,14 +1182,14 @@ export function isProgramBundleEmitBuildInfo(info: ProgramBuildInfo): info is Pr /** * Gets the program information to be emitted in buildInfo so that we can use it to create new program */ -function getBuildInfo(state: BuilderProgramState): BuildInfo { - const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory(); +function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo { + const currentDirectory = state.program.getCurrentDirectory(); const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); // Convert the file name to Path here if we set the fileName instead to optimize multiple d.ts file emits and having to compute Canonical path const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined; const fileNames: string[] = []; const fileNameToFileId = new Map(); - const rootFileNames = new Set(state.program!.getRootFileNames().map(f => toPath(f, currentDirectory, state.program!.getCanonicalFileName))); + const rootFileNames = new Set(state.program.getRootFileNames().map(f => toPath(f, currentDirectory, state.program.getCanonicalFileName))); const root: ProgramBuildInfoRoot[] = []; if (state.compilerOptions.outFile) { // Copy all fileInfo, version and impliedFormat @@ -1177,8 +1233,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo { const oldSignature = state.oldSignatures?.get(key); const actualSignature = oldSignature !== undefined ? oldSignature || undefined : value.signature; if (state.compilerOptions.composite) { - const file = state.program!.getSourceFileByPath(key)!; - if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program!)) { + const file = state.program.getSourceFileByPath(key)!; + if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program)) { const emitSignature = state.emitSignatures?.get(key); if (emitSignature !== actualSignature) { emitSignatures = append( @@ -1222,8 +1278,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo { const seenFiles = new Set(); for (const path of arrayFrom(state.affectedFilesPendingEmit.keys()).sort(compareStringsCaseSensitive)) { if (tryAddToSet(seenFiles, path)) { - const file = state.program!.getSourceFileByPath(path); - if (!file || !sourceFileMayBeEmitted(file, state.program!)) continue; + const file = state.program.getSourceFileByPath(path); + if (!file || !sourceFileMayBeEmitted(file, state.program)) continue; const fileId = toFileId(path), pendingEmit = state.affectedFilesPendingEmit.get(path)!; affectedFilesPendingEmit = append( affectedFilesPendingEmit, @@ -1259,7 +1315,7 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo { } function relativeToBuildInfo(path: string) { - return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, state.program!.getCanonicalFileName)); + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, state.program.getCanonicalFileName)); } function toFileId(path: Path): ProgramBuildInfoFileId { @@ -1283,8 +1339,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo { } function tryAddRoot(path: Path, fileId: ProgramBuildInfoFileId) { - const file = state.program!.getSourceFile(path)!; - if (!state.program!.getFileIncludeReasons().get(file.path)!.some(r => r.kind === FileIncludeKind.RootFile)) return; + const file = state.program.getSourceFile(path)!; + if (!state.program.getFileIncludeReasons().get(file.path)!.some(r => r.kind === FileIncludeKind.RootFile)) return; // First fileId as is if (!root.length) return root.push(fileId); const last = root[root.length - 1]; @@ -1304,7 +1360,7 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo { function toResolvedRoot(): ProgramBuildInfoResolvedRoot[] | undefined { let result: ProgramBuildInfoResolvedRoot[] | undefined; rootFileNames.forEach(path => { - const file = state.program!.getSourceFileByPath(path); + const file = state.program.getSourceFileByPath(path); if (file && path !== file.resolvedPath) { result = append(result, [toFileId(file.resolvedPath), toFileId(path)]); } @@ -1538,34 +1594,47 @@ export function computeSignature(text: string, host: HostForComputeHash, data?: } /** @internal */ -export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; +export function createBuilderProgram( + kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, + builderCreationParameters: BuilderCreationParameters, +): SemanticDiagnosticsBuilderProgram; /** @internal */ -export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; +export function createBuilderProgram( + kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, + builderCreationParameters: BuilderCreationParameters, +): EmitAndSemanticDiagnosticsBuilderProgram; /** @internal */ -export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { +export function createBuilderProgram( + kind: BuilderProgramKind, + { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters, +) { // Return same program if underlying program doesnt change - let oldState = oldProgram && oldProgram.getState(); + let oldState = oldProgram && oldProgram.state; if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { - newProgram = undefined!; // TODO: GH#18217 + newProgram = undefined!; oldState = undefined; return oldProgram; } const state = createBuilderProgramState(newProgram, oldState); - newProgram.getBuildInfo = () => getBuildInfo(state); + newProgram.getBuildInfo = () => getBuildInfo(toBuilderProgramStateWithDefinedProgram(state)); // To ensure that we arent storing any references to old program or new program without state - newProgram = undefined!; // TODO: GH#18217 + newProgram = undefined!; oldProgram = undefined; oldState = undefined; - const getState = () => state; - const builderProgram = createRedirectedBuilderProgram(getState, configFileParsingDiagnostics); - builderProgram.getState = getState; + const builderProgram = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); + builderProgram.state = state; builderProgram.saveEmitState = () => backupBuilderProgramEmitState(state); builderProgram.restoreEmitState = saved => restoreBuilderProgramEmitState(state, saved); builderProgram.hasChangedEmitSignature = () => !!state.hasChangedEmitSignature; - builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile); + builderProgram.getAllDependencies = sourceFile => + BuilderState.getAllDependencies( + state, + Debug.checkDefined(state.program), + sourceFile, + ); builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; builderProgram.emit = emit; builderProgram.releaseProgram = () => releaseCache(state); @@ -1581,12 +1650,18 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos else { notImplemented(); } - return builderProgram; - function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { + function emitBuildInfo( + writeFile: WriteFileCallback | undefined, + cancellationToken: CancellationToken | undefined, + ): EmitResult { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); if (state.buildInfoEmitPending) { - const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); + const result = state.program.emitBuildInfo( + writeFile || maybeBind(host, host.writeFile), + cancellationToken, + ); state.buildInfoEmitPending = false; return result; } @@ -1598,7 +1673,13 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host * in that order would be used to write the files */ - function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { + function emitNextAffectedFile( + writeFile: WriteFileCallback | undefined, + cancellationToken: CancellationToken | undefined, + emitOnlyDtsFiles: boolean | undefined, + customTransformers: CustomTransformers | undefined, + ): AffectedFileResult { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); let affected = getNextAffectedFile(state, cancellationToken, host); const programEmitKind = getBuilderFileEmit(state.compilerOptions); let emitKind: BuilderFileEmit = emitOnlyDtsFiles ? @@ -1637,7 +1718,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos state.emitDiagnosticsPerFile.forEach(d => addRange(diagnostics, d)); return { result: { emitSkipped: true, diagnostics }, - affected: state.program!, + affected: state.program, }; } } @@ -1646,8 +1727,11 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos if (!affected) { // Emit buildinfo if pending if (!state.buildInfoEmitPending) return undefined; - const affected = state.program!; - const result = affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); + const affected = state.program; + const result = affected.emitBuildInfo( + writeFile || maybeBind(host, host.writeFile), + cancellationToken, + ); state.buildInfoEmitPending = false; return { result, affected }; } @@ -1657,7 +1741,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos if (emitKind & BuilderFileEmit.AllJs) emitOnly = EmitOnly.Js; if (emitKind & BuilderFileEmit.AllDts) emitOnly = emitOnly === undefined ? EmitOnly.Dts : undefined; // Actual emit without buildInfo as we want to emit it later so the state is updated - const result = state.program!.emit( + const result = state.program.emit( affected === state.program ? undefined : affected as SourceFile, getWriteFileCallback(writeFile, customTransformers), cancellationToken, @@ -1707,7 +1791,11 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos return { result, affected }; } - function getWriteFileCallback(writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined): WriteFileCallback | undefined { + function getWriteFileCallback( + writeFile: WriteFileCallback | undefined, + customTransformers: CustomTransformers | undefined, + ): WriteFileCallback | undefined { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); if (!getEmitDeclarations(state.compilerOptions)) return writeFile || maybeBind(host, host.writeFile); return (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => { if (isDeclarationFileName(fileName)) { @@ -1719,7 +1807,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos const info = state.fileInfos.get(file.resolvedPath)!; if (info.signature === file.version) { const signature = computeSignatureWithDiagnostics( - state.program!, + state.program, file, text, host, @@ -1761,7 +1849,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos } if (writeFile) writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); else if (host.writeFile) host.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); - else state.program!.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); + else state.program.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data); /** * Compare to existing computed signature and store it or handle the changes in d.ts map option from before @@ -1799,7 +1887,14 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host * in that order would be used to write the files */ - function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + function emit( + targetSourceFile: SourceFile | undefined, + writeFile: WriteFileCallback | undefined, + cancellationToken: CancellationToken | undefined, + emitOnlyDtsFiles: boolean | undefined, + customTransformers: CustomTransformers | undefined, + ): EmitResult { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); } @@ -1816,7 +1911,14 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos let emittedFiles: string[] = []; let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { + while ( + affectedEmitResult = emitNextAffectedFile( + writeFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers, + ) + ) { emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); @@ -1834,7 +1936,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos clearAffectedFilesPendingEmit(state, emitOnlyDtsFiles); } } - return Debug.checkDefined(state.program).emit( + return state.program.emit( targetSourceFile, getWriteFileCallback(writeFile, customTransformers), cancellationToken, @@ -1847,7 +1949,11 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos * Return the semantic diagnostics for the next affected file or undefined if iteration is complete * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true */ - function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult { + function getSemanticDiagnosticsOfNextAffectedFile( + cancellationToken: CancellationToken | undefined, + ignoreSourceFile?: (sourceFile: SourceFile) => boolean, + ): AffectedFileResult { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); while (true) { const affected = getNextAffectedFile(state, cancellationToken, host); let result; @@ -1897,7 +2003,11 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics */ - function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { + function getSemanticDiagnostics( + sourceFile: SourceFile | undefined, + cancellationToken: CancellationToken | undefined, + ): readonly Diagnostic[] { + Debug.assert(isBuilderProgramStateWithDefinedProgram(state)); assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); if (sourceFile) { return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); @@ -1913,14 +2023,18 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos } let diagnostics: Diagnostic[] | undefined; - for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) { + for (const sourceFile of state.program.getSourceFiles()) { diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); } return diagnostics || emptyArray; } } -function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { +function addToAffectedFilesPendingEmit( + state: BuilderProgramState, + affectedFilePendingEmit: Path, + kind: BuilderFileEmit, +) { const existingKind = state.affectedFilesPendingEmit?.get(affectedFilePendingEmit) || BuilderFileEmit.None; (state.affectedFilesPendingEmit ??= new Map()).set(affectedFilePendingEmit, existingKind | kind); state.emitDiagnosticsPerFile?.delete(affectedFilePendingEmit); @@ -1936,17 +2050,27 @@ export function toBuilderStateFileInfoForMultiEmit(fileInfo: ProgramMultiFileEmi } /** @internal */ -export function toBuilderFileEmit(value: ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: BuilderFileEmit): BuilderFileEmit { +export function toBuilderFileEmit( + value: ProgramBuilderInfoFilePendingEmit, + fullEmitForOptions: BuilderFileEmit, +): BuilderFileEmit { return isNumber(value) ? fullEmitForOptions : value[1] || BuilderFileEmit.Dts; } /** @internal */ -export function toProgramEmitPending(value: ProgramBuildInfoBundlePendingEmit, options: CompilerOptions | undefined): BuilderFileEmit | undefined { +export function toProgramEmitPending( + value: ProgramBuildInfoBundlePendingEmit, + options: CompilerOptions | undefined, +): BuilderFileEmit | undefined { return !value ? getBuilderFileEmit(options || {}) : value; } /** @internal */ -export function createBuilderProgramUsingProgramBuildInfo(buildInfo: BuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { +export function createBuilderProgramUsingProgramBuildInfo( + buildInfo: BuildInfo, + buildInfoPath: string, + host: ReadBuildProgramHost, +): EmitAndSemanticDiagnosticsBuilderProgram { const program = buildInfo.program!; const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); @@ -2012,7 +2136,7 @@ export function createBuilderProgramUsingProgramBuildInfo(buildInfo: BuildInfo, } return { - getState: () => state, + state, saveEmitState: noop as BuilderProgram["saveEmitState"], restoreEmitState: noop, getProgram: notImplemented, @@ -2127,15 +2251,18 @@ export function getBuildInfoFileVersionMap( } /** @internal */ -export function createRedirectedBuilderProgram(getState: () => { program?: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { +export function createRedirectedBuilderProgram( + state: Pick, + configFileParsingDiagnostics: readonly Diagnostic[], +): BuilderProgram { return { - getState: notImplemented, + state: undefined!, saveEmitState: noop as BuilderProgram["saveEmitState"], restoreEmitState: noop, getProgram, - getProgramOrUndefined: () => getState().program, - releaseProgram: () => getState().program = undefined, - getCompilerOptions: () => getState().compilerOptions, + getProgramOrUndefined: () => state.program, + releaseProgram: () => state.program = undefined, + getCompilerOptions: () => state.compilerOptions, getSourceFile: fileName => getProgram().getSourceFile(fileName), getSourceFiles: () => getProgram().getSourceFiles(), getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), @@ -2152,6 +2279,6 @@ export function createRedirectedBuilderProgram(getState: () => { program?: Progr }; function getProgram() { - return Debug.checkDefined(getState().program); + return Debug.checkDefined(state.program); } } diff --git a/src/compiler/builderPublic.ts b/src/compiler/builderPublic.ts index eb2b9b9f4f6dc..ac63aafd7c8cf 100644 --- a/src/compiler/builderPublic.ts +++ b/src/compiler/builderPublic.ts @@ -46,7 +46,7 @@ export type HostForComputeHash = Pick ({ program: newProgram, compilerOptions: newProgram.getCompilerOptions() }), newConfigFileParsingDiagnostics); +export function createAbstractBuilder( + newProgram: Program, + host: BuilderProgramHost, + oldProgram?: BuilderProgram, + configFileParsingDiagnostics?: readonly Diagnostic[], +): BuilderProgram; +export function createAbstractBuilder( + rootNames: readonly string[] | undefined, + options: CompilerOptions | undefined, + host?: CompilerHost, + oldProgram?: BuilderProgram, + configFileParsingDiagnostics?: readonly Diagnostic[], + projectReferences?: readonly ProjectReference[], +): BuilderProgram; +export function createAbstractBuilder( + newProgramOrRootNames: Program | readonly string[] | undefined, + hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, + oldProgramOrHost?: CompilerHost | BuilderProgram, + configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, + configFileParsingDiagnostics?: readonly Diagnostic[], + projectReferences?: readonly ProjectReference[], +): BuilderProgram { + const { newProgram, configFileParsingDiagnostics: newConfigFileParsingDiagnostics } = getBuilderCreationParameters( + newProgramOrRootNames, + hostOrOptions, + oldProgramOrHost, + configFileParsingDiagnosticsOrOldProgram, + configFileParsingDiagnostics, + projectReferences, + ); + return createRedirectedBuilderProgram( + { program: newProgram, compilerOptions: newProgram.getCompilerOptions() }, + newConfigFileParsingDiagnostics, + ); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 4b28e288a3442..b2f99dd393cfb 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -329,8 +329,8 @@ function createTabularErrorsDisplay(filesInError: (ReportFileInError | undefined } /** @internal */ -export function isBuilderProgram(program: Program | BuilderProgram): program is BuilderProgram { - return !!(program as BuilderProgram).getState; +export function isBuilderProgram(program: Program | BuilderProgram): program is T { + return !!(program as T).state; } /** @internal */ diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 425fedfdd6f22..433c34ad96d93 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -30,6 +30,7 @@ import { identity, insertSorted, isArray, + isBuilderProgram, isDeclarationFileName, isExcludedFile, isSupportedSourceFileName, @@ -627,7 +628,7 @@ export function isIgnoredFileFromWildCardWatching({ return realProgram ? !!realProgram.getSourceFileByPath(file) : builderProgram ? - builderProgram.getState().fileInfos.has(file) : + builderProgram.state.fileInfos.has(file) : !!find(program as readonly string[], rootFile => toPath(rootFile) === file); } @@ -651,10 +652,6 @@ export function isIgnoredFileFromWildCardWatching({ } } -function isBuilderProgram(program: Program | T): program is T { - return !!(program as T).getState; -} - /** @internal */ export function isEmittedFileOfProgram(program: Program | undefined, file: string) { if (!program) { diff --git a/src/testRunner/unittests/helpers/baseline.ts b/src/testRunner/unittests/helpers/baseline.ts index 7f49ce4308c6a..ab985741d5cd7 100644 --- a/src/testRunner/unittests/helpers/baseline.ts +++ b/src/testRunner/unittests/helpers/baseline.ts @@ -72,9 +72,8 @@ function baselineProgram(baseline: string[], [program, builderProgram]: CommandL if (!builderProgram) return; if (builderProgram !== oldProgram?.[1]) { - const state = builderProgram.getState(); - const internalState = state as unknown as ts.BuilderProgramState; - if (state.semanticDiagnosticsPerFile.size) { + const internalState = builderProgram.state as ts.BuilderProgramState; + if (builderProgram.state.semanticDiagnosticsPerFile.size) { baseline.push("Semantic diagnostics in builder refreshed for::"); for (const file of program.getSourceFiles()) { if (!internalState.semanticDiagnosticsFromOldState || !internalState.semanticDiagnosticsFromOldState.has(file.resolvedPath)) { @@ -90,7 +89,7 @@ function baselineProgram(baseline: string[], [program, builderProgram]: CommandL if (internalState.hasCalledUpdateShapeSignature?.size) { baseline.push("Shape signatures in builder refreshed for::"); internalState.hasCalledUpdateShapeSignature.forEach((path: ts.Path) => { - const info = state.fileInfos.get(path); + const info = builderProgram.state.fileInfos.get(path); const signatureInfo = internalState.signatureInfo?.get(path)!; switch (signatureInfo) { case ts.SignatureInfo.ComputedDts: diff --git a/src/testRunner/unittests/helpers/tsc.ts b/src/testRunner/unittests/helpers/tsc.ts index d06053267acfb..e44bd2c497087 100644 --- a/src/testRunner/unittests/helpers/tsc.ts +++ b/src/testRunner/unittests/helpers/tsc.ts @@ -180,8 +180,7 @@ function storeDtsSignatures(sys: TscCompileSystem, programs: readonly CommandLin sys.dtsSignaures ??= new Map(); const dtsSignatureData = new Map(); sys.dtsSignaures.set(`${toPathWithSystem(sys, buildInfoPath)}.readable.baseline.txt` as ts.Path, dtsSignatureData); - const state = builderProgram.getState(); - state.hasCalledUpdateShapeSignature?.forEach(resolvedPath => { + builderProgram.state.hasCalledUpdateShapeSignature?.forEach(resolvedPath => { const file = program.getSourceFileByPath(resolvedPath); if (!file || file.isDeclarationFile) return; // Compute dts and exported map and store it diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index a2c6bcdbc9cd4..a459f700c78f1 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -170,42 +170,41 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => { host: ts.createIncrementalCompilerHost(command.options, system), }); - const state = builderProgram.getState(); - assert.equal(state.changedFilesSet!.size, 0, "changes"); + assert.equal(builderProgram.state.changedFilesSet!.size, 0, "changes"); - assert.equal(state.fileInfos.size, 3, "FileInfo size"); - assert.deepEqual(state.fileInfos.get(libFile.path as ts.Path), { + assert.equal(builderProgram.state.fileInfos.size, 3, "FileInfo size"); + assert.deepEqual(builderProgram.state.fileInfos.get(libFile.path as ts.Path), { version: system.createHash(libFile.content), signature: system.createHash(libFile.content), affectsGlobalScope: true, impliedFormat: ts.ModuleKind.CommonJS, }); - assert.deepEqual(state.fileInfos.get(file1.path as ts.Path), { + assert.deepEqual(builderProgram.state.fileInfos.get(file1.path as ts.Path), { version: system.createHash(file1.content), signature: system.createHash(file1.content), affectsGlobalScope: undefined, impliedFormat: ts.ModuleKind.CommonJS, }); - assert.deepEqual(state.fileInfos.get(file2.path as ts.Path), { + assert.deepEqual(builderProgram.state.fileInfos.get(file2.path as ts.Path), { version: system.createHash(fileModified.content), signature: system.createHash(fileModified.content), affectsGlobalScope: undefined, impliedFormat: ts.ModuleKind.CommonJS, }); - assert.deepEqual(state.compilerOptions, { + assert.deepEqual(builderProgram.state.compilerOptions, { incremental: true, module: ts.ModuleKind.AMD, configFilePath: config.path, }); - assert.equal(ts.arrayFrom(state.referencedMap!.keys()).length, 0); + assert.equal(ts.arrayFrom(builderProgram.state.referencedMap!.keys()).length, 0); - assert.equal(state.semanticDiagnosticsPerFile.size, 3); - assert.deepEqual(state.semanticDiagnosticsPerFile.get(libFile.path as ts.Path), ts.emptyArray); - assert.deepEqual(state.semanticDiagnosticsPerFile.get(file1.path as ts.Path), ts.emptyArray); - assert.deepEqual(state.semanticDiagnosticsPerFile.get(file2.path as ts.Path), [{ - file: state.program!.getSourceFileByPath(file2.path as ts.Path)!, + assert.equal(builderProgram.state.semanticDiagnosticsPerFile.size, 3); + assert.deepEqual(builderProgram.state.semanticDiagnosticsPerFile.get(libFile.path as ts.Path), ts.emptyArray); + assert.deepEqual(builderProgram.state.semanticDiagnosticsPerFile.get(file1.path as ts.Path), ts.emptyArray); + assert.deepEqual(builderProgram.state.semanticDiagnosticsPerFile.get(file2.path as ts.Path), [{ + file: builderProgram.state.program!.getSourceFileByPath(file2.path as ts.Path)!, start: 13, length: 1, code: ts.Diagnostics.Type_0_is_not_assignable_to_type_1.code,