From ff318e754c37c2331e33a65f2c8615c4a12edf63 Mon Sep 17 00:00:00 2001 From: n33 <1741170479@qq.com> Date: Mon, 12 Oct 2026 22:06:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(sync):=20=E5=A2=9E=E9=87=8F=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=87=8D=E6=96=B0=E6=A3=80=E6=B5=8B=E5=8F=97=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=E7=9A=84=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ __tests__/sync-framework-detection.test.ts | 28 ++++++++++++++++++++++ src/extraction/index.ts | 14 +++++++++-- src/index.ts | 3 +++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 __tests__/sync-framework-detection.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c750ed86e3..4a5d94dfc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes +- Incremental updates now detect frameworks introduced by newly added files, so their routes appear without a full re-index. + +### Fixes + - In VB.NET, every member of a `Structure` is now indexed, including its fields, properties, methods, constructors and nested enums. Before, only the first member was, so the rest could not be found and their callers looked empty. - In VB.NET and C#, what a property's `Get` and `Set` code calls, creates and reads now belongs to that property, as do C#'s `get => …` accessors and `=> …` property bodies. Before, it was dropped, so a method used only from a property looked unused. - In VB.NET, a field or property initializer like `= Compute()` or `As New List(Of Order)` now links what it calls and creates, and so do a `Custom Event`'s `AddHandler`, `RemoveHandler` and `RaiseEvent` blocks. Re-index VB.NET and C# projects after upgrading. diff --git a/__tests__/sync-framework-detection.test.ts b/__tests__/sync-framework-detection.test.ts new file mode 100644 index 0000000000..7a441fdec2 --- /dev/null +++ b/__tests__/sync-framework-detection.test.ts @@ -0,0 +1,28 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import CodeGraph from '../src/index'; +const projects: Array<{ root: string; graph: CodeGraph }> = []; +afterEach(async () => { + for (const { root, graph } of projects.splice(0)) { + await graph.close(); + fs.rmSync(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }); + } +}); +describe('framework detection across incremental updates', () => { + it.each([false, true])('extracts newly introduced Express routes (scoped=%s)', async (scoped) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-framework-sync-')); + fs.mkdirSync(path.join(root, 'src')); + fs.writeFileSync(path.join(root, 'src/app.ts'), 'export function boot() { return 1; }\n'); + const graph = CodeGraph.initSync(root); + projects.push({ root, graph }); + await graph.indexAll(); + expect(graph.getDetectedFrameworks()).not.toContain('express'); + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ dependencies: { express: '^4.18.0' } })); + fs.writeFileSync(path.join(root, 'src/routes.js'), "const app = express();\napp.get('/hello', handler);\n"); + await graph.sync(scoped ? { paths: ['src/routes.js'] } : {}); + expect(graph.getDetectedFrameworks()).toContain('express'); + expect(graph.getNodesInFile('src/routes.js').some(node => node.kind === 'route' && node.name === 'GET /hello')).toBe(true); + }); +}); diff --git a/src/extraction/index.ts b/src/extraction/index.ts index 8623d72034..e4775e59fa 100644 --- a/src/extraction/index.ts +++ b/src/extraction/index.ts @@ -1850,6 +1850,7 @@ export class ExtractionOrchestrator { * hasn't run yet so single-file re-index paths can detect on the spot. */ private detectedFrameworkNames: string[] | null = null; + private detectedFrameworkProbes = new Set(); /** * Scope matcher for SCOPED syncs, memoized on the mtimes of the two root * files it is derived from (`codegraph.json`, `.gitignore`). See @@ -1907,7 +1908,7 @@ export class ExtractionOrchestrator { * the DB hasn't been populated yet, but detect() only uses readFile, * fileExists, and getAllFiles, so that's fine. */ - private buildDetectionContext(files: string[]): ResolutionContext { + private buildDetectionContext(files: string[], probes?: Set): ResolutionContext { const rootDir = this.rootDir; return { getNodesInFile: () => [], @@ -1919,6 +1920,7 @@ export class ExtractionOrchestrator { getAllFiles: () => files, getProjectRoot: () => rootDir, fileExists: (relativePath: string) => { + probes?.add(relativePath); const full = validatePathWithinRoot(rootDir, relativePath); if (!full) return false; try { @@ -1928,6 +1930,7 @@ export class ExtractionOrchestrator { } }, readFile: (relativePath: string) => { + probes?.add(relativePath); const full = validatePathWithinRoot(rootDir, relativePath); if (!full) return null; try { @@ -1967,8 +1970,10 @@ export class ExtractionOrchestrator { private ensureDetectedFrameworks(files?: string[]): string[] { if (this.detectedFrameworkNames !== null) return this.detectedFrameworkNames; const fileList = files ?? scanDirectory(this.rootDir); - const context = this.buildDetectionContext(fileList); + const probes = new Set(); + const context = this.buildDetectionContext(fileList, probes); this.detectedFrameworkNames = detectFrameworks(context).map((r) => r.name); + this.detectedFrameworkProbes = probes; const declared = declaredDependencies(context); this.gatedFrameworks = new Map(); for (const name of this.detectedFrameworkNames) { @@ -3497,6 +3502,11 @@ export class ExtractionOrchestrator { await loadGrammarsForLanguages(preloadLanguagesForFiles(filesToIndex, overrides, this.rootDir)); } + if (filesAdded > 0 || filesRemoved > 0 || filesToIndex.some(file => this.detectedFrameworkProbes.has(file))) { + this.detectedFrameworkNames = null; + this.ensureDetectedFrameworks(scopedPaths?.length ? undefined : currentFiles); + } + // Index changed files const total = filesToIndex.length; for (let i = 0; i < filesToIndex.length; i++) { diff --git a/src/index.ts b/src/index.ts index 2e777dbf5a..5a350bf6e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -970,6 +970,9 @@ export class CodeGraph { // removal case — and runPostExtract starts by dropping the resolver's // name caches, which a long-lived daemon warmed against the // pre-removal graph.) + if (result.filesAdded > 0 || result.filesRemoved > 0) { + this.resolver.initialize(); + } if (result.filesAdded > 0 || result.filesModified > 0 || result.filesRemoved > 0) { this.resolver.runPostExtract(); }