@@ -10,10 +10,14 @@ import { runInNewContext } from "node:vm";
1010import { x } from "tinyexec" ;
1111import ts from "typescript" ;
1212import cache from "./cache.mts" ;
13- import { GeneratedFile } from "./generatedFile.mts" ;
13+ import {
14+ defaultCacheDirectory ,
15+ GeneratedFile ,
16+ } from "./generatedFile.mts" ;
1417import {
1518 enableFileFingerprintCache ,
1619 getFileFingerprint ,
20+ repoRoot ,
1721 run ,
1822} from "./utils.mts" ;
1923
@@ -212,6 +216,38 @@ test("invocation fingerprints reuse metadata and invalidate writes and commands"
212216 }
213217} ) ;
214218
219+ test ( "fingerprint cache scopes can overlap" , context => {
220+ const directory = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "tsgo-fingerprint-" ) ) ;
221+ context . after ( ( ) => fs . rmSync ( directory , { recursive : true , force : true } ) ) ;
222+ const input = path . join ( directory , "input.ts" ) ;
223+ fs . writeFileSync ( input , "input" ) ;
224+ const stats = context . mock . method ( fs , "statSync" ) ;
225+ syncBuiltinESMExports ( ) ;
226+ context . after ( ( ) => {
227+ stats . mock . restore ( ) ;
228+ syncBuiltinESMExports ( ) ;
229+ } ) ;
230+ const disableFirst = enableFileFingerprintCache ( ) ;
231+ const disableSecond = enableFileFingerprintCache ( ) ;
232+ try {
233+ getFileFingerprint ( input ) ;
234+ getFileFingerprint ( input ) ;
235+ assert . equal ( stats . mock . callCount ( ) , 1 ) ;
236+ disableFirst ( ) ;
237+ getFileFingerprint ( input ) ;
238+ getFileFingerprint ( input ) ;
239+ assert . equal ( stats . mock . callCount ( ) , 2 ) ;
240+ disableSecond ( ) ;
241+ getFileFingerprint ( input ) ;
242+ getFileFingerprint ( input ) ;
243+ assert . equal ( stats . mock . callCount ( ) , 4 ) ;
244+ }
245+ finally {
246+ disableSecond ( ) ;
247+ disableFirst ( ) ;
248+ }
249+ } ) ;
250+
215251test ( "command cache snapshots and generated files share invocation fingerprints" , async context => {
216252 const directory = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "tsgo-fingerprint-" ) ) ;
217253 context . after ( ( ) => fs . rmSync ( directory , { recursive : true , force : true } ) ) ;
@@ -251,11 +287,12 @@ test("changing inputs during generation does not mark stale output current", con
251287 assert . equal ( new GeneratedFile ( output , [ input ] , cache ) . isCurrent ( ) , false ) ;
252288} ) ;
253289
254- test ( "default metadata has a stable path in the OS temporary directory " , context => {
290+ test ( "default metadata is isolated by repository worktree " , context => {
255291 const directory = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "tsgo-codegen-" ) ) ;
256292 context . after ( ( ) => fs . rmSync ( directory , { recursive : true , force : true } ) ) ;
257293 const output = path . join ( directory , "output.ts" ) ;
258- const cacheFile = path . join ( os . tmpdir ( ) , "typescript-codegen" , createHash ( "sha256" ) . update ( output ) . digest ( "hex" ) + ".json" ) ;
294+ assert . equal ( path . basename ( defaultCacheDirectory ) , createHash ( "sha256" ) . update ( repoRoot ) . digest ( "hex" ) ) ;
295+ const cacheFile = path . join ( defaultCacheDirectory , createHash ( "sha256" ) . update ( output ) . digest ( "hex" ) + ".json" ) ;
259296 context . after ( ( ) => fs . rmSync ( cacheFile , { force : true } ) ) ;
260297 const file = new GeneratedFile ( output , [ ] ) ;
261298 file . write ( "generated" ) ;
@@ -319,9 +356,9 @@ test("generate:go runs Go generators directly and shares caches with Go fallback
319356 assert . match ( current . stdout , / U n i c o d e t a b l e s a r e u p t o d a t e / ) ;
320357 assert . deepEqual ( files . map ( file => fs . statSync ( path . join ( root , file ) ) . mtimeMs ) , timestamps ) ;
321358 const fallback = await x ( "go" , [ "-C" , "./tsc" , "generate" , "./internal/diagnostics" ] , { throwOnError : true , nodeOptions : { cwd : root } } ) ;
322- assert . equal ( fallback . stdout . match ( / C o d e g e n o u t p u t s a r e u p t o d a t e / g) ?. length , 2 ) ;
359+ assert . equal ( fallback . stdout . match ( / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / g) ?. length , 2 ) ;
323360 const nested = await x ( "npx" , [ "hereby" , "generate:compileroptions" ] , { throwOnError : true , nodeOptions : { cwd : path . join ( root , "tsc/internal/core" ) } } ) ;
324- assert . equal ( nested . stdout . match ( / C o d e g e n o u t p u t s a r e u p t o d a t e / g) ?. length , 2 ) ;
361+ assert . equal ( nested . stdout . match ( / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / g) ?. length , 2 ) ;
325362} ) ;
326363
327364test ( "generate includes standalone generators without Go traversal" , async ( ) => {
@@ -465,7 +502,7 @@ test("bundled generation skips unchanged library outputs", async () => {
465502 await generate ( ) ;
466503 const files = [ "libs_generated.go" , "embed_generated.go" ] . map ( file => path . join ( root , "tsc/internal/bundled" , file ) ) ;
467504 const timestamps = files . map ( file => fs . statSync ( file ) . mtimeMs ) ;
468- assert . match ( ( await generate ( ) ) . stdout , / C o d e g e n o u t p u t s a r e u p t o d a t e \. / ) ;
505+ assert . match ( ( await generate ( ) ) . stdout , / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / ) ;
469506 assert . deepEqual ( files . map ( file => fs . statSync ( file ) . mtimeMs ) , timestamps ) ;
470507} ) ;
471508
@@ -684,7 +721,7 @@ test("Unicode generation repairs outputs and supports force", async context => {
684721 assert . match ( ( await generate ( ) ) . stdout , / G e n e r a t e d U n i c o d e t a b l e s \. / ) ;
685722 assert . deepEqual ( fs . readFileSync ( file ) , originals [ index ] ) ;
686723 }
687- const cacheFile = path . join ( os . tmpdir ( ) , "typescript-codegen" , createHash ( "sha256" ) . update ( files [ 0 ] ) . digest ( "hex" ) + ".json" ) ;
724+ const cacheFile = path . join ( defaultCacheDirectory , createHash ( "sha256" ) . update ( files [ 0 ] ) . digest ( "hex" ) + ".json" ) ;
688725 context . after ( ( ) => fs . rmSync ( cacheFile , { force : true } ) ) ;
689726 fs . rmSync ( cacheFile ) ;
690727 assert . match ( ( await generate ( ) ) . stdout , / G e n e r a t e d U n i c o d e t a b l e s \. / ) ;
@@ -719,7 +756,7 @@ test("enum generation skips unchanged outputs and Go verification", async () =>
719756 assert . match ( forced . stdout , / A l l g e n e r a t e d v a l u e s m a t c h G o \. / ) ;
720757 assert . match ( ( await generate ( ) ) . stdout , / E n u m s a r e u p t o d a t e \. / ) ;
721758 const verifier = path . join ( root , "tsc/internal/api/enum_values_generated.go" ) ;
722- const cacheFile = path . join ( os . tmpdir ( ) , "typescript-codegen" , createHash ( "sha256" ) . update ( verifier ) . digest ( "hex" ) + ".json" ) ;
759+ const cacheFile = path . join ( defaultCacheDirectory , createHash ( "sha256" ) . update ( verifier ) . digest ( "hex" ) + ".json" ) ;
723760 fs . rmSync ( cacheFile ) ;
724761 const regenerated = await generate ( ) ;
725762 assert . match ( regenerated . stdout , / A l l g e n e r a t e d v a l u e s m a t c h G o \. / ) ;
@@ -730,17 +767,19 @@ test("AST generation forwards force to schema generators and the kind stringer",
730767 const root = path . resolve ( import . meta. dirname , "../../.." ) ;
731768 const generate = ( force = false ) => x ( "npx" , [ "hereby" , "generate:ast" , ...( force ? [ "--force" ] : [ ] ) ] , { throwOnError : true , nodeOptions : { cwd : root } } ) ;
732769 await generate ( ) ;
770+ const output = path . join ( root , "tsc/internal/ast/kind_stringer_generated.go" ) ;
771+ const oldTime = new Date ( "2000-01-01T00:00:00Z" ) ;
772+ fs . utimesSync ( output , oldTime , oldTime ) ;
733773 const forced = await generate ( true ) ;
734774 assert . match ( forced . stdout , / W r o t e .* e n c o d e r _ g e n e r a t e d \. g o / ) ;
735775 assert . match ( forced . stdout , / W r o t e .* a s t _ g e n e r a t e d \. g o / ) ;
736776 assert . match ( forced . stdout , / G e n e r a t e d .* a s t \. g e n e r a t e d \. t s / ) ;
737- assert . match ( forced . stdout , / G e n e r a t e d c o d e g e n o u t p u t s \. / ) ;
738- const output = path . join ( root , "tsc/internal/ast/kind_stringer_generated.go" ) ;
777+ assert . notEqual ( fs . statSync ( output ) . mtimeMs , oldTime . getTime ( ) ) ;
739778 const timestamp = fs . statSync ( output ) . mtimeMs ;
740779 assert . doesNotMatch ( forced . stdout , / \$ n o d e .* t o o l s \/ s c r i p t s \/ t s c \/ g e n e r a t e \. t s / ) ;
741780 const current = await generate ( ) ;
742781 assert . doesNotMatch ( current . stdout , / (?: W r o t e | G e n e r a t e d ) / ) ;
743- assert . match ( current . stdout , / C o d e g e n o u t p u t s a r e u p t o d a t e \. / ) ;
782+ assert . match ( current . stdout , / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / ) ;
744783 assert . equal ( fs . statSync ( output ) . mtimeMs , timestamp ) ;
745784} ) ;
746785
@@ -764,14 +803,14 @@ test("API protocol generation caches formatted output and supports force", async
764803 await generate ( ) ;
765804 const timestamp = fs . statSync ( output ) . mtimeMs ;
766805 const current = await generate ( ) ;
767- assert . match ( current . stdout , / C o d e g e n o u t p u t s a r e u p t o d a t e \. / ) ;
806+ assert . match ( current . stdout , / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / ) ;
768807 assert . doesNotMatch ( current . stdout , / \$ n o d e .* c a c h e \. m t s / ) ;
769808 assert . equal ( fs . statSync ( output ) . mtimeMs , timestamp ) ;
770809 const unrelated = path . join ( root , "tsc/internal/parser/codegen_cache_probe.go" ) ;
771810 assert . equal ( fs . existsSync ( unrelated ) , false ) ;
772811 context . after ( ( ) => fs . rmSync ( unrelated , { force : true } ) ) ;
773812 fs . writeFileSync ( unrelated , "package parser\n" ) ;
774- assert . match ( ( await generate ( ) ) . stdout , / C o d e g e n o u t p u t s a r e u p t o d a t e \. / ) ;
813+ assert . match ( ( await generate ( ) ) . stdout , / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / ) ;
775814 fs . rmSync ( unrelated ) ;
776815 const schema = path . join ( root , "tsc/internal/api/requestfilesystem/codegen_cache_probe.go" ) ;
777816 assert . equal ( fs . existsSync ( schema ) , false ) ;
@@ -783,17 +822,21 @@ test("API protocol generation caches formatted output and supports force", async
783822 }
784823 } ) ;
785824 fs . writeFileSync ( schema , 'package requestfilesystem\n\nconst KindCodegenCacheProbe Kind = "codegen-cache-probe"\n' ) ;
786- assert . match ( ( await generate ( ) ) . stdout , / G e n e r a t e d c o d e g e n o u t p u t s \. / ) ;
825+ await generate ( ) ;
787826 assert . match ( fs . readFileSync ( output , "utf8" ) , / " c o d e g e n - c a c h e - p r o b e " / ) ;
788827 fs . rmSync ( schema ) ;
789- assert . match ( ( await generate ( ) ) . stdout , / G e n e r a t e d c o d e g e n o u t p u t s \. / ) ;
828+ await generate ( ) ;
790829 assert . equal ( fs . readFileSync ( output , "utf8" ) , original ) ;
791- assert . match ( ( await generate ( true ) ) . stdout , / G e n e r a t e d c o d e g e n o u t p u t s \. / ) ;
792- assert . match ( ( await generate ( ) ) . stdout , / C o d e g e n o u t p u t s a r e u p t o d a t e \. / ) ;
830+ const oldTime = new Date ( "2000-01-01T00:00:00Z" ) ;
831+ fs . utimesSync ( output , oldTime , oldTime ) ;
832+ await generate ( true ) ;
833+ assert . notEqual ( fs . statSync ( output ) . mtimeMs , oldTime . getTime ( ) ) ;
834+ assert . match ( ( await generate ( ) ) . stdout , / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / ) ;
835+ fs . utimesSync ( output , oldTime , oldTime ) ;
793836 const nested = await x ( "go" , [ "-C" , "./tsc" , "generate" , "./internal/api" ] , {
794837 throwOnError : true ,
795838 nodeOptions : { cwd : root , env : { ...process . env , TSGO_HEREBY_FORCE : "1" } } ,
796839 } ) ;
797- assert . match ( nested . stdout , / G e n e r a t e d c o d e g e n o u t p u t s \. / ) ;
798- assert . match ( ( await generate ( ) ) . stdout , / C o d e g e n o u t p u t s a r e u p t o d a t e \. / ) ;
840+ assert . notEqual ( fs . statSync ( output ) . mtimeMs , oldTime . getTime ( ) ) ;
841+ assert . match ( ( await generate ( ) ) . stdout , / c o d e g e n o u t p u t s a r e a l r e a d y u p t o d a t e / ) ;
799842} ) ;
0 commit comments