diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index 5b17f2f4d902d..d38dca3793169 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -148,6 +148,7 @@ namespace ts { /*@internal*/ buildNextInvalidatedProject(): void; /*@internal*/ getAllParsedConfigs(): readonly ParsedCommandLine[]; /*@internal*/ close(): void; + /*@internal*/ getModuleResolutionCache(): ModuleResolutionCache | undefined; } /** @@ -1992,6 +1993,7 @@ namespace ts { config => isParsedCommandLine(config) ? config : undefined )), close: () => stopWatching(state), + getModuleResolutionCache: ()=> state.moduleResolutionCache, }; } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index 401c5aeb68a8d..200149a3d08f2 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -147,6 +147,7 @@ "unittests/tsbuildWatch/noEmitOnError.ts", "unittests/tsbuildWatch/programUpdates.ts", "unittests/tsbuildWatch/publicApi.ts", + "unittests/tsbuildWatch/publicApiImpliedNodeFormat.ts", "unittests/tsbuildWatch/reexport.ts", "unittests/tsbuildWatch/watchEnvironment.ts", "unittests/tsc/composite.ts", diff --git a/src/testRunner/unittests/tsbuildWatch/publicApiImpliedNodeFormat.ts b/src/testRunner/unittests/tsbuildWatch/publicApiImpliedNodeFormat.ts new file mode 100644 index 0000000000000..e84362bfda7f7 --- /dev/null +++ b/src/testRunner/unittests/tsbuildWatch/publicApiImpliedNodeFormat.ts @@ -0,0 +1,161 @@ +namespace ts.tscWatch { + it("unittests:: tsbuildWatch:: watchMode:: Public API with custom transformers / impliedNodeFormat", () => { + const solution: File = { + path: `${projectRoot}/tsconfig.json`, + content: JSON.stringify({ + references: [ + { path: "./shared/tsconfig.json" }, + { path: "./webpack/tsconfig.json" } + ], + files: [] + }) + }; + const sharedConfig: File = { + path: `${projectRoot}/shared/tsconfig.json`, + content: JSON.stringify({ + module:"Node12", + compilerOptions: { composite: true }, + }) + }; + const sharedPackageJson: File = { + path: `${projectRoot}/shared/package.json`, + content: JSON.stringify({ + name:"shared", + version:"1.0.0", + type:"commonjs", + }) + }; + const sharedIndex: File = { + path: `${projectRoot}/shared/index.ts`, + content: `export function f1() { } +export class c { } +export enum e { } +// leading +export function f2() { } // trailing` + }; + const webpackConfig: File = { + path: `${projectRoot}/webpack/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, }, + references: [{ path: "../shared/tsconfig.json" }] + }) + }; + const webpackIndex: File = { + path: `${projectRoot}/webpack/index.ts`, + content: `export function f2() { } +export class c2 { } +export enum e2 { } +// leading +export function f22() { } // trailing` + }; + const commandLineArgs = ["--b", "--w", /*"--verbose"*/]; + const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedPackageJson, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: projectRoot })); + + const writeToConsole = true; + if (writeToConsole){ + const origSysWrite = sys.write.bind(sys); + sys.write = (message: string)=>{ + console.log(message); + origSysWrite(message); + }; + } + type ImpliedNodeFormat = ModuleKind.CommonJS | ModuleKind.ESNext | undefined; + const impliedNodeFormatToString = (f: ImpliedNodeFormat)=>(f===undefined)? "undefined" : ModuleKind[f]; + const lastImpliedNodeFormats = new Map(); + function printLastImpliedNodeFormats(){ + lastImpliedNodeFormats.forEach((x,fileName)=>{ + sys.write(`fileName:${fileName},impliedNodeFormat:${impliedNodeFormatToString(x.impliedNodeFormat)}`+sys.newLine); + }); + } + + function printModuleResolutionCache(buildr: SolutionBuilder){ + const x = { moduleResolutionCache: buildr.getModuleResolutionCache() }; + sys.write(JSON.stringify(x,undefined,2)); + } + + const buildHost = createSolutionBuilderWithWatchHostForBaseline(sys, cb); + buildHost.getCustomTransformers = getCustomTransformers; + const builder = createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true }); + + builder.build(); + runWatchBaseline({ + scenario: "publicApi", + subScenario: "with custom transformers", + commandLineArgs, + sys, + baseline, + oldSnap, + getPrograms, + changes: [ + { + caption: "change to shared", + change: sys => sys.prependFile(sharedIndex.path, "export function fooBar() {}"), + timeouts: sys => { + sys.checkTimeoutQueueLengthAndRun(1); // Shared + sys.checkTimeoutQueueLengthAndRun(1); // webpack + sys.checkTimeoutQueueLengthAndRun(1); // solution + sys.checkTimeoutQueueLength(0); + printLastImpliedNodeFormats(); + printModuleResolutionCache(builder); + if (lastImpliedNodeFormats.get("/user/username/projects/myproject/shared/index.ts")?.impliedNodeFormat!==ModuleKind.CommonJS) { + throw new Error(`Expecting impliedNodeFormat for /user/username/projects/myproject/shared/index.ts to be ModuleKind.CommonJS`); + } + } + }, + // { + // caption: "add package.json to shared", + // change: sys => sys.writeFile(sharedPackageJson.path, sharedPackageJson.content), + // timeouts: sys => { + // sys.checkTimeoutQueueLengthAndRun(1); // Shared + // sys.checkTimeoutQueueLengthAndRun(1); // webpack + // sys.checkTimeoutQueueLengthAndRun(1); // solution + // sys.checkTimeoutQueueLength(0); + // printLastImpliedNodeFormats(); + // } + // } + ], + watchOrSolution: builder + }); + + function getCustomTransformers(project: string): CustomTransformers { + const before: TransformerFactory = context => { + return file => { + // const gotImpliedNodeFormat = getImpliedNodeFormatForFile( + // importingSourceFileName, buildHost.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions);; + + lastImpliedNodeFormats.set(file.fileName,{ impliedNodeFormat : file.impliedNodeFormat }); + return visitEachChild(file, visit, context); + }; + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return visitFunction(node as FunctionDeclaration); + default: + return visitEachChild(node, visit, context); + } + } + function visitFunction(node: FunctionDeclaration) { + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `@before${project}`, /*hasTrailingNewLine*/ true); + return node; + } + }; + + const after: TransformerFactory = context => { + return file => visitEachChild(file, visit, context); + function visit(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + default: + return visitEachChild(node, visit, context); + } + } + function visitVariableStatement(node: VariableStatement) { + addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, `@after${project}`); + return node; + } + }; + return { before: [before], after: [after] }; + } + }); +}