JS-1383 Fix non-deterministic tsconfig discovery order#6478
Conversation
opendir() returns entries in filesystem-native order (not guaranteed to be stable across OSes, filesystems, or runs). Files discovered via a LIFO stack compound the non-determinism further. When multiple tsconfigs cover the same file, whichever is processed first wins, causing different type information (and thus different analysis results) across runs. Fix: sort foundLookupTsConfigs and foundPropertyTsConfigs in postProcess() once all tsconfigs have been discovered, ensuring a stable alphabetical order before analyzeWithProgram iterates over them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vdiez
left a comment
There was a problem hiding this comment.
I guess you where right when you pointed out we should be more deterministic when selecting tsconfigs
🤖 Generated with GitHub Actions
See this PR to fix the false positive #6463 |
| this.foundLookupTsConfigs.sort(); | ||
| // Sort property tsconfigs by their position in the user-provided list to preserve | ||
| // the intentional ordering from sonar.typescript.tsconfigPaths. | ||
| const providedOrder = this.providedPropertyTsConfigs?.map(p => p.path) ?? []; |
There was a problem hiding this comment.
it's a corner case, but if the user provided a glob instead of a path, this will not apply
Vuetify ruling differencesThe sorting change produced ruling differences in the vuetify project, mainly in S4328 (imports). These are expected and were automatically resolved by the ruling bot. Root cause: vuetify has a root tsconfig.json (no include/files — covers everything) plus sub-package tsconfigs (packages/docs/tsconfig.json, packages/vuetify/tsconfig.json, etc.). All extend the root and use lib: ["esnext"], so lib differences are not the issue here. Before the fix, filesystem traversal could process the root tsconfig first, meaning ALL files were analyzed as one big program. After sorting alphabetically, sub-package tsconfigs are processed first (packages/api-generator/ → packages/docs/ → packages/vuetify/ → root), so files get analyzed in |
With alphabetical sorting, src/tsconfig.json is processed before the root tsconfig.json. Since src/tsconfig.json has baseUrl, it correctly resolves modules and finds the issue — fixing a previous false negative. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
288d41a to
f981a4b
Compare
|








Summary
opendir()returns directory entries in filesystem-native order, which is not guaranteed to be consistent across OSes, filesystems, or runs (e.g. ext4 withdir_index, APFS). Combined with the LIFO stack traversal infindFiles, the order in whichtsconfig.jsonfiles are discovered varies.When multiple tsconfigs cover the same source file, whichever tsconfig is processed first in
analyzeWithProgramwins — the file is removed frompendingFilesand skipped by all subsequent tsconfigs. This means different runs can analyze the same file with different TypeScript programs (and different compiler options / lib settings), producing non-deterministic analysis results.Concrete example: In the TypeScript ruling project,
src/services/formatting/rulesMap.tscan be pulled into different tsconfigs depending on filesystem order:Successful run (example): tsconfigs discovered in order
TypeScript/src/compiler/tsconfig.json,TypeScript/src/harness/tsconfig.json—rulesMap.tsis picked up byharness/tsconfig.jsonwhich haslib: ["es6"]. S7728 correctly fires (arrays haveSymbol.iterator).Unsuccessful run (example): tsconfigs discovered in order
TypeScript/scripts/importDefinitelyTypedTests/tsconfig.json,TypeScript/scripts/tslint/tsconfig.json,TypeScript/src/server/tsconfig.json—rulesMap.tsgets transitively pulled intoserver/tsconfig.jsonwhich haslib: ["es5"]. S7728 is suppressed (noSymbol.iteratoron arrays in ES5), causing the ruling snapshot to differ.Fix
Sort tsconfigs in
postProcess()once after all files have been discovered:sonar.typescript.tsconfigPathsnot set): sorted alphabetically for stable orderingsonar.typescript.tsconfigPathsset): sorted by their position in the user-provided list, preserving the intentional orderingTest plan
🤖 Generated with Claude Code