-
Notifications
You must be signed in to change notification settings - Fork 192
JS-946 Implement direct TypeScript program caching #5921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Latest Changes Summary1. Global Source File Content Cache with Lazy Loading
2. Global TSConfig Content Cache
3. Refactored
|
Updates - November 19, 2025Module Resolution FixFixed failing test "jsonParse does not resolve imports, createProgram does" by adding proper module resolution defaults:
Root cause: Without these defaults, TypeScript falls back to Classic module resolution which doesn't support:
This ensures modern module resolution works correctly for all analysis runs. Global SourceFile CacheImplemented a significant performance optimization by adding a global cache for parsed TypeScript SourceFile ASTs: Architecture:
Benefits:
API (in getCachedSourceFile(fileName, scriptTarget, contentHash): ts.SourceFile | undefined
setCachedSourceFile(fileName, scriptTarget, contentHash, sourceFile): void
invalidateCachedSourceFile(fileName): void
clearSourceFileContentCache(): void // Now clears both content and parsed cachesDesign Decision - Per-Target Caching: We cache SourceFiles per
Rationale for keeping target-specific caching:
See detailed rationale in the code comments. If profiling shows diverse targets are common, we can simplify to target-agnostic caching (always use ESNext) with minimal impact. Config File DiagnosticsAdded support for tsconfig parsing diagnostics:
TSConfig Cache EnhancementUpdated tsconfig cache structure to track missing files:
Test Updates
DocumentationAdded
Status: ✅ All 13 tests passing, TypeScript compilation successful Commit: a3addf9 - "Add module resolution defaults and global SourceFile cache" |
d98064d to
b22519f
Compare
266c555 to
1b5a26d
Compare
|
🤖 Claude Code Investigation Update I'm continuing to debug the backslash-reference test issue. Initial findings:
Currently investigating |
636b9e4 to
88dcae3
Compare
88dcae3 to
3d797a0
Compare
|
| const [filename] = fileEvent; | ||
| if (isPackageJson(filename)) { | ||
| this.clearCache(); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch!
| * Analyzes a single file, optionally with a TypeScript program for type-checking. | ||
| * This is the common entry point for all analysis paths (with program, without program, with cache). | ||
| */ | ||
| export async function analyzeSingleFile( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| export async function analyzeSingleFile( | |
| export async function analyzeFile( |
| }; | ||
|
|
||
| // Analyze the file (with error handling) | ||
| const result = await analyzeFile(input); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const result = await analyzeFile(input); | |
| const result = await analyzeInput(input); |
| * @param input JsTsAnalysisInput object containing all the data necessary for the analysis | ||
| */ | ||
| export async function analyzeFile(input: JsTsAnalysisInput): Promise<FileResult> { | ||
| async function analyzeFile(input: JsTsAnalysisInput): Promise<FileResult> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| async function analyzeFile(input: JsTsAnalysisInput): Promise<FileResult> { | |
| async function analyzeInput(input: JsTsAnalysisInput): Promise<FileResult> { |
| programOptionsFromClosestTsconfig(filename, results, foundProgramOptions, pendingFiles), | ||
| ); | ||
|
|
||
| await analyzeSingleFile( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| await analyzeSingleFile( | |
| await analyzeFile( |
| if (filesContext?.[fileName]) { | ||
| filesContext[fileName].fileContent = content; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why only if it exists? I guess I'm getting lost with all of these caches.
| // 3. Fallback to filesystem | ||
| this.trackFsCall('fileExists-disk', fileName); | ||
| return this.baseHost.fileExists(fileName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should cache this, no? Otherwise when something is missing, we will keep on touching the filesystem
| const content = this.readFile(fileName); | ||
| let sourceFile: (ts.SourceFile & { version?: string }) | undefined; | ||
| if (content) { | ||
| this.trackFsCall('getSourceFile', fileName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this extra check here? The FsCall occurs in this.readFile, no?
| sourceFile = this.baseHost.getSourceFile( | ||
| fileName, | ||
| languageVersionOrOptions, | ||
| onError, | ||
| shouldCreateNewSourceFile, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't this.readFile fallback to fs, so if we don't have the contents, then too bad?
| * | ||
| * For project-based analysis with tsconfig, use createProgramOptions() + createStandardProgram(). | ||
| */ | ||
| export function createProgramFromSingleFile( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this only used for tests? Why keep it?




JS-946
JS-946
Summary
This PR replaces typescript-eslint's internal program creation with direct TypeScript program management, giving full control over
CompilerHostand enabling efficient program caching with incremental updates for SonarLint analysis.Core Objectives
Architecture
New Components
1. IncrementalCompilerHost (
packages/jsts/src/program/incrementalCompilerHost.ts)CompilerHostimplementation for TypeScript programs2. ProgramCacheManager (
packages/jsts/src/program/programCacheManager.ts)Hybrid LRU + WeakMap caching strategy:
3. Program Creation Functions (
packages/jsts/src/program/program.ts)extractCompilerOptions(): Parses tsconfig.json with properextendsresolution (including from node_modules)mergeCompilerOptions(): Merges all discovered tsconfig compiler optionscreateOrGetCachedProgramForFile(): Main entry point for program cachingModified Components
4. analyzeWithWatchProgram (
packages/jsts/src/analysis/projectAnalysis/analyzeWithWatchProgram.ts)Complete rewrite of SonarLint analysis flow:
files/include/exclude)ts.Programto typescript-eslint (nottsConfigsarray)5. Simplified TSConfig Discovery (
packages/jsts/src/analysis/projectAnalysis/tsconfigCache.ts,file-stores/tsconfigs.ts)Removed (~80 lines):
Kept:
TypeScript Program Type: SemanticDiagnosticsBuilderProgram
Why This Choice
The implementation uses
ts.createSemanticDiagnosticsBuilderProgram(). I think this appears to be a good fit because:createProgram()SemanticDiagnosticsBuilder✅EmitAndSemanticDiagnosticsBuilderIncrementalProgram.tsbuildinfo)* Requires disk I/O for
.tsbuildinfofilesAdvantages:
Incremental behavior:
Trade-offs with alternatives:
createProgram()- Simpler but no incremental updates (parses everything each time)EmitAndSemanticDiagnosticsBuilder- Higher memory for emit tracking we don't useIncrementalProgram- Lower memory but requires.tsbuildinfodisk I/O (problematic for concurrent IDE instances)All program types provide identical type checker APIs - the difference is in update efficiency, not functionality.
Key Features
1. Per-File Caching Strategy
Instead of creating programs containing all requested files, we:
Example:
This allows multiple smaller, focused programs to coexist rather than requiring large programs containing all files.
2. Incremental Updates with Content Change Detection
3. Compiler Options Merging
This approach:
extends(including fromnode_moduleslike@tsconfig/node16)Analysis Flow
Expected Performance Characteristics
These are estimates that should be validated with real-world usage.
Memory Profile
Typical SonarLint session (10 cached programs):
Benefits
Breaking Changes
Removed APIs
Cache.getTsConfigForInputFile()- No longer neededCache.getTsConfigMapForInputFile()- Removed BFS logicCache.clearFileToTsConfigCache()- Not applicableTsConfigStore.getTsConfigForInputFile()- Not used anymoreTest Updates Needed
Test file
packages/jsts/tests/analysis/tsconfigs.test.tsneeds updates as it tests the removed file-to-tsconfig mapping functionality.Testing
Main source code compiles successfully:
Test files have compilation errors due to removed APIs (need updates).
Future Improvements
maxSizeconfigurable via settings