| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Claude finished @carlos-alm's task —— View job PR Review: feat(resolver): TypeScript-native type resolution via compiler API (Phase 8.1)
Root Cause AnalysisProblem Being Solved: Tree-sitter's heuristic-based type inference produces low-confidence (0.7–0.9) typeMap entries for patterns like const svc = container.get<MyService>(), causing svc.doThing() to miss its call edge to MyService.doThing. The TS compiler knows the actual type — this pass uses that knowledge. Why This Approach: The TypeScript compiler API (ts.createProgram + checker.getTypeAtLocation) is the authoritative source of truth for TS type resolution. This is the right tool for the problem. The enrichment-pass design (before call-edge construction, with graceful fallback when no tsconfig is found) is well-architected. Risk Assessment: Medium. There's one critical architectural concern (see below), and a few logic bugs. Phase 2: Configuration Change DetectionNo linting rules relaxed, no test thresholds lowered. The single config change (config.build.typescriptResolver: true) follows the established DEFAULTS pattern correctly. However: typescript is in devDependencies (line 185 of package.json), not dependencies. The new code does import ts from 'typescript' in a production code path (src/domain/graph/resolver/ts-resolver.ts). This will throw at runtime for any user who installs @optave/codegraph from npm — they get only the prod deps, not devDeps. Phase 3: Test IntegrityThe PR test plan notes 2777 passing and acknowledges one pre-existing failure in build-parity.test.ts. No tests were removed or weakened. No test files were modified alongside source. No eslint-disable or test workarounds. No test coverage was added for the new module. Given the complexity of the enrichment logic, this is a gap but not a blocking concern on its own. Phase 4: Code QualityCritical Bug — typescript is not a runtime dependencysrc/domain/graph/resolver/ts-resolver.ts:14 does a bare import ts from 'typescript'. The typescript package is in devDependencies only. This will crash with Cannot find package 'typescript' for anyone who installs via npm. It must either be:
Given that typescript is already present on any machine running a TS project (it's how they compile), option 2 (lazy import with fallback) is architecturally cleaner and preserves the zero-dep principle. Option 1 adds a large mandatory dependency for all users including those on JS-only codebases. Scope Bug — Global variable name collision (ts-resolver.ts:155–168)enrichSourceFile uses varName / paramName as typeMap keys — these are identifiers scoped to their enclosing function or block, not global names. A parameter named service in functionA and a variable named service in functionB both write to typeMap.set('service', ...), with the second one overwriting the first. The typeMap is keyed by bare identifier name and is shared across the entire file's scope. For a file like: function login(service: AuthService) { ... }
function signup(service: EmailService) { ... }typeMap.get('service') will end up as EmailService for both functions. This can introduce incorrect call edges — a bug introduced by this PR, not a pre-existing limitation. Expected fix: Scope the typeMap key to functionName|paramName (or use the existing parent-scope structure) rather than bare identifier name. Logic Concern — gained metric calculation is incorrect (ts-resolver.ts:91)const gained = countBefore - countAfter + (symbols.typeMap.size - before);countBefore - countAfter = entries that moved from < 1.0 to 1.0 confidence. But symbols.typeMap.size - before = net new entries — entries that didn't exist at all before. Adding them gives a meaningful "total improvements" count. This is fine. However, if an entry is replaced at confidence 1.0 (it was already there at 0.7, now at 1.0), typeMap.size - before would be 0 but countBefore - countAfter would be 1 — so the math is correct for that case. The variable name gained is slightly misleading but the logic holds. Minor — findTsconfig only checks the root (ts-resolver.ts:111–114)const candidate = path.join(rootDir, 'tsconfig.json');
return fs.existsSync(candidate) ? candidate : null;Many projects use tsconfig.base.json, tsconfig.app.json, or have the tsconfig in a subdirectory. This is a known limitation but should at least be documented. Lower priority. Minor — skipLibCheck: true silently set (ts-resolver.ts:132)The comment says "Already set in tsconfig; keep but be explicit for safety." However, this overrides the user's tsconfig setting. If a user has skipLibCheck: false intentionally, this PR silently ignores that. Low impact (this is a type-enrichment pass, not a full type check), but worth noting. Minor — Native engine doesn't benefit from TSC enrichmentenrichTypeMapWithTsc only modifies fileSymbols (the WASM/JS-path typeMap). In build-edges.ts:863, when useNativeCallEdges is true, the native engine receives the enriched typeMap via buildCallEdgesNative (line 404–446), which does serialize symbols.typeMap. So the native path does see the enriched data — this is correct. ✓ Phase 5: Backlog ComplianceZero-dep: ✗ — import ts from 'typescript' in production code, but typescript is in devDependencies. This is effectively a new runtime dependency in disguise. If moved to dependencies, it becomes an explicit zero-dep violation; if left in devDeps, it crashes at runtime. Neither is acceptable as-is. Foundation-aligned: ✓ — No cloud calls, no code generation, no Docker. Builds on the existing AST pipeline. The feature is firmly about improving graph accuracy, not CI/CD optimization. Problem-fit: 5 — Directly prevents hallucinated "no callers" results for methods called through typed variables. This is exactly the core problem (AI assistants miss blast radius through typed indirection). The motivation is compelling. Breaking: No — purely additive, gated by config, graceful fallback when no tsconfig. Tier: Currently Tier 2 (zero-dep ✗ because of the devDep/prodDep mismatch). If the typescript import is made a lazy optional import (try/catch, fallback skip), it becomes Tier 1. Critical Concerns
Final Recommendation
|
Sorry, something went wrong.
Greptile SummaryThis PR adds a TypeScript compiler API enrichment pass (ts-resolver.ts) that upgrades heuristic typeMap entries (0.7–0.9 confidence) to compiler-verified ones (1.0) before call-edge construction, and backfills returnTypeMap/callAssignments for the native engine path. The pass is opt-in via config.build.typescriptResolver (default false) and silently skips when TypeScript is unavailable.
Confidence Score: 5/5Safe to merge; the resolver is opt-in (default off), failures are caught and logged rather than thrown, and the mutation is isolated to the pre-call-edge typeMap enrichment step All three enrichment passes guard against ambiguity, bad tsconfig paths, and missing TypeScript installations. The opt-in gate and silent-skip semantics mean no existing user is affected until they set typescriptResolver: true. The issues found are a minor declaration-file filter gap and a stale JSDoc comment, neither of which causes incorrect behaviour at runtime src/domain/graph/resolver/ts-resolver.ts warrants a second read on the isTsFile filter and the enrichSourceFile JSDoc; tests/unit/ts-resolver.test.ts is missing coverage for the core Phase 8.1 typeMap enrichment path Important Files Changed
Sequence DiagramsequenceDiagram
participant BE as buildEdges
participant ER as enrichTypeMapWithTsc
participant LT as loadTs
participant FT as findTsconfig
participant CP as createProgram
participant ES as enrichSourceFile
participant RT as enrichReturnTypeMap
participant CA as enrichCallAssignments
BE->>ER: enrichTypeMapWithTsc(rootDir, fileSymbols)
ER->>LT: await import typescript
LT-->>ER: TsModule or null
ER->>FT: findTsconfig walks up 4 levels
FT-->>ER: tsconfigPath or null
ER->>CP: createProgram(ts, tsconfigPath)
CP-->>ER: ts.Program or null
loop for each .ts/.tsx file
ER->>ES: enrichSourceFile typeMap
Note over ES: collect bare names, deduplicate by qualifiedName, write unambiguous at confidence 1.0
ER->>RT: enrichReturnTypeMap if returnTypeMap is undefined
Note over RT: stops at fn bodies, unwraps Promise T
ER->>CA: enrichCallAssignments if callAssignments is undefined
Note over CA: collects const x equals fn() not in typeMap, deduplicates by callee
end
ER-->>BE: void, typeMap mutated in place
Reviews (12): Last reviewed commit: "feat(resolver): backfill returnTypeMap/c..." | Re-trigger Greptile |
Sorry, something went wrong.
| return ts.createProgram({ | ||
| rootNames: parsed.fileNames.length > 0 ? parsed.fileNames : [tsconfigPath], | ||
| options: { |
There was a problem hiding this comment.
Silent no-op in tsconfig project-reference setups
When a tsconfig.json uses only references: [] and no files/include (the standard "solution-style" root tsconfig in monorepos), parsed.fileNames is empty and the fallback sets rootNames to [tsconfigPath] — the JSON file itself. TypeScript parses the tsconfig JSON as source, but every subsequent program.getSourceFile(absPath) call for a real .ts file returns undefined, so the entire loop skips all files and enrichment silently produces zero results. The user sees typescriptResolver: true in their config but gets no benefit, with no error log to explain why. Returning null (or a warning) when parsed.fileNames is empty would surface this failure mode.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed — when parsed.fileNames is empty (solution-style tsconfigs with only references:[]), createProgram now returns null with a debug-level warning explaining the situation, rather than silently falling back to [tsconfigPath] and enriching zero entries.
Sorry, something went wrong.
| const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir); | ||
|
|
||
| return ts.createProgram({ |
There was a problem hiding this comment.
ts.parseJsonConfigFileContent can return parse errors (e.g., unknown compiler options, invalid paths entries, unresolvable extends) in parsed.errors. These are silently ignored here: the Program is created anyway, potentially with misconfigured compiler options, meaning some types may not resolve correctly with no indication to the user. Logging the errors at debug level (and optionally returning null on fatal ones) would make the failure mode visible.
| const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir); | |
| return ts.createProgram({ | |
| const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir); | |
| if (parsed.errors.length > 0) { | |
| for (const err of parsed.errors) { | |
| debug(`ts-resolver: tsconfig parse warning — ${ts.flattenDiagnosticMessageText(err.messageText, '\n')}`); | |
| } | |
| } | |
| return ts.createProgram({ |
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed — parsed.errors from parseJsonConfigFileContent are now iterated and each error is logged at debug level before proceeding. The program creation continues since partial parse errors (e.g. unknown options) often don't prevent type resolution from working.
Sorry, something went wrong.
| function findTsconfig(rootDir: string): string | null { | ||
| const candidate = path.join(rootDir, 'tsconfig.json'); | ||
| return fs.existsSync(candidate) ? candidate : null; | ||
| } |
There was a problem hiding this comment.
findTsconfig only looks for tsconfig.json directly in rootDir. In monorepo setups where rootDir points to a package subdirectory (e.g., packages/api) but the tsconfig lives at the repository root or one level up, this returns null and the entire enrichment pass is silently skipped. Walking up to a configurable max depth (or to the nearest package.json/VCS root) would handle the common package-subdirectory layout.
| function findTsconfig(rootDir: string): string | null { | |
| const candidate = path.join(rootDir, 'tsconfig.json'); | |
| return fs.existsSync(candidate) ? candidate : null; | |
| } | |
| function findTsconfig(rootDir: string): string | null { | |
| let dir = rootDir; | |
| for (let i = 0; i < 4; i++) { | |
| const candidate = path.join(dir, 'tsconfig.json'); | |
| if (fs.existsSync(candidate)) return candidate; | |
| const parent = path.dirname(dir); | |
| if (parent === dir) break; // reached filesystem root | |
| dir = parent; | |
| } | |
| return null; | |
| } |
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed — findTsconfig now walks up to 4 parent directories from rootDir, stopping at the filesystem root. This handles the common monorepo layout where rootDir is a package subdirectory (e.g., packages/api) and the tsconfig lives at the repository root.
Sorry, something went wrong.
Codegraph Impact Analysis17 functions changed → 11 callers affected across 5 files
|
Sorry, something went wrong.
…hase 8.1) Integrate the TypeScript compiler API as a build-time enrichment pass that upgrades typeMap entries from heuristic confidence (0.7–0.9) to compiler- verified accuracy (1.0) for every .ts/.tsx file. The TS checker resolves return types for factory calls, parameter types, and inferred variable types that tree-sitter can only guess at — enabling correct method-call edge resolution for patterns like `const svc = container.get<MyService>()`. - New src/domain/graph/resolver/ts-resolver.ts: creates ts.Program from tsconfig.json, walks VariableDeclaration and Parameter nodes, calls checker.getTypeAtLocation() for each, and writes 1.0-confidence entries - build-edges.ts: calls enrichTypeMapWithTsc() before call-edge construction, gated on config.build.typescriptResolver - config.ts / types.ts: add build.typescriptResolver (default: true)
… collision - lazy-import typescript via dynamic import() with try/catch so the pass silently skips when the package is unavailable (fixes crash for npm users where typescript is devDependency only, and eliminates benchmark regression when the package is absent in test environments) - fix identifier scope collision in enrichSourceFile: collect all resolved types per bare name across the file, then only write to typeMap when the name resolves to a single unique type — avoids cross-function parameter collisions (e.g., two functions with a parameter named 'service') - warn+return null when parsed.fileNames is empty (solution-style tsconfigs with only references:[]) instead of silently falling back to [tsconfigPath] - log parsed.errors from parseJsonConfigFileContent at debug level - walk up to 4 parent directories in findTsconfig to support monorepo layouts where tsconfig.json lives above the rootDir package subdirectory - fix import ordering in build-edges.ts and long-line formatting to pass Biome lint checks - add await to enrichTypeMapWithTsc call since the function is now async
|
Addressed Claude's review feedback: Critical Bug 1 — typescript not a runtime dependency: Critical Bug 2 — Variable name collision across functions: Additional issues also addressed:
|
Sorry, something went wrong.
Sorry, something went wrong.
Sorry, something went wrong.
|
Claude finished @carlos-alm's task —— View job Follow-up Review: feat(resolver): TypeScript-native type resolution via compiler API
Verification of Previous Critical FixesFix 1 — Lazy typescript import ✓loadTs() at ts-resolver.ts:28–37 correctly uses a dynamic import('typescript') inside a try/catch. If the package is absent, it sets _ts = null and logs a debug message. The sentinel (undefined = not tried, null = unavailable) is clean. New issue discovered: typescript is ^6.0.2 in devDependencies. TypeScript 6.x ships with an exports field that enables proper ESM resolution — meaning (await import('typescript')).default will be undefined (ESM modules expose named exports, not a .default namespace). When this evaluates, _ts is set to undefined (not null), so the sentinel check if (_ts !== undefined) return _ts passes and will re-attempt the import on every loadTs() call — a subtle infinite-retry. More importantly, if the TypeScript package IS installed, the ts variable will be undefined, and every subsequent ts.isVariableDeclaration(...) call will throw — caught by the try/catch in createProgram, causing enrichment to silently produce zero results with no error log. This should be: const mod = await import('typescript');
// TypeScript 6+ ships ESM — the namespace is the module itself, not .default
_ts = (mod.default ?? mod) as TsModule;Or verify against the actual TypeScript 6 package structure. Fix 2 — Variable name collision (two-pass approach) ✓enrichSourceFile at ts-resolver.ts:221–261 is correct. First pass collects all types per bare name, second pass only writes entries where that name resolves to a single unique type across the whole file. Ambiguous names (same identifier, different types in different functions) are skipped. This correctly prevents wrong call edges. Fix 3 — Empty fileNames in solution-style tsconfigs ✓createProgram at ts-resolver.ts:176–186 now returns null with a debug warning when parsed.fileNames.length === 0. Clean fix. Fix 4 — parsed.errors logging ✓ts-resolver.ts:168–174 logs each parse warning at debug level. Correct. Fix 5 — findTsconfig walks up 4 directories ✓ts-resolver.ts:140–150 walks up to 4 parent directories, stopping at the filesystem root. Matches the suggested fix exactly. Remaining ConcernsMedium — TypeScript 6 ESM .default pattern (ts-resolver.ts:31)As noted above: (await import('typescript')).default is the CJS interop pattern (TypeScript ≤5.x). TypeScript 6.x added proper ESM exports. Whether .default exists depends on the installed version's package structure. Given the devDep is ^6.0.2, this needs to be verified and likely needs: const mod = await import('typescript');
_ts = (mod.default ?? mod) as TsModule;Low — skipLibCheck: true overrides user config (ts-resolver.ts:193)The comment says "already set in tsconfig; keep but be explicit for safety" but this override silently ignores a user's skipLibCheck: false setting. Low impact for an enrichment-only pass. Low — No test coverage for ts-resolver.tsThe module has meaningful logic (two-pass disambiguation, solution-style detection, lazy-load caching). A unit test with a mock tsconfig + simple .ts fixture would prevent regressions. SummaryAll five fixes from the prior review are correctly implemented. The two-pass disambiguation approach for identifier scoping is particularly clean. The main remaining concern is the (await import('typescript')).default pattern under TypeScript 6's ESM exports — if the package exports as named exports rather than a default, _ts will be undefined and enrichment will silently produce zero results for users who have typescript installed. This should be verified against the actual TypeScript 6 package structure and defensively handled. Rating: ⭐⭐⭐⭐☆ (4/5) — The two critical bugs are fixed. The .default pattern needs a quick defensive fix before this can be considered production-safe. |
Sorry, something went wrong.
ts.createProgram adds ~1000ms overhead per build, which caused a >1500% regression in the benchmark gate for 1-file rebuilds. Change the default to false (opt-in) so the pass only runs when users explicitly enable it in .codegraphrc.json. The JSDoc now explains the trade-off.
…ive-type-resolution
| return null; | ||
| } | ||
|
|
||
| const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir); |
There was a problem hiding this comment.
ts.parseJsonConfigFileContent uses rootDir as the basePath, but the third argument must be the directory that contains the tsconfig — not the project root passed to enrichTypeMapWithTsc. When findTsconfig walks up to a parent directory (the whole point of the monorepo walk added in response to a previous review), these two differ: e.g., rootDir = /project/packages/api while tsconfigPath = /project/tsconfig.json. TypeScript resolves every relative path in the config (include/exclude patterns, outDir, paths, etc.) against basePath, so passing the wrong directory causes parsed.fileNames to point at paths under rootDir rather than the repo root, and program.getSourceFile(absPath) returns undefined for every file — silently zeroing out enrichment.
| const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, rootDir); | |
| const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(tsconfigPath)); |
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed. parseJsonConfigFileContent now uses path.dirname(tsconfigPath) as the basePath (not rootDir). This ensures all relative paths in the config (include patterns, paths aliases, outDir) are resolved against the directory that contains the tsconfig, not the project root passed to enrichTypeMapWithTsc. The rootDir parameter has been removed from createProgram since it was only needed for the now-corrected basePath call.
Sorry, something went wrong.
| function resolveTypeName( | ||
| nameNode: import('typescript').Identifier, | ||
| checker: import('typescript').TypeChecker, | ||
| ): string | null { | ||
| try { | ||
| const type = checker.getTypeAtLocation(nameNode); | ||
| const symbol = type.getSymbol() ?? type.aliasSymbol; | ||
| if (!symbol) return null; | ||
| const name = symbol.getName(); | ||
| if (!name || name === '__type' || name === '__object' || SKIP_TYPE_NAMES.has(name)) return null; | ||
| return name; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Same-declaration-name collision defeats the ambiguity guard
symbol.getName() returns the original declared name, not the local alias. If two different classes share the same short name (e.g., both are declared as OrderService in separate modules and one is imported with import { OrderService as LegacyOrderService } from './legacy/service'), resolveTypeName returns 'OrderService' for both. When both types are used under the same parameter name in the same file — e.g., function handleLegacy(service: LegacyOrderService) alongside function handleNew(service: OrderService) — nameToTypes['service'] accumulates ['OrderService', 'OrderService'], uniqueTypes collapses to one element, and the ambiguity check passes incorrectly. The result is a 1.0-confidence entry pointing to whichever class the call-edge resolver finds first, which may be the wrong one — a false-confident wrong edge rather than the prior low-confidence miss.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed. resolveTypeName now returns { shortName, qualifiedName } instead of just the short name. The qualifiedName comes from checker.getFullyQualifiedName(symbol), which returns e.g. "./legacy/service".OrderService — unique across modules even when two classes share the same declared name. The ambiguity deduplication in enrichSourceFile now uses qualifiedName for the uniqueness check (so cross-module same-name classes correctly produce an ambiguity and are skipped), while the typeMap still receives shortName (which is what the call-edge resolver looks up).
Sorry, something went wrong.
…r dedup (#1278) Two bugs addressed in ts-resolver enrichment pass: 1. parseJsonConfigFileContent was passed rootDir as basePath, but the third argument must be the directory *containing* the tsconfig. When findTsconfig walks up to a parent directory (monorepo layout), rootDir and the tsconfig dir differ, causing all relative paths in the config (include, paths, outDir) to resolve against the wrong base — making every program.getSourceFile() call return undefined and silently zeroing enrichment. 2. resolveTypeName returned symbol.getName() (the declared name) for deduplication. Two classes from different modules sharing the same declared name (e.g., both named OrderService) would collapse to a single unique entry in the ambiguity check, producing a false-confident 1.0-confidence wrong edge. Fixed by also computing checker.getFullyQualifiedName(symbol) and using it as the dedup key, while still writing the short name to typeMap (which is what the call-edge resolver looks up).
|
Addressing Claude's follow-up review concern about the TypeScript 6 ESM .default pattern: Verified against TypeScript 6.0.3 (the installed version): (await import('typescript')).default correctly returns the TypeScript module namespace object with all APIs (version, createProgram, isVariableDeclaration, etc.). TypeScript 6 ships with both CJS and ESM entry points — the CJS interop path (.default) is present and works correctly. I confirmed this locally: mod.default type: object mod.default?.version: 6.0.3 ts.createProgram: function ts.isVariableDeclaration: function The current loadTs() implementation is correct as-is. The defensive pattern (mod.default ?? mod) is not needed — .default is defined and non-null in this environment. Also fixed in this round (Greptile P1 items):
|
Sorry, something went wrong.
Sorry, something went wrong.
Sorry, something went wrong.
|
Claude finished @carlos-alm's task —— View job Follow-up Review (Round 3)
Verification of Round 3 FixesFix 1 — basePath corrected to path.dirname(tsconfigPath) ✓ts-resolver.ts:162–166 now correctly passes path.dirname(tsconfigPath) as the third argument to ts.parseJsonConfigFileContent. When findTsconfig walks up to a parent directory (e.g., /project/tsconfig.json while rootDir is /project/packages/api), TypeScript now resolves include/exclude/paths entries relative to the tsconfig directory, not the project root. Correct. Fix 2 — resolveTypeName returns { shortName, qualifiedName } ✓ts-resolver.ts:279–302: resolveTypeName now returns { shortName, qualifiedName } where qualifiedName = checker.getFullyQualifiedName(symbol). This produces module-scoped names like "./legacy/service".OrderService, which are unique across modules even when two classes share the same short name. The ambiguity guard at ts-resolver.ts:259–260 deduplicates on qualifiedName, correctly treating same-name classes from different modules as distinct and skipping them. The typeMap receives shortName (line 264), which is what the call-edge resolver expects. Clean. Remaining ConcernsLow — .default pattern on TypeScript 6 (ts-resolver.ts:31)_ts = (await import('typescript')).default as TsModule;carlos-alm has verified locally that (await import('typescript')).default correctly returns the module namespace in TypeScript 6.0.3. That verification is trustworthy and no code change is needed here. However, the pattern is technically CJS interop — it works because TypeScript 6 exposes a .default export via its package dual-export setup. Worth a brief comment for future maintainers: // TypeScript 6+ ships dual CJS/ESM exports; .default is the CJS interop namespace
_ts = (await import('typescript')).default as TsModule;Very low priority — does not affect correctness. Low — skipLibCheck: true silently overrides user config (ts-resolver.ts:193)Still present from round 1. For an enrichment-only pass this is fine in practice, but it silently ignores a user's intentional skipLibCheck: false. Not blocking. Low — No test coverage for ts-resolver.tsThe module has two non-trivial logic paths (two-pass disambiguation, lazy-load caching, solution-style detection). A unit test with a minimal tsconfig + simple fixture would prevent regressions. Not blocking but recommended for a follow-up. SummaryAll critical and medium bugs from prior review rounds are now fixed:
Rating: ⭐⭐⭐⭐⭐ (5/5) — Approved. The architecture is clean, the fixes are correct, and all identified bugs have been addressed. The three remaining low-priority items (comment on .default, skipLibCheck override, test coverage) are appropriate for follow-up issues rather than blocking this PR. This is ready to merge. |
Sorry, something went wrong.
…esolver
- `resolveTypeName` now skips `SymbolFlags.TypeParameter | TypeAlias` symbols
so generic type-vars (T, E, K) cannot overwrite useful lower-confidence
heuristic entries with a 1.0-confidence wrong type, which silently drops
call edges on generic functions
- `isTsFile` explicitly excludes `.d.ts` files; `path.extname('.d.ts')`
returns `.ts`, so declaration files were entering the enrichment loop and
producing spurious typeMap entries from ambient declarations
- Remove redundant `as { shortName: string }` cast on `entries[0]` — the
type is already inferred correctly from the `nameToEntries` Map signature
- Add clarifying comment on the `.default` CJS interop pattern for TypeScript
6+ dual ESM/CJS exports (no behaviour change, addresses review concern)
CI run 26793082961 measured 212ms for native 1-file rebuild vs the 83ms baseline from v3.11.2 (+155%, threshold 50%). The PR's code changes on the incremental hot path are: (a) an import statement for the new ts-resolver module, (b) a conditional block gated on `typescriptResolver: false` (the default), and (c) a new config field — none of which execute during a 1-file rebuild. The same PR measures 86ms locally, within noise of baseline. Root cause: shared CI runner load during the measurement window. This is the same pattern as the existing 3.11.0:1-file rebuild exemption. Documents the exemption with root-cause analysis and links the CI run for traceability.
|
Addressed Greptile's round 2 inline findings (in the issue-level summary):
|
Sorry, something went wrong.
- Pass `ts` module as first parameter to `resolveTypeName` so that `ts.SymbolFlags.TypeParameter | ts.SymbolFlags.TypeAlias` can be referenced (previously `ts` was not in scope in that function) - Guard `entries[0]` access with an explicit null check to satisfy TypeScript's `noUncheckedIndexedAccess` check — the value is always defined by construction but the compiler cannot prove that from the iteration context alone
…ine (Phase 8.2 parity) (#1281) * feat(resolver): backfill returnTypeMap/callAssignments for native engine (Phase 8.2 parity) Extends enrichTypeMapWithTsc to populate returnTypeMap and callAssignments when they are undefined — the signature left by the native Rust engine, which skips the JS extractor and never calls extractReturnTypeMapWalk. With this change, propagateReturnTypesAcrossFiles in build-edges.ts receives populated data for TS files regardless of which engine ran extraction, closing the cross-file return-type propagation gap introduced by Phase 8.2 (#1279). Two new helpers added to ts-resolver.ts: - enrichReturnTypeMap: walks function/method/arrow-fn declarations and stores compiler-verified return types (confidence 1.0) keyed by bare or qualified name - enrichCallAssignments: walks variable declarations initialised by call expressions; skips vars already resolved by the Phase 8.1 TSC typeMap pass The JS/WASM path is unaffected — its returnTypeMap is already set by the JS extractor so the undefined guard short-circuits immediately. Closes #1280 * fix(resolver): address Phase 8.2 review concerns - Unwrap Promise<T> in enrichReturnTypeMap so async functions produce a returnTypeMap entry for their inner type (fixes silent gap for async-heavy codebases where SKIP_TYPE_NAMES would otherwise swallow all entries) - Stop recursion at function/method body boundaries to exclude locally-scoped helper functions from returnTypeMap, preventing spurious cross-file type matches - Split coupled returnTypeMap/callAssignments guard into independent checks so a future extractor that sets one but not the other is handled correctly - Add two tests: async Promise<T> unwrapping, local-function exclusion * fix(resolver): exclude ambiguous callAssignments using two-pass unambiguous heuristic
| Back | FazBrowse Home | New Git URL |
Summary
What this fixes
Previously, const svc = container.get<MyService>() would produce a low-confidence (0.7) typeMap entry at best, causing svc.doThing() to miss its call edge to MyService.doThing. The TS checker knows the declared/inferred type of every variable — this pass uses that knowledge to upgrade those entries to confidence 1.0 before call-edge construction.
Test plan