From 6183a8cf0c10480fcbe16faaaca4a57ad0f13363 Mon Sep 17 00:00:00 2001 From: Manh Hung Date: Wed, 15 Jul 2026 16:24:24 +0700 Subject: [PATCH 1/3] feat: add full shader and build-script support --- THIRD_PARTY_NOTICES.md | 54 ++ __tests__/affected-vendored.test.ts | 78 ++ __tests__/build-script-extraction.test.ts | 127 +++ __tests__/cpp-local-lambda.test.ts | 83 ++ __tests__/extraction.test.ts | 16 + __tests__/same-name-disambiguation.test.ts | 82 ++ __tests__/shader-support.test.ts | 848 +++++++++++++++++++++ __tests__/status-json.test.ts | 24 + package.json | 3 +- src/bin/codegraph.ts | 252 +++++- src/db/queries.ts | 28 +- src/extraction/build-script-extractors.ts | 245 ++++++ src/extraction/extraction-version.ts | 2 +- src/extraction/glslfx-extractor.ts | 257 +++++++ src/extraction/grammars.ts | 42 +- src/extraction/languages/glsl.ts | 264 +++++++ src/extraction/languages/hlsl.ts | 198 +++++ src/extraction/languages/index.ts | 4 + src/extraction/languages/shader-common.ts | 445 +++++++++++ src/extraction/tree-sitter-types.ts | 4 + src/extraction/tree-sitter.ts | 293 ++++++- src/extraction/wasm/tree-sitter-glsl.wasm | Bin 0 -> 853461 bytes src/extraction/wasm/tree-sitter-hlsl.wasm | Bin 0 -> 4281939 bytes src/graph/traversal.ts | 190 ++++- src/index.ts | 16 +- src/mcp/tools.ts | 576 +++++++++++++- src/powershell-dependencies.ts | 29 + src/project-config.ts | 60 +- src/resolution/callback-synthesizer.ts | 3 + src/resolution/import-resolver.ts | 45 ++ src/resolution/index.ts | 89 ++- src/resolution/name-matcher.ts | 52 ++ src/resolution/shader-resolver.ts | 496 ++++++++++++ src/resolution/shader-synthesizer.ts | 276 +++++++ src/types.ts | 4 + 35 files changed, 5099 insertions(+), 86 deletions(-) create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 __tests__/affected-vendored.test.ts create mode 100644 __tests__/build-script-extraction.test.ts create mode 100644 __tests__/cpp-local-lambda.test.ts create mode 100644 __tests__/shader-support.test.ts create mode 100644 src/extraction/build-script-extractors.ts create mode 100644 src/extraction/glslfx-extractor.ts create mode 100644 src/extraction/languages/glsl.ts create mode 100644 src/extraction/languages/hlsl.ts create mode 100644 src/extraction/languages/shader-common.ts create mode 100644 src/extraction/wasm/tree-sitter-glsl.wasm create mode 100644 src/extraction/wasm/tree-sitter-hlsl.wasm create mode 100644 src/powershell-dependencies.ts create mode 100644 src/resolution/shader-resolver.ts create mode 100644 src/resolution/shader-synthesizer.ts diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 000000000..115eeeeb7 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,54 @@ +# Third-party notices + +## tree-sitter-glsl + +Source: `Caellian/tree-sitter-glsl@09b8cbc3a36bce116d641f1bc176268c0899e6e0` + +The MIT License (MIT) + +Copyright (c) 2026 Tin Švagelj + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## tree-sitter-hlsl + +Source: `tree-sitter-grammars/tree-sitter-hlsl@bab9111922d53d43668fabb61869bec51bbcb915` + +The MIT License (MIT) + +Copyright (c) 2014 Max Brunsfield +Copyright (c) 2022 Stephan Seitz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/__tests__/affected-vendored.test.ts b/__tests__/affected-vendored.test.ts new file mode 100644 index 000000000..974ff47a2 --- /dev/null +++ b/__tests__/affected-vendored.test.ts @@ -0,0 +1,78 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { execFileSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; + +const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js'); +let dir: string; + +function write(relative: string, content: string): void { + const file = path.join(dir, relative); + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, content); +} + +function affected(file: string): string[] { + const stdout = execFileSync(process.execPath, [BIN, 'affected', file, '--path', dir, '--json'], { + encoding: 'utf8', + env: { ...process.env, CODEGRAPH_NO_DAEMON: '1' }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + return JSON.parse(stdout).affectedTests as string[]; +} + +beforeAll(async () => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-affected-vendor-')); + write('src/core.ts', 'export function core() { return 1; }\n'); + write('tests/core.test.ts', 'import { core } from "../src/core"; core();\n'); + write('External/lib/source.ts', 'export function external() { return 1; }\n'); + write('External/lib/tests/external.test.ts', 'import { core } from "../../../src/core"; import { external } from "../source"; core(); external();\n'); + write('Shaders/shared.hlsli', 'float sharedShader(){ return 1.0; }\n'); + write('Samples/Minimal/Shaders/Render.hlsl', '#include "../../../Shaders/shared.hlsli"\nfloat main(){ return sharedShader(); }\n'); + write('Samples/Full/Shaders/Bridge/RAB_Surface.hlsli', 'float fullBridge(){ return 1.0; }\n'); + write('Samples/Full/Shaders/Full.hlsl', '#include "Bridge/RAB_Surface.hlsli"\n#include "../../../Shaders/shared.hlsli"\nfloat main(){ return fullBridge() + sharedShader(); }\n'); + write('Support/Tests/Runtime/Shaders/Bridge/RAB_Surface.hlsli', 'float testBridge(){ return 1.0; }\n'); + write('Support/Tests/Runtime/Shaders/RAB_PathTracer.hlsli', 'float helperHeader(){ return 1.0; }\n'); + write('Support/Tests/Runtime/Shaders/CompileTest.hlsl', '#include "../../../../Shaders/shared.hlsli"\n#include "Bridge/RAB_Surface.hlsli"\n#include "RAB_PathTracer.hlsli"\nfloat main(){ return sharedShader() + testBridge() + helperHeader(); }\n'); + const cg = CodeGraph.initSync(dir); + await cg.indexAll(); + cg.close(); +}, 60_000); + +afterAll(() => { + if (dir) fs.rmSync(dir, { recursive: true, force: true }); +}); + +describe('affected vendored-test filtering', () => { + it('omits dependency-suite tests for a project-owned change', () => { + expect(affected('src/core.ts')).toEqual(['tests/core.test.ts']); + }); + + it('keeps vendored tests when the changed file is itself vendored', () => { + expect(affected('External/lib/source.ts')).toContain('External/lib/tests/external.test.ts'); + }); + + it('recognizes shader compile tests under case-insensitive test directories', () => { + expect(affected('Shaders/shared.hlsli')).toContain('Support/Tests/Runtime/Shaders/CompileTest.hlsl'); + }); + + it('does not cross from one shader compilation root into another', () => { + expect(affected('Samples/Minimal/Shaders/Render.hlsl')).toEqual([]); + expect(affected('Samples/Full/Shaders/Bridge/RAB_Surface.hlsli')).toEqual([]); + }); + + it('maps a test bridge to its compile-test entry instead of the helper header itself', () => { + expect(affected('Support/Tests/Runtime/Shaders/Bridge/RAB_Surface.hlsli')).toEqual([ + 'Support/Tests/Runtime/Shaders/CompileTest.hlsl', + ]); + expect(affected('Support/Tests/Runtime/Shaders/RAB_PathTracer.hlsli')).toEqual([ + 'Support/Tests/Runtime/Shaders/CompileTest.hlsl', + ]); + }); + + it('does not classify shader helper headers as tests', () => { + expect(affected('Shaders/shared.hlsli')).not.toContain('Support/Tests/Runtime/Shaders/RAB_PathTracer.hlsli'); + }); +}); diff --git a/__tests__/build-script-extraction.test.ts b/__tests__/build-script-extraction.test.ts new file mode 100644 index 000000000..e2332f589 --- /dev/null +++ b/__tests__/build-script-extraction.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; +import { detectLanguage } from '../src/extraction/grammars'; +import { extractFromSource } from '../src/extraction/tree-sitter'; + +describe('build-script extraction', () => { + it('indexes CMake shader variants and function/variable declarations', () => { + expect(detectLanguage('CMakeLists.txt')).toBe('cmake'); + const result = extractFromSource('CMakeLists.txt', ` +function(add_pathtrace_variant name) + set(SHADER "shaders/pathtrace.rgen") +endfunction() +add_pathtrace_variant(default) +`); + expect(result.nodes.some((node) => node.kind === 'function' && node.name === 'add_pathtrace_variant')).toBe(true); + expect(result.nodes.some((node) => node.kind === 'variable' && node.name === 'SHADER')).toBe(true); + expect(result.unresolvedReferences.some((ref) => ref.referenceName === 'shaders/pathtrace.rgen')).toBe(true); + }); + + it('indexes PowerShell shader build helpers and shader paths', () => { + expect(detectLanguage('tools/CompileShaders.ps1')).toBe('powershell'); + const result = extractFromSource('tools/CompileShaders.ps1', ` +function Build-Shader { + & "glslc.exe" "shaders/pathtrace.rgen" -o "pathtrace.rgen.spv" +} +$ShaderRoot = "shaders" +`); + expect(result.nodes.some((node) => node.kind === 'function' && node.name === 'Build-Shader')).toBe(true); + expect(result.nodes.some((node) => node.kind === 'variable' && node.name === 'ShaderRoot')).toBe(true); + expect(result.unresolvedReferences.some((ref) => ref.referenceName === 'shaders/pathtrace.rgen')).toBe(true); + }); + + it('builds PowerShell call references, preserves declaration lines, and deduplicates assignments', () => { + const result = extractFromSource('run_game_stability_diagnostics.ps1', ` +\$ErrorActionPreference = 'Stop' +\$ErrorActionPreference = 'Stop' +function Resolve-SceneList { + return @() +} +function Invoke-Validation { + Resolve-SceneList +} +\$resolved = Resolve-SceneList +`); + const resolve = result.nodes.find((node) => node.kind === 'function' && node.name === 'Resolve-SceneList'); + expect(resolve?.startLine).toBe(4); + expect(result.nodes.filter((node) => node.kind === 'variable' && node.name === 'ErrorActionPreference')).toHaveLength(1); + expect(result.unresolvedReferences.some((ref) => + ref.fromNodeId === result.nodes.find((node) => node.name === 'Invoke-Validation')?.id && + ref.referenceKind === 'calls' && ref.referenceName === 'Resolve-SceneList' && ref.line === 8 + )).toBe(true); + expect(result.unresolvedReferences.some((ref) => + ref.fromNodeId === result.nodes.find((node) => node.kind === 'file')?.id && + ref.referenceName === 'Resolve-SceneList' && ref.line === 10 + )).toBe(true); + }); + + it('does not report exported functions as callers or executable launches as imports', () => { + const result = extractFromSource('scripts/EditorToolingCommon.psm1', ` +function Read-EditorJson { return @{} } +function Get-EntityCount { Read-EditorJson } +Export-ModuleMember -Function Read-EditorJson, Get-EntityCount +& "$BuildDir\\rtvulkan.exe" +`); + const file = result.nodes.find((node) => node.kind === 'file')!; + expect(result.unresolvedReferences.some((ref) => + ref.fromNodeId === file.id && ref.referenceKind === 'calls' && + (ref.referenceName === 'Read-EditorJson' || ref.referenceName === 'Get-EntityCount') + )).toBe(false); + expect(result.unresolvedReferences.some((ref) => ref.referenceKind === 'imports' && ref.referenceName.includes('rtvulkan.exe'))).toBe(false); + expect(result.unresolvedReferences.some((ref) => + ref.fromNodeId === result.nodes.find((node) => node.name === 'Get-EntityCount')?.id && + ref.referenceKind === 'calls' && ref.referenceName === 'Read-EditorJson' + )).toBe(true); + }); + + it('resolves Import-Module dependencies and calls to exported module functions', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-powershell-module-')); + fs.mkdirSync(path.join(dir, 'scripts')); + fs.writeFileSync(path.join(dir, 'scripts', 'EditorToolingCommon.psm1'), `function Read-EditorJson { param([string]\$Path) return @{} } +Export-ModuleMember -Function Read-EditorJson +`); + fs.writeFileSync(path.join(dir, 'scripts', 'audit.ps1'), `Import-Module (Join-Path \$PSScriptRoot 'EditorToolingCommon.psm1') -Force +function Get-Block { return '\\{' } +\$manifest = Read-EditorJson -Path 'manifest.json' +`); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const moduleFile = cg.getNodesInFile('scripts/EditorToolingCommon.psm1').find((node) => node.kind === 'file')!; + const readJson = cg.getNodesInFile('scripts/EditorToolingCommon.psm1').find((node) => node.name === 'Read-EditorJson')!; + expect(cg.getIncomingEdges(moduleFile.id).some((edge) => edge.kind === 'imports')).toBe(true); + expect(cg.getCallers(readJson.id).some((caller) => caller.node.kind === 'file' && caller.node.filePath === 'scripts/audit.ps1' && caller.edge.line === 3)).toBe(true); + } finally { + cg.close(); + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + it('resolves dot-sourced script dependencies and their shared functions', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-powershell-dot-source-')); + fs.mkdirSync(path.join(dir, 'scripts')); + fs.writeFileSync(path.join(dir, 'scripts', 'restir_reference_common.ps1'), `function Write-RestirJsonAndCsv { param([string]\$Path) } +function Invoke-PrivateRestirHelper { } +`); + fs.writeFileSync(path.join(dir, 'scripts', 'restir_reference_quality_matrix.ps1'), `. (Join-Path \$PSScriptRoot "restir_reference_common.ps1") +Write-RestirJsonAndCsv -Path 'quality.json' +Invoke-PrivateRestirHelper +`); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const commonFile = cg.getNodesInFile('scripts/restir_reference_common.ps1').find((node) => node.kind === 'file')!; + const writer = cg.getNodesInFile('scripts/restir_reference_common.ps1').find((node) => node.name === 'Write-RestirJsonAndCsv')!; + const privateHelper = cg.getNodesInFile('scripts/restir_reference_common.ps1').find((node) => node.name === 'Invoke-PrivateRestirHelper')!; + expect(cg.getIncomingEdges(commonFile.id).some((edge) => edge.kind === 'imports')).toBe(true); + expect(cg.getCallers(writer.id).some((caller) => caller.node.filePath === 'scripts/restir_reference_quality_matrix.ps1' && caller.edge.line === 2)).toBe(true); + expect(cg.getCallers(privateHelper.id).some((caller) => caller.node.filePath === 'scripts/restir_reference_quality_matrix.ps1' && caller.edge.line === 3)).toBe(true); + } finally { + cg.close(); + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/__tests__/cpp-local-lambda.test.ts b/__tests__/cpp-local-lambda.test.ts new file mode 100644 index 000000000..72b578ebe --- /dev/null +++ b/__tests__/cpp-local-lambda.test.ts @@ -0,0 +1,83 @@ +import { afterEach, beforeAll, describe, expect, it } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; +import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars'; +import { extractFromSource } from '../src/extraction/tree-sitter'; + +beforeAll(async () => { + await initGrammars(); + await loadGrammarsForLanguages(['cpp']); +}); + +const tempDirs: string[] = []; +afterEach(() => { + for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true }); +}); + +describe('C++ named local lambdas', () => { + it('indexes lambda-valued locals as scoped functions without indexing ordinary locals', () => { + const result = extractFromSource('renderer.cpp', `int helper() { return 1; } +void recordGraph() { + int ordinaryLocal = 0; + auto imageResource = [](int binding) { return helper() + binding; }; + auto bufferResource = [&](int binding) { return imageResource(binding); }; + bufferResource(3); +} +`); + const image = result.nodes.find((node) => node.kind === 'function' && node.name === 'imageResource'); + const buffer = result.nodes.find((node) => node.kind === 'function' && node.name === 'bufferResource'); + expect(image?.qualifiedName).toBe('recordGraph::imageResource'); + expect(buffer?.qualifiedName).toBe('recordGraph::bufferResource'); + expect(image?.startLine).toBe(4); + expect(result.nodes.some((node) => node.name === 'ordinaryLocal')).toBe(false); + expect(result.unresolvedReferences.some((ref) => ref.fromNodeId === image?.id && ref.referenceName === 'helper')).toBe(true); + expect(result.unresolvedReferences.some((ref) => ref.fromNodeId === buffer?.id && ref.referenceName === 'imageResource')).toBe(true); + }); + + it('resolves calls to the enclosing function lambda instead of an unrelated method', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-local-lambda-')); + tempDirs.push(dir); + fs.writeFileSync(path.join(dir, 'cache.h'), `class NativeGpuAssetCache { +public: + int bufferResource(); + bool empty(); +}; +`); + fs.writeFileSync(path.join(dir, 'cache.cpp'), `#include "cache.h" +int NativeGpuAssetCache::bufferResource() { return 0; } +bool NativeGpuAssetCache::empty() { return false; } +`); + fs.writeFileSync(path.join(dir, 'renderer.cpp'), `#include "cache.h" +#include +namespace rtv { +class PathTracerRenderer { public: void recordGraph(); private: std::string status_; }; +int helper() { return 1; } +void PathTracerRenderer::recordGraph() { + auto bufferResource = [](int binding) { return helper() + binding; }; + bufferResource(3); + status_.empty(); +} +} +`); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const rendererNodes = cg.getNodesInFile('renderer.cpp'); + const lambda = rendererNodes.find((node) => node.kind === 'function' && node.name === 'bufferResource')!; + const helper = rendererNodes.find((node) => node.kind === 'function' && node.name === 'helper')!; + const cacheMethod = cg.getNodesInFile('cache.cpp').find((node) => node.kind === 'method' && node.name === 'bufferResource')!; + const unrelatedEmpty = cg.getNodesInFile('cache.cpp').find((node) => node.kind === 'method' && node.name === 'empty')!; + + expect(lambda.qualifiedName).toBe('rtv::PathTracerRenderer::recordGraph::bufferResource'); + expect(lambda.decorators).toContain('cpp:lambda'); + expect(cg.getCallers(lambda.id).some((caller) => caller.node.name === 'recordGraph' && caller.edge.line === 8)).toBe(true); + expect(cg.getCallees(lambda.id).some((callee) => callee.node.id === helper.id)).toBe(true); + expect(cg.getCallers(cacheMethod.id).some((caller) => caller.node.filePath === 'renderer.cpp')).toBe(false); + expect(cg.getCallers(unrelatedEmpty.id).some((caller) => caller.node.filePath === 'renderer.cpp')).toBe(false); + } finally { + cg.close(); + } + }); +}); diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index d7947073a..b399108cb 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -69,6 +69,22 @@ describe('Language Detection', () => { expect(detectLanguage('class.hpp')).toBe('cpp'); }); + it('should detect GLSL, Vulkan shader stages, GLSLFX, and HLSL files', () => { + for (const file of ['shader.glsl', 'shader.vert', 'shader.frag', 'shader.comp', 'shader.geom', + 'shader.tesc', 'shader.tese', 'shader.rgen', 'shader.rmiss', 'shader.rchit', + 'shader.rahit', 'shader.rint', 'shader.rcall', 'shader.mesh', 'shader.task', 'shader.glslfx']) { + expect(detectLanguage(file)).toBe('glsl'); + expect(isSourceFile(file)).toBe(true); + } + for (const file of ['shader.hlsl', 'shader.hlsli', 'shader.fx', 'shader.fxh']) { + expect(detectLanguage(file)).toBe('hlsl'); + expect(isSourceFile(file)).toBe(true); + } + expect(isLanguageSupported('glsl')).toBe(true); + expect(isLanguageSupported('hlsl')).toBe(true); + expect(getSupportedLanguages()).toEqual(expect.arrayContaining(['glsl', 'hlsl'])); + }); + it('should detect C# files', () => { expect(detectLanguage('Program.cs')).toBe('csharp'); }); diff --git a/__tests__/same-name-disambiguation.test.ts b/__tests__/same-name-disambiguation.test.ts index 7c260c7c4..4b77c9ba6 100644 --- a/__tests__/same-name-disambiguation.test.ts +++ b/__tests__/same-name-disambiguation.test.ts @@ -15,6 +15,7 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; +import { execFileSync } from 'child_process'; import { CodeGraph } from '../src'; import { ToolHandler } from '../src/mcp/tools'; import { initGrammars, loadAllGrammars } from '../src/extraction/grammars'; @@ -22,6 +23,7 @@ import { initGrammars, loadAllGrammars } from '../src/extraction/grammars'; let tmpDir: string; let cg: CodeGraph; let handler: ToolHandler; +const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js'); const text = async (tool: string, args: Record): Promise => { const res = await handler.execute(tool, args); @@ -68,6 +70,24 @@ beforeAll(async () => { ); } + mk( + 'src/lambdas.cpp', + [ + 'int firstLeaf() { return 1; }', + 'int secondLeaf() { return 2; }', + 'void record() {', + ' {', + ' auto bufferResource = []() { return firstLeaf(); };', + ' bufferResource();', + ' }', + ' {', + ' auto bufferResource = []() { return secondLeaf(); };', + ' bufferResource();', + ' }', + '}', + ].join('\n') + ); + cg = CodeGraph.initSync(tmpDir); await cg.indexAll(); handler = new ToolHandler(cg); @@ -138,4 +158,66 @@ describe('same-named symbols across apps (#764)', () => { const out = await text('codegraph_callees', { symbol: 'list' }); expect(out).toContain('2 distinct definitions'); }); + + it('line narrows repeated same-name lambdas within one file and scope', async () => { + const lambdas = cg.getNodesByName('bufferResource'); + expect(lambdas.map((node) => node.startLine).sort((a, b) => a - b)).toEqual([5, 9]); + expect(new Set(lambdas.map((node) => node.qualifiedName)).size).toBe(1); + expect(lambdas.every((node) => node.decorators?.includes('cpp:lambda'))).toBe(true); + + const fileOnly = await text('codegraph_impact', { + symbol: 'bufferResource', + file: 'src/lambdas.cpp', + }); + expect(fileOnly).toContain('2 distinct definitions'); + expect(fileOnly).toContain('src/lambdas.cpp:5'); + expect(fileOnly).toContain('src/lambdas.cpp:9'); + + const callees = await text('codegraph_callees', { + symbol: 'bufferResource', + file: 'src/lambdas.cpp', + line: 5, + }); + expect(callees).toContain('firstLeaf'); + expect(callees).not.toContain('secondLeaf'); + + const impact = await text('codegraph_impact', { + symbol: 'bufferResource', + file: 'src/lambdas.cpp', + line: 5, + }); + expect(impact).toContain('bufferResource'); + expect(impact).not.toContain(':9'); + }); + + it('CLI impact accepts --line to select one same-file definition', () => { + const output = execFileSync(process.execPath, [ + BIN, + 'impact', + 'bufferResource', + '--file', + 'src/lambdas.cpp', + '--line', + '5', + '--path', + tmpDir, + '--json', + ], { + encoding: 'utf8', + env: { ...process.env, CODEGRAPH_WASM_RELAUNCHED: '1' }, + }); + const result = JSON.parse(output) as { line: number; affected: Array<{ name: string; startLine?: number }> }; + expect(result.line).toBe(5); + expect(result.affected).toContainEqual(expect.objectContaining({ name: 'bufferResource', startLine: 5 })); + expect(result.affected).not.toContainEqual(expect.objectContaining({ name: 'bufferResource', startLine: 9 })); + }); + + it('line misses are success-shaped and do not fall back to another definition', async () => { + const out = await text('codegraph_callers', { + symbol: 'bufferResource', + file: 'src/lambdas.cpp', + line: 99, + }); + expect(out).toContain('not found at file "src/lambdas.cpp" at line 99'); + }); }); diff --git a/__tests__/shader-support.test.ts b/__tests__/shader-support.test.ts new file mode 100644 index 000000000..800a74812 --- /dev/null +++ b/__tests__/shader-support.test.ts @@ -0,0 +1,848 @@ +import { afterEach, beforeAll, describe, expect, it } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; +import { extractFromSource } from '../src/extraction/tree-sitter'; +import { initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars'; +import { clearProjectConfigCache, loadShaderIncludeAliases, loadShaderIncludePaths } from '../src/project-config'; +import { ShaderResolver } from '../src/resolution/shader-resolver'; +import { shaderIntegrationEdges } from '../src/resolution/shader-synthesizer'; +import { ToolHandler } from '../src/mcp/tools'; +import type { Language, Node } from '../src/types'; +import type { ResolutionContext, UnresolvedRef } from '../src/resolution/types'; + +beforeAll(async () => { + await initGrammars(); + await loadGrammarsForLanguages(['glsl', 'hlsl']); +}); + +const tempDirs: string[] = []; +afterEach(() => { + clearProjectConfigCache(); + for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true }); +}); + +function node(partial: Partial & Pick): Node { + return { + qualifiedName: partial.name, + startLine: 1, + endLine: 1, + startColumn: 0, + endColumn: 0, + updatedAt: 1, + ...partial, + } as Node; +} + +describe('shader extraction', () => { + it('extracts Vulkan GLSL resources, macros, structs, specialization constants, and calls', () => { + const source = `#version 460 +#include "common.glsl" +#define ENABLE_GI 1 +#define WRAP(x) shade(x) +struct Payload { vec3 color; float depth; }; +layout(set = 1, binding = 4) uniform accelerationStructureEXT scene; +layout(push_constant) uniform Push { uint frame; } pc; +layout(constant_id = 3) const int MODE = 0; +layout(location = 0) rayPayloadEXT Payload payload; +vec3 shade(vec3 value) { return value; } +void main() { payload.color = WRAP(shade(vec3(MODE))); } +`; + const result = extractFromSource('shaders/path.rgen', source); + expect(result.errors).toEqual([]); + expect(result.nodes.find((n) => n.kind === 'import' && n.name === 'common.glsl')).toBeDefined(); + expect(result.nodes.find((n) => n.kind === 'constant' && n.name === 'ENABLE_GI')).toBeDefined(); + expect(result.nodes.find((n) => n.kind === 'function' && n.name === 'WRAP')).toBeDefined(); + expect(result.nodes.find((n) => n.kind === 'struct' && n.name === 'Payload')).toBeDefined(); + expect(result.nodes.filter((n) => n.kind === 'field').map((n) => n.name)).toEqual(expect.arrayContaining(['color', 'depth', 'frame'])); + expect(result.nodes.find((n) => n.name === 'scene')?.decorators).toEqual(expect.arrayContaining(['set:1', 'binding:4', 'resource:acceleration-structure'])); + expect(result.nodes.find((n) => n.name === 'Push')?.decorators).toContain('push_constant'); + expect(result.nodes.find((n) => n.name === 'MODE')?.decorators).toContain('constant_id:3'); + expect(result.nodes.find((n) => n.name === 'main')?.decorators).toEqual(expect.arrayContaining(['entrypoint', 'shader:ray-generation'])); + expect(result.unresolvedReferences.some((ref) => ref.referenceKind === 'calls' && ref.referenceName === 'shade')).toBe(true); + const macro = result.nodes.find((n) => n.kind === 'function' && n.name === 'WRAP'); + expect(result.unresolvedReferences.some((ref) => ref.fromNodeId === macro?.id && ref.referenceName === 'shade')).toBe(true); + expect(result.unresolvedReferences.some((ref) => ref.referenceName === 'vec3')).toBe(false); + }); + + it('indexes every conditional branch with its normalized condition', () => { + const result = extractFromSource('shaders/branches.comp', `#if USE_PRIMARY +layout(binding = 0) uniform sampler2D primaryTexture; +#elif USE_FALLBACK +layout(binding = 0) uniform sampler2D fallbackTexture; +#else +layout(binding = 0) uniform sampler2D debugTexture; +#endif +`); + expect(result.nodes.find((n) => n.name === 'primaryTexture')?.decorators).toContain('pp:USE_PRIMARY'); + expect(result.nodes.find((n) => n.name === 'fallbackTexture')?.decorators).toContain('pp:!(USE_PRIMARY)&&(USE_FALLBACK)'); + expect(result.nodes.find((n) => n.name === 'debugTexture')?.decorators).toContain('pp:!(USE_PRIMARY||USE_FALLBACK)'); + }); + + it('extracts HLSL cbuffers, registers, semantics, entry points, and NRD macro resources', () => { + const source = `#define BINDING(x) [[vk::binding(x, 0)]] +#define DECL_RESOURCE_IMPL(name, slot) Texture2D name : register(t##slot); +#define DECL_RESOURCE(name, slot) DECL_RESOURCE_IMPL(name, slot) +BINDING(0) cbuffer Globals : register(b0) { float exposure; uint frame; } +BINDING(1) Texture2D inputTex : register(t1, space2); +RWTexture2D outputTex : register(u2); +DECL_RESOURCE(gHistory, 7) +NRD_INPUT(Texture2D, float4, gIn_Normal, t, 3) +NRD_OUTPUT(RWTexture2D, float4, gOut_Color, u, 4) +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) { outputTex[id.xy] = inputTex.Load(int3(id.xy, 0)); } +`; + const result = extractFromSource('shaders/filter.cs.hlsl', source); + expect(result.errors).toEqual([]); + expect(result.nodes.find((n) => n.kind === 'struct' && n.name === 'Globals')?.decorators).toEqual(expect.arrayContaining(['register:b0', 'resource:constant-buffer'])); + expect(result.nodes.filter((n) => n.kind === 'field').map((n) => n.name)).toEqual(expect.arrayContaining(['exposure', 'frame'])); + expect(result.nodes.find((n) => n.name === 'inputTex')?.decorators).toEqual(expect.arrayContaining(['register:t1', 'space:2', 'resource:texture'])); + expect(result.nodes.find((n) => n.name === 'gHistory')?.decorators).toEqual(expect.arrayContaining(['register:t7', 'resource:texture'])); + expect(result.nodes.find((n) => n.name === 'gIn_Normal')?.decorators).toContain('register:t3'); + expect(result.nodes.find((n) => n.name === 'gOut_Color')?.decorators).toContain('register:u4'); + expect(result.nodes.find((n) => n.name === 'main')?.decorators).toEqual(expect.arrayContaining(['entrypoint', 'shader:compute', 'numthreads:8,8,1'])); + expect(result.nodes.find((n) => n.name === 'main')?.decorators).toContain('semantic:SV_DispatchThreadID'); + expect(result.unresolvedReferences.some((ref) => /(?:Load|int3)/.test(ref.referenceName))).toBe(false); + }); + + it('keeps calls to user-defined functions that shadow intrinsic names', () => { + const result = extractFromSource('shaders/shadow.frag', 'float clamp(float x){return x;}\nvoid main(){clamp(1.0);}\n'); + expect(result.unresolvedReferences.some((ref) => ref.referenceKind === 'calls' && ref.referenceName === 'clamp')).toBe(true); + }); + + it('records shader call arity so same-file overloads can be disambiguated', () => { + const result = extractFromSource('shaders/overloads.hlsl', ` +float pick(float a) { return a; } +float pick(float a, float b) { return a + b; } +float run() { return pick(1.0, 2.0); } +`); + expect(result.nodes.find((candidate) => candidate.name === 'pick')?.signature).toMatch(/^float pick\(float a\)$/); + const ref = result.unresolvedReferences.find((candidate) => candidate.referenceName === 'pick'); + expect(ref?.candidates).toContain('arity:2'); + }); + + it('keeps GLSL function names and parameter parentheses in signatures', () => { + const result = extractFromSource('shaders/overloads.glsl', ` +bool resolve(Item item, out uint index) { index = 0u; return true; } +`); + expect(result.nodes.find((candidate) => candidate.name === 'resolve')?.signature) + .toBe('bool resolve(Item item, out uint index)'); + }); + + it('records conservative HLSL argument types for overload resolution', () => { + const result = extractFromSource('shaders/checkerboard.hlsl', ` +void activate(inout uint2 pixel, bool previous, uint field) {} +void activate(inout int2 pixel, bool previous, uint field) {} +void run(uint2 pixel) { activate(pixel, false, 0); } +`); + const run = result.nodes.find((candidate) => candidate.name === 'run'); + const ref = result.unresolvedReferences.find((candidate) => candidate.fromNodeId === run?.id && candidate.referenceName === 'activate'); + expect(ref?.candidates).toEqual(expect.arrayContaining(['arity:3', 'argtype:0:uint2', 'argtype:1:bool'])); + }); + + it('recovers functions whose bodies are detached by GLSL preprocessor error recovery', () => { + const result = extractFromSource('shaders/pathtrace_integrator.glsl', `#ifndef RTV_PATHTRACE_INTEGRATOR_GLSL +#define RTV_PATHTRACE_INTEGRATOR_GLSL +#ifndef RTV_MATERIAL_RAY_CONE_LOD +#define RTV_MATERIAL_RAY_CONE_LOD 0 +// Main path integration routine. +vec3 trace_path(Ray ray, inout uint rng, uint pixelIndex, ivec2 coords, ivec2 dims, out bool did_hit, out float first_depth, out vec3 first_normal, out vec3 first_position, out PathComponents components) { + vec3 radiance = vec3(0.0); + renderer_debug_view(); +`); + const tracePath = result.nodes.find((candidate) => candidate.kind === 'function' && candidate.name === 'trace_path'); + expect(tracePath).toBeDefined(); + expect(tracePath!.endLine).toBeGreaterThan(tracePath!.startLine); + expect(tracePath!.decorators ?? []).not.toEqual(expect.arrayContaining(['interface:in', 'interface:out'])); + expect(result.unresolvedReferences.some((ref) => + ref.fromNodeId === tracePath!.id && ref.referenceKind === 'calls' && ref.referenceName === 'renderer_debug_view' + )).toBe(true); + expect(result.unresolvedReferences.filter((ref) => ref.referenceName === 'renderer_debug_view')).toHaveLength(1); + }); + + it('recovers complete signatures for detached declarations', () => { + const result = extractFromSource('shaders/pathtrace_ray_queries.glsl', ` +vec3 direct_shadow_transmittance_stats( + vec3 origin, vec3 direction, float tMax, float rayTime, + Material receiverMaterial, out uint transmissiveHits, + out uint visiblePath, out uint blockedPath) { + return caustic_shadow_transmittance_stats(origin, direction, tMax, rayTime, transmissiveHits, visiblePath, blockedPath); +} +`); + const fn = result.nodes.find((candidate) => candidate.name === 'direct_shadow_transmittance_stats'); + expect(fn?.signature).toContain('vec3 origin'); + expect(fn?.signature).toContain('Material receiverMaterial'); + expect(fn?.signature).not.toMatch(/^}/); + }); + + it('extracts OpenUSD GLSLFX sections, techniques, embedded symbols, and imports with original lines', () => { + const source = `-- glslfx version 0.1 +#import "$TOOLS/hdSt/shared.glslfx" +-- configuration +{"techniques":{"default":{"vertexShader":{"source":"Surface.Vertex"},"fragmentShader":{"source":"Surface.Fragment"}}}} +-- glsl Surface.Vertex +void vertexMain() {} +-- glsl Surface.Fragment +vec4 shade() { return vec4(1.0); } +`; + const result = extractFromSource('pxr/shaders/material.glslfx', source); + expect(result.errors).toEqual([]); + expect(result.nodes.find((n) => n.kind === 'file')?.decorators).toContain('glslfx:0.1'); + expect(result.nodes.filter((n) => n.kind === 'module').map((n) => n.name)).toEqual(['Surface.Vertex', 'Surface.Fragment']); + expect(result.nodes.find((n) => n.kind === 'component' && n.name === 'default')).toBeDefined(); + expect(result.nodes.find((n) => n.kind === 'function' && n.name === 'vertexMain')?.startLine).toBe(6); + expect(result.nodes.find((n) => n.kind === 'import')?.name).toBe('$TOOLS/hdSt/shared.glslfx'); + expect(result.edges.filter((edge) => edge.kind === 'references')).toHaveLength(2); + }); + + it('keeps useful symbols from partial shaders and leaves ambiguous GLSLFX sections unresolved', () => { + const partial = extractFromSource('shaders/partial.rchit', 'layout(set=0,binding=2) uniform accelerationStructureEXT scene;\nvoid main( {\n'); + expect(partial.nodes.find((node) => node.name === 'scene')?.decorators).toEqual(expect.arrayContaining(['set:0', 'binding:2'])); + + const glslfx = extractFromSource('pxr/shaders/ambiguous.glslfx', `-- glslfx version 0.2 +-- configuration +{"techniques":{"default":{"source":"Shared.Surface"}}} +-- glsl Shared.Surface +void first() {} +-- glsl Shared.Surface +void second() {} +`); + expect(glslfx.errors.some((error) => error.code === 'glslfx_version' && error.severity === 'warning')).toBe(true); + expect(glslfx.edges.filter((edge) => edge.kind === 'references')).toEqual([]); + }); +}); + +describe('shader exploration', () => { + it('expands an explicit shader through its transitive include closure when requested', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-explore-')); + tempDirs.push(dir); + const shaders = path.join(dir, 'shaders'); + fs.mkdirSync(shaders); + fs.writeFileSync(path.join(shaders, 'root.comp'), `#version 460 +#include "common.glsl" +void main() { shade(); } +`); + fs.writeFileSync(path.join(shaders, 'common.glsl'), `#include "nested.glsl" +void shade() { helper(); } +`); + fs.writeFileSync(path.join(shaders, 'nested.glsl'), `void helper() {} +`); + const cg = CodeGraph.initSync(dir, { config: { include: ['shaders/**/*'], exclude: [] } }); + try { + await cg.indexAll(); + const result = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'root.comp main and its included GLSL files', + maxFiles: 6, + }); + const text = result.content[0]!.text; + expect(text).toContain('root.comp'); + expect(text).toContain('common.glsl'); + expect(text).toContain('nested.glsl'); + expect(text).toContain('void shade()'); + expect(text).toContain('void helper()'); + } finally { + cg.destroy(); + } + }); + + it('treats a full shader path as exact instead of matching every same basename', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-path-anchor-')); + tempDirs.push(dir); + fs.mkdirSync(path.join(dir, 'a'), { recursive: true }); + fs.mkdirSync(path.join(dir, 'b'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'a', 'bridge.hlsli'), 'float SelectedBridge(){ return 1.0; }\n'); + fs.writeFileSync(path.join(dir, 'b', 'bridge.hlsli'), 'float UnrelatedBridge(){ return 2.0; }\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const result = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'a/bridge.hlsli SelectedBridge callers and impact', + maxFiles: 4, + }); + const text = result.content[0]!.text; + expect(text).toContain('a/bridge.hlsli'); + expect(text).toContain('SelectedBridge'); + expect(text).not.toContain('b/bridge.hlsli'); + expect(text).not.toContain('UnrelatedBridge'); + } finally { + cg.destroy(); + } + }); + + it('keeps explored bridge symbols inside the explicitly named shader translation unit', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-context-explore-')); + tempDirs.push(dir); + for (const target of ['A', 'B']) { + fs.mkdirSync(path.join(dir, 'Targets', target, 'Bridge'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'Targets', target, 'main.hlsl'), '#include "../../Shared/algorithm.hlsli"\n#include "Bridge/bridge.hlsli"\nvoid main(){ sharedAlgorithm(); }\n'); + fs.writeFileSync(path.join(dir, 'Targets', target, 'Bridge', 'bridge.hlsli'), `float BridgeSurface(int2 p){ return ${target === 'A' ? '1.0' : '2.0'}; }\n`); + } + fs.mkdirSync(path.join(dir, 'Shared'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'Shared', 'algorithm.hlsli'), 'void sharedLeaf(){}\nvoid sharedAlgorithm(){ sharedLeaf(); BridgeSurface(int2(0, 0)); }\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const result = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'Trace BridgeSurface from Targets/A/main.hlsl through Targets/A/Bridge/bridge.hlsli', + maxFiles: 8, + }); + const text = result.content[0]!.text; + expect(text).toContain('Targets/A/Bridge/bridge.hlsli'); + expect(text).toContain('Targets/A/main.hlsl'); + expect(text).not.toContain('Targets/B/Bridge/bridge.hlsli'); + expect(text).not.toContain('Targets/B/main.hlsl'); + } finally { + cg.destroy(); + } + }); + + it('shows direct shader call paths pinned by explicit file lines', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-line-flow-')); + tempDirs.push(dir); + const shaders = path.join(dir, 'shaders'); + fs.mkdirSync(shaders); + fs.writeFileSync(path.join(shaders, 'common.glsl'), 'bool resolve_light(uint value){return true;}\nbool resolve_light(uint value,uint version){return true;}\n'); + fs.writeFileSync(path.join(shaders, 'a.comp'), '#include "common.glsl"\nvoid main(){\n resolve_light(1u, 2u);\n}\n'); + fs.writeFileSync(path.join(shaders, 'b.comp'), '#include "common.glsl"\nvoid evaluate(){\n resolve_light(3u, 4u);\n}\n'); + fs.writeFileSync(path.join(shaders, 'temporal.hlsli'), 'void RTXDI_DITemporalResampling(uint2 pixel){}\n'); + fs.writeFileSync(path.join(shaders, 'di.hlsl'), '#include "temporal.hlsli"\nvoid main(){\n RTXDI_DITemporalResampling(uint2(0, 0));\n}\n'); + const largeCalleeBody = Array.from({ length: 700 }, (_, index) => + ` value += ${index}u; // keep this endpoint larger than the explore output budget` + ).join('\n'); + fs.writeFileSync(path.join(shaders, 'huge_trace.glsl'), `void huge_trace(){\n uint value = 0u;\n${largeCalleeBody}\n}\n`); + fs.writeFileSync(path.join(shaders, 'huge.rgen'), '#include "huge_trace.glsl"\nvoid main(){ huge_trace(); }\n'); + const fillers = path.join(dir, 'fillers'); + fs.mkdirSync(fillers); + for (let index = 0; index < 500; index++) fs.writeFileSync(path.join(fillers, `f${index}.ts`), ''); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const result = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'trace resolve_light from shaders/a.comp line 3 and shaders/b.comp (line 3)', + maxFiles: 5, + }); + const text = result.content[0]!.text; + expect(text).toContain('Flow (call paths at the source locations you queried)'); + expect(text).toContain('@shaders/a.comp:3'); + expect(text).toContain('@shaders/b.comp:3'); + expect(text).toContain('**Relationships**'); + expect(text).toContain('- main → resolve_light'); + expect(text).toContain('- evaluate → resolve_light'); + expect(text).toContain('**`shaders/a.comp`**'); + expect(text).toContain('**`shaders/b.comp`**'); + + const namedResult = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'main resolve_light from shaders/a.comp', + maxFiles: 5, + }); + const namedText = namedResult.content[0]!.text; + expect(namedText).toContain('**Relationships**'); + expect(namedText).toContain('- main → resolve_light'); + + const directNamedResult = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'show main → resolve_light', + maxFiles: 5, + }); + const directNamedText = directNamedResult.content[0]!.text; + expect(directNamedText).toContain('Flow (direct call paths among the symbols you queried)'); + expect(directNamedText).toContain('- main → resolve_light'); + expect(directNamedText).toContain('**`shaders/a.comp`**'); + expect(directNamedText).toContain('**`shaders/common.glsl`**'); + + const hlslResult = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'trace main to RTXDI_DITemporalResampling from shaders/di.hlsl line 3', + maxFiles: 5, + }); + const hlslText = hlslResult.content[0]!.text; + expect(hlslText).toContain('@shaders/di.hlsl:3'); + expect(hlslText).toContain('- main → RTXDI_DITemporalResampling'); + + const directHlslResult = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'show main → RTXDI_DITemporalResampling', + maxFiles: 5, + }); + const directHlslText = directHlslResult.content[0]!.text; + expect(directHlslText).toContain('Flow (direct call paths among the symbols you queried)'); + expect(directHlslText).toContain('- main → RTXDI_DITemporalResampling'); + expect(directHlslText).toContain('**`shaders/di.hlsl`**'); + expect(directHlslText).toContain('**`shaders/temporal.hlsli`**'); + + const largeEndpointResult = await new ToolHandler(cg).execute('codegraph_explore', { + query: 'show main → huge_trace', + maxFiles: 5, + }); + const largeEndpointText = largeEndpointResult.content[0]!.text; + expect(largeEndpointText).toContain('- main → huge_trace'); + expect(largeEndpointText).toContain('**`shaders/huge.rgen`**'); + expect(largeEndpointText).toContain('**`shaders/huge_trace.glsl`**'); + } finally { + cg.destroy(); + } + }); +}); + +describe('shader config and include-scoped resolution', () => { + it('loads include roots and aliases from codegraph.json', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-config-')); + tempDirs.push(dir); + fs.writeFileSync(path.join(dir, 'codegraph.json'), JSON.stringify({ + shaderIncludePaths: ['shaders/include', './vendor/shaders/'], + shaderIncludeAliases: { '$TOOLS': 'pxr/imaging' }, + })); + expect(loadShaderIncludePaths(dir)).toEqual(['shaders/include', 'vendor/shaders']); + expect(loadShaderIncludeAliases(dir)).toEqual({ '$TOOLS': 'pxr/imaging' }); + }); + + it('applies quoted, angle, root, and alias include rules without escaping the project', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-includes-')); + tempDirs.push(dir); + fs.writeFileSync(path.join(dir, 'codegraph.json'), JSON.stringify({ + shaderIncludePaths: ['shared'], + shaderIncludeAliases: { '$TOOLS': 'vendor/tools' }, + })); + const files = new Map([ + ['shaders/main.comp', '#include "common.glsl"\n#include \n#include "$TOOLS/lib.glsl"\n#include "../../outside.glsl"\nvoid main(){}'], + ['shaders/common.glsl', 'float localHelper(){}'], + ['shared/common.glsl', 'float wrongHelper(){}'], + ['shared/root.glsl', 'float rootHelper(){}'], + ['vendor/tools/lib.glsl', 'float aliasHelper(){}'], + ['../outside.glsl', 'float outsideHelper(){}'], + ]); + const nodes = new Map(); + for (const file of files.keys()) nodes.set(file, [node({ id: `file:${file}`, kind: 'file', name: path.basename(file), filePath: file, language: 'glsl' })]); + const context = fakeContext(files, nodes, [...nodes.values()].flat(), dir); + const closure = new ShaderResolver(context).getClosure('shaders/main.comp', 'glsl'); + expect([...closure.keys()]).toEqual(expect.arrayContaining(['shaders/main.comp', 'shaders/common.glsl', 'shared/root.glsl', 'vendor/tools/lib.glsl'])); + expect(closure.has('shared/common.glsl')).toBe(false); + expect(closure.has('../outside.glsl')).toBe(false); + }); + + it('resolves a compiler-root include by unique project suffix but leaves duplicate bridge suffixes ambiguous', () => { + const files = new Map([ + ['Tests/Compile.hlsl', '#include \n#include '], + ['Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli', 'void finalize(){}'], + ['Samples/A/Bridge/RAB_Surface.hlsli', 'void surface(){}'], + ['Samples/B/Bridge/RAB_Surface.hlsli', 'void surface(){}'], + ]); + const nodes = new Map(); + for (const file of files.keys()) nodes.set(file, [node({ id: `file:${file}`, kind: 'file', name: path.basename(file), filePath: file, language: 'hlsl' })]); + const closure = new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())) + .getClosure('Tests/Compile.hlsl', 'hlsl'); + expect(closure.has('Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli')).toBe(true); + expect(closure.has('Samples/A/Bridge/RAB_Surface.hlsli')).toBe(false); + expect(closure.has('Samples/B/Bridge/RAB_Surface.hlsli')).toBe(false); + }); + + it('resolves calls only through the transitive include closure and drops equal-depth ambiguity', () => { + const files = new Map([ + ['shaders/main.rgen', '#include "a.glsl"\nvoid main(){ helper(); }'], + ['shaders/a.glsl', '#include "nested/b.glsl"'], + ['shaders/nested/b.glsl', 'void helper(){}'], + ['other/helper.glsl', 'void helper(){}'], + ]); + const nodes = new Map([ + ['shaders/main.rgen', [node({ id: 'f-main', kind: 'file', name: 'main.rgen', filePath: 'shaders/main.rgen', language: 'glsl' }), node({ id: 'main', kind: 'function', name: 'main', filePath: 'shaders/main.rgen', language: 'glsl' })]], + ['shaders/a.glsl', [node({ id: 'f-a', kind: 'file', name: 'a.glsl', filePath: 'shaders/a.glsl', language: 'glsl' })]], + ['shaders/nested/b.glsl', [node({ id: 'f-b', kind: 'file', name: 'b.glsl', filePath: 'shaders/nested/b.glsl', language: 'glsl' }), node({ id: 'included-helper', kind: 'function', name: 'helper', filePath: 'shaders/nested/b.glsl', language: 'glsl' })]], + ['other/helper.glsl', [node({ id: 'f-other', kind: 'file', name: 'helper.glsl', filePath: 'other/helper.glsl', language: 'glsl' }), node({ id: 'global-helper', kind: 'function', name: 'helper', filePath: 'other/helper.glsl', language: 'glsl' })]], + ]); + const allNodes = [...nodes.values()].flat(); + const context = fakeContext(files, nodes, allNodes, process.cwd()); + const resolver = new ShaderResolver(context); + const ref: UnresolvedRef = { fromNodeId: 'main', referenceName: 'helper', referenceKind: 'calls', line: 2, column: 0, filePath: 'shaders/main.rgen', language: 'glsl' }; + expect(resolver.resolve(ref)?.targetNodeId).toBe('included-helper'); + + files.set('shaders/c.glsl', 'void helper(){}'); + files.set('shaders/a.glsl', 'void helper(){}'); + files.set('shaders/main.rgen', '#include "a.glsl"\n#include "c.glsl"\nvoid main(){ helper(); }'); + nodes.set('shaders/a.glsl', [node({ id: 'f-a', kind: 'file', name: 'a.glsl', filePath: 'shaders/a.glsl', language: 'glsl' }), node({ id: 'a-helper', kind: 'function', name: 'helper', filePath: 'shaders/a.glsl', language: 'glsl' })]); + nodes.set('shaders/c.glsl', [node({ id: 'f-c', kind: 'file', name: 'c.glsl', filePath: 'shaders/c.glsl', language: 'glsl' }), node({ id: 'c-helper', kind: 'function', name: 'helper', filePath: 'shaders/c.glsl', language: 'glsl' })]); + const ambiguousContext = fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd()); + expect(new ShaderResolver(ambiguousContext).resolve(ref)).toBeNull(); + }); + + it('resolves equivalent injected shader declarations when no textual include edge exists', () => { + const files = new Map([ + ['shaders/use.glsl', 'float use(inout uint state) { return rand_f32(state); }'], + ['shaders/a.glsl', 'float rand_f32(inout uint seed) { return 0.0; }'], + ['shaders/b.glsl', 'float rand_f32(inout uint state) { return 1.0; }'], + ]); + const nodes = new Map([ + ['shaders/use.glsl', [node({ id: 'use-file', kind: 'file', name: 'use.glsl', filePath: 'shaders/use.glsl', language: 'glsl' })]], + ['shaders/a.glsl', [node({ id: 'a-file', kind: 'file', name: 'a.glsl', filePath: 'shaders/a.glsl', language: 'glsl' }), node({ id: 'rand-a', kind: 'function', name: 'rand_f32', filePath: 'shaders/a.glsl', language: 'glsl', signature: 'float inout uint seed' })]], + ['shaders/b.glsl', [node({ id: 'b-file', kind: 'file', name: 'b.glsl', filePath: 'shaders/b.glsl', language: 'glsl' }), node({ id: 'rand-b', kind: 'function', name: 'rand_f32', filePath: 'shaders/b.glsl', language: 'glsl', signature: 'float inout uint state' })]], + ]); + const ref: UnresolvedRef = { fromNodeId: 'use-fn', referenceName: 'rand_f32', referenceKind: 'calls', line: 1, column: 0, filePath: 'shaders/use.glsl', language: 'glsl' }; + const resolved = new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())).resolve(ref); + expect(resolved?.targetNodeId).toBe('rand-a'); + }); + + it('resolves sibling includes through their shared shader translation unit', () => { + const files = new Map([ + ['shaders/path.rgen', '#include "rt_common.glsl"\n#include "pathtrace_integrator.glsl"\nvoid main(){}'], + ['shaders/rt_common.glsl', 'uint renderer_debug_view(){ return 0u; }'], + ['shaders/pathtrace_integrator.glsl', 'vec3 trace_path(){ return vec3(renderer_debug_view()); }'], + ['other/rt_common.glsl', 'uint renderer_debug_view(){ return 1u; }'], + ]); + const nodes = new Map([ + ['shaders/path.rgen', [node({ id: 'root-file', kind: 'file', name: 'path.rgen', filePath: 'shaders/path.rgen', language: 'glsl' })]], + ['shaders/rt_common.glsl', [node({ id: 'common-file', kind: 'file', name: 'rt_common.glsl', filePath: 'shaders/rt_common.glsl', language: 'glsl' }), node({ id: 'debug-view', kind: 'function', name: 'renderer_debug_view', filePath: 'shaders/rt_common.glsl', language: 'glsl' })]], + ['shaders/pathtrace_integrator.glsl', [node({ id: 'integrator-file', kind: 'file', name: 'pathtrace_integrator.glsl', filePath: 'shaders/pathtrace_integrator.glsl', language: 'glsl' }), node({ id: 'trace-path', kind: 'function', name: 'trace_path', filePath: 'shaders/pathtrace_integrator.glsl', language: 'glsl' })]], + ['other/rt_common.glsl', [node({ id: 'other-file', kind: 'file', name: 'rt_common.glsl', filePath: 'other/rt_common.glsl', language: 'glsl' }), node({ id: 'other-debug-view', kind: 'function', name: 'renderer_debug_view', filePath: 'other/rt_common.glsl', language: 'glsl' })]], + ]); + const allNodes = [...nodes.values()].flat(); + const ref: UnresolvedRef = { fromNodeId: 'trace-path', referenceName: 'renderer_debug_view', referenceKind: 'calls', line: 1, column: 33, filePath: 'shaders/pathtrace_integrator.glsl', language: 'glsl' }; + expect(new ShaderResolver(fakeContext(files, nodes, allNodes, process.cwd())).resolve(ref)?.targetNodeId).toBe('debug-view'); + + files.set('shaders/path-alt.rgen', '#include "../other/rt_common.glsl"\n#include "pathtrace_integrator.glsl"\nvoid main(){}'); + nodes.set('shaders/path-alt.rgen', [node({ id: 'alt-root-file', kind: 'file', name: 'path-alt.rgen', filePath: 'shaders/path-alt.rgen', language: 'glsl' })]); + const ambiguousNodes = [...nodes.values()].flat(); + expect(new ShaderResolver(fakeContext(files, nodes, ambiguousNodes, process.cwd())).resolve(ref)).toBeNull(); + }); + + it('selects the HLSL overload reachable through a compiler-root include suffix', () => { + const files = new Map([ + ['Tests/DiCompile.hlsl', '#include \n#include '], + ['Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli', 'void RTXDI_FinalizeResampling(inout DIReservoir r, float n, float d){}'], + ['Libraries/Rtxdi/Include/Rtxdi/DI/TemporalResampling.hlsli', 'void run(){ RTXDI_FinalizeResampling(r, 1, 1); }'], + ['Libraries/Rtxdi/Include/Rtxdi/PT/Reservoir.hlsli', 'void RTXDI_FinalizeResampling(inout PTReservoir r, float n, float d){}'], + ]); + const nodes = new Map([ + ['Tests/DiCompile.hlsl', [node({ id: 'root', kind: 'file', name: 'DiCompile.hlsl', filePath: 'Tests/DiCompile.hlsl', language: 'hlsl' })]], + ['Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli', [node({ id: 'di-file', kind: 'file', name: 'Reservoir.hlsli', filePath: 'Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli', language: 'hlsl' }), node({ id: 'di-finalize', kind: 'function', name: 'RTXDI_FinalizeResampling', filePath: 'Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli', language: 'hlsl', signature: 'void (inout DIReservoir r, float n, float d)' })]], + ['Libraries/Rtxdi/Include/Rtxdi/DI/TemporalResampling.hlsli', [node({ id: 'temporal-file', kind: 'file', name: 'TemporalResampling.hlsli', filePath: 'Libraries/Rtxdi/Include/Rtxdi/DI/TemporalResampling.hlsli', language: 'hlsl' }), node({ id: 'run', kind: 'function', name: 'run', filePath: 'Libraries/Rtxdi/Include/Rtxdi/DI/TemporalResampling.hlsli', language: 'hlsl' })]], + ['Libraries/Rtxdi/Include/Rtxdi/PT/Reservoir.hlsli', [node({ id: 'pt-file', kind: 'file', name: 'Reservoir.hlsli', filePath: 'Libraries/Rtxdi/Include/Rtxdi/PT/Reservoir.hlsli', language: 'hlsl' }), node({ id: 'pt-finalize', kind: 'function', name: 'RTXDI_FinalizeResampling', filePath: 'Libraries/Rtxdi/Include/Rtxdi/PT/Reservoir.hlsli', language: 'hlsl', signature: 'void (inout PTReservoir r, float n, float d)' })]], + ]); + const ref: UnresolvedRef = { fromNodeId: 'run', referenceName: 'RTXDI_FinalizeResampling', referenceKind: 'calls', line: 1, column: 12, filePath: 'Libraries/Rtxdi/Include/Rtxdi/DI/TemporalResampling.hlsli', language: 'hlsl' }; + expect(new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())).resolve(ref)?.targetNodeId).toBe('di-finalize'); + }); + + it('selects a same-file HLSL overload by call arity', () => { + const files = new Map([['shaders/use.hlsl', 'float load(float a){}\nfloat load(float a,float b){}\nfloat run(){return load(1,2);}']]); + const nodes = new Map([['shaders/use.hlsl', [ + node({ id: 'file', kind: 'file', name: 'use.hlsl', filePath: 'shaders/use.hlsl', language: 'hlsl' }), + node({ id: 'one', kind: 'function', name: 'load', filePath: 'shaders/use.hlsl', language: 'hlsl', signature: 'float (float a)' }), + node({ id: 'two', kind: 'function', name: 'load', filePath: 'shaders/use.hlsl', language: 'hlsl', signature: 'float (float a, float b)' }), + node({ id: 'run', kind: 'function', name: 'run', filePath: 'shaders/use.hlsl', language: 'hlsl' }), + ]]]); + const ref: UnresolvedRef = { fromNodeId: 'run', referenceName: 'load', referenceKind: 'calls', line: 3, column: 19, filePath: 'shaders/use.hlsl', language: 'hlsl', candidates: ['arity:2'] }; + expect(new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())).resolve(ref)?.targetNodeId).toBe('two'); + }); + + it('selects same-arity HLSL overloads using exact argument-type evidence', () => { + const files = new Map([['shaders/use.hlsl', 'void activate(inout uint2 p,bool prev,uint field){}\nvoid activate(inout int2 p,bool prev,uint field){}\nvoid run(uint2 p){activate(p,false,0);}\n']]); + const nodes = new Map([['shaders/use.hlsl', [ + node({ id: 'file', kind: 'file', name: 'use.hlsl', filePath: 'shaders/use.hlsl', language: 'hlsl' }), + node({ id: 'uint', kind: 'function', name: 'activate', filePath: 'shaders/use.hlsl', language: 'hlsl', signature: 'void (inout uint2 p, bool prev, uint field)' }), + node({ id: 'int', kind: 'function', name: 'activate', filePath: 'shaders/use.hlsl', language: 'hlsl', signature: 'void (inout int2 p, bool prev, uint field)' }), + node({ id: 'run', kind: 'function', name: 'run', filePath: 'shaders/use.hlsl', language: 'hlsl' }), + ]]]); + const ref: UnresolvedRef = { fromNodeId: 'run', referenceName: 'activate', referenceKind: 'calls', line: 3, column: 18, filePath: 'shaders/use.hlsl', language: 'hlsl', candidates: ['arity:3', 'argtype:0:uint2'] }; + expect(new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())).resolve(ref)?.targetNodeId).toBe('uint'); + }); + + it('unions same-name shader implementations from mutually exclusive preprocessor branches', () => { + const files = new Map([ + ['shaders/main.comp', '#include "common.glsl"\nvoid main(){ recordCounter(1u); }'], + ['shaders/common.glsl', '#if DIAGNOSTICS\nvoid recordCounter(uint index){}\n#else\n#define recordCounter(index)\n#endif\n'], + ]); + const nodes = new Map([ + ['shaders/main.comp', [ + node({ id: 'main-file', kind: 'file', name: 'main.comp', filePath: 'shaders/main.comp', language: 'glsl' }), + node({ id: 'main', kind: 'function', name: 'main', filePath: 'shaders/main.comp', language: 'glsl', startLine: 2 }), + ]], + ['shaders/common.glsl', [ + node({ id: 'common-file', kind: 'file', name: 'common.glsl', filePath: 'shaders/common.glsl', language: 'glsl' }), + node({ id: 'enabled', kind: 'function', name: 'recordCounter', filePath: 'shaders/common.glsl', language: 'glsl', startLine: 2, signature: 'void recordCounter(uint index)', decorators: ['pp:DIAGNOSTICS'] }), + node({ id: 'disabled', kind: 'function', name: 'recordCounter', filePath: 'shaders/common.glsl', language: 'glsl', startLine: 4, signature: '#define recordCounter(index)', decorators: ['pp:!(DIAGNOSTICS)'] }), + ]], + ]); + const ref: UnresolvedRef = { fromNodeId: 'main', referenceName: 'recordCounter', referenceKind: 'calls', line: 2, column: 13, filePath: 'shaders/main.comp', language: 'glsl', candidates: ['arity:1'] }; + const resolver = new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())); + expect(resolver.resolve(ref)).toBeNull(); + expect(resolver.getConditionalCallTargets(ref).map((target) => target.id)).toEqual(['enabled', 'disabled']); + }); + + it('does not union overloads from independent shader conditionals', () => { + const files = new Map([ + ['shaders/main.comp', '#include "common.glsl"\nvoid main(){ choose(value); }'], + ['shaders/common.glsl', '#if FEATURE_A\nvoid choose(uint value){}\n#endif\n#if FEATURE_B\nvoid choose(float value){}\n#endif\n'], + ]); + const nodes = new Map([ + ['shaders/main.comp', [node({ id: 'main-file', kind: 'file', name: 'main.comp', filePath: 'shaders/main.comp', language: 'glsl' }), node({ id: 'main', kind: 'function', name: 'main', filePath: 'shaders/main.comp', language: 'glsl', startLine: 2 })]], + ['shaders/common.glsl', [ + node({ id: 'common-file', kind: 'file', name: 'common.glsl', filePath: 'shaders/common.glsl', language: 'glsl' }), + node({ id: 'a', kind: 'function', name: 'choose', filePath: 'shaders/common.glsl', language: 'glsl', startLine: 2, signature: 'void choose(uint value)', decorators: ['pp:FEATURE_A'] }), + node({ id: 'b', kind: 'function', name: 'choose', filePath: 'shaders/common.glsl', language: 'glsl', startLine: 5, signature: 'void choose(float value)', decorators: ['pp:FEATURE_B'] }), + ]], + ]); + const ref: UnresolvedRef = { fromNodeId: 'main', referenceName: 'choose', referenceKind: 'calls', line: 2, column: 13, filePath: 'shaders/main.comp', language: 'glsl', candidates: ['arity:1'] }; + expect(new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())).getConditionalCallTargets(ref)).toEqual([]); + }); + + it('returns every equivalent application-bridge target for a shared HLSL call site', () => { + const files = new Map([ + ['Samples/A/main.hlsl', '#include "../../Shared/Algorithm.hlsli"\n#include "Bridge/RAB_Surface.hlsli"'], + ['Samples/B/main.hlsl', '#include "../../Shared/Algorithm.hlsli"\n#include "Bridge/RAB_Surface.hlsli"'], + ['Shared/Algorithm.hlsli', 'void run(){ RAB_GetGBufferSurface(0, false); }'], + ['Samples/A/Bridge/RAB_Surface.hlsli', 'Surface RAB_GetGBufferSurface(int2 p, bool prev){return 0;}'], + ['Samples/B/Bridge/RAB_Surface.hlsli', 'Surface RAB_GetGBufferSurface(int2 p, bool prev){return 0;}'], + ]); + const nodes = new Map([ + ['Samples/A/main.hlsl', [node({ id: 'a-root', kind: 'file', name: 'main.hlsl', filePath: 'Samples/A/main.hlsl', language: 'hlsl' })]], + ['Samples/B/main.hlsl', [node({ id: 'b-root', kind: 'file', name: 'main.hlsl', filePath: 'Samples/B/main.hlsl', language: 'hlsl' })]], + ['Shared/Algorithm.hlsli', [node({ id: 'shared-file', kind: 'file', name: 'Algorithm.hlsli', filePath: 'Shared/Algorithm.hlsli', language: 'hlsl' }), node({ id: 'run', kind: 'function', name: 'run', filePath: 'Shared/Algorithm.hlsli', language: 'hlsl' })]], + ['Samples/A/Bridge/RAB_Surface.hlsli', [node({ id: 'a-file', kind: 'file', name: 'RAB_Surface.hlsli', filePath: 'Samples/A/Bridge/RAB_Surface.hlsli', language: 'hlsl' }), node({ id: 'a-surface', kind: 'function', name: 'RAB_GetGBufferSurface', filePath: 'Samples/A/Bridge/RAB_Surface.hlsli', language: 'hlsl', signature: 'Surface (int2 p, bool prev)' })]], + ['Samples/B/Bridge/RAB_Surface.hlsli', [node({ id: 'b-file', kind: 'file', name: 'RAB_Surface.hlsli', filePath: 'Samples/B/Bridge/RAB_Surface.hlsli', language: 'hlsl' }), node({ id: 'b-surface', kind: 'function', name: 'RAB_GetGBufferSurface', filePath: 'Samples/B/Bridge/RAB_Surface.hlsli', language: 'hlsl', signature: 'Surface (int2, bool previousFrame)' })]], + ]); + const ref: UnresolvedRef = { fromNodeId: 'run', referenceName: 'RAB_GetGBufferSurface', referenceKind: 'calls', line: 1, column: 12, filePath: 'Shared/Algorithm.hlsli', language: 'hlsl' }; + const resolver = new ShaderResolver(fakeContext(files, nodes, [...nodes.values()].flat(), process.cwd())); + expect(resolver.resolve(ref)).toBeNull(); + expect(resolver.getContextualCallTargets(ref).map((target) => target.id)).toEqual(['a-surface', 'b-surface']); + expect(resolver.getContextualCallTargetContexts(ref).map((target) => [target.node.id, target.contextRoots])).toEqual([ + ['a-surface', ['Samples/A/main.hlsl']], + ['b-surface', ['Samples/B/main.hlsl']], + ]); + }); + + it('keeps shader impact inside the selected translation-unit context', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-context-impact-')); + + tempDirs.push(dir); + for (const target of ['A', 'B']) { + fs.mkdirSync(path.join(dir, 'Targets', target, 'Bridge'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'Targets', target, 'main.hlsl'), '#include "../../Shared/algorithm.hlsli"\n#include "Bridge/bridge.hlsli"\nvoid main(){ sharedAlgorithm(); }\n'); + fs.writeFileSync(path.join(dir, 'Targets', target, 'Bridge', 'bridge.hlsli'), 'float BridgeSurface(int2 p){ return 1.0; }\n'); + } + fs.mkdirSync(path.join(dir, 'Shared'), { recursive: true }); + fs.writeFileSync(path.join(dir, 'Shared', 'algorithm.hlsli'), 'void sharedLeaf(){}\nvoid sharedAlgorithm(){ sharedLeaf(); BridgeSurface(int2(0, 0)); }\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const bridge = cg.getNodesInFile('Targets/A/Bridge/bridge.hlsli').find((candidate) => candidate.name === 'BridgeSurface'); + expect(bridge).toBeDefined(); + const impact = cg.getImpactRadius(bridge!.id, 6); + const files = new Set([...impact.nodes.values()].map((candidate) => candidate.filePath)); + expect(files).toContain('Targets/A/main.hlsl'); + expect(files).not.toContain('Targets/B/main.hlsl'); + expect([...impact.nodes.values()].some((candidate) => candidate.filePath === 'Targets/A/main.hlsl' && candidate.kind === 'import' && candidate.startLine === 2)).toBe(true); + + const leaf = cg.getNodesInFile('Shared/algorithm.hlsli').find((candidate) => candidate.name === 'sharedLeaf'); + expect(leaf).toBeDefined(); + const sharedImpact = cg.getImpactRadius(leaf!.id, 2); + expect([...sharedImpact.nodes.values()].some((candidate) => candidate.filePath === 'Targets/A/main.hlsl' && candidate.kind === 'import' && candidate.startLine === 1)).toBe(true); + expect([...sharedImpact.nodes.values()].some((candidate) => candidate.filePath === 'Targets/B/main.hlsl' && candidate.kind === 'import' && candidate.startLine === 1)).toBe(true); + expect([...sharedImpact.nodes.values()].filter((candidate) => candidate.filePath.endsWith('/main.hlsl') && candidate.name === 'main')).toHaveLength(2); + expect(sharedImpact.edges.filter((edge) => edge.metadata?.synthesizedBy === 'shader-context-entry').map((edge) => edge.line)).toEqual([3, 3]); + } finally { + cg.destroy(); + } + }); + + it('persists include-scoped HLSL overload edges selected by call arity', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-hlsl-overload-')); + tempDirs.push(dir); + const shaders = path.join(dir, 'shaders'); + fs.mkdirSync(shaders); + fs.writeFileSync(path.join(shaders, 'main.hlsl'), '#include "library.hlsli"\nfloat run(){return load(1.0, 2.0);}\n'); + fs.writeFileSync(path.join(shaders, 'library.hlsli'), 'float load(float a){return a;}\nfloat load(float a,float b){return a+b;}\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const twoArg = cg.getNodesInFile('shaders/library.hlsli').find((candidate) => candidate.name === 'load' && candidate.startLine === 2); + expect(twoArg).toBeDefined(); + expect(cg.getCallers(twoArg!.id).map((caller) => caller.node.name)).toContain('run'); + } finally { + cg.close(); + } + }); + + it('persists include-scoped GLSL overload edges selected by call arity', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-glsl-overload-')); + tempDirs.push(dir); + const shaders = path.join(dir, 'shaders'); + fs.mkdirSync(shaders); + fs.writeFileSync(path.join(shaders, 'main.comp'), '#include "library.glsl"\nvoid main(){uint index; Item item; resolve(item, index, 1u, 2u);}\n'); + fs.writeFileSync(path.join(shaders, 'library.glsl'), 'bool resolve(Item item,out uint index){return true;}\nbool resolve(Item item,out uint index,uint version,uint environment){return true;}\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const fourArg = cg.getNodesInFile('shaders/library.glsl').find((candidate) => candidate.name === 'resolve' && candidate.startLine === 2); + expect(fourArg?.signature).toBe('bool resolve(Item item,out uint index,uint version,uint environment)'); + expect(cg.getCallers(fourArg!.id).map((caller) => caller.node.name)).toContain('main'); + } finally { + cg.close(); + } + }); + + it('persists calls to every mutually exclusive function/macro shader variant', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-glsl-conditional-call-')); + tempDirs.push(dir); + const shaders = path.join(dir, 'shaders'); + fs.mkdirSync(shaders); + fs.writeFileSync(path.join(shaders, 'main.comp'), '#include "common.glsl"\nvoid main(){\n recordCounter(1u);\n recordCounter(2u);\n}\n'); + fs.writeFileSync(path.join(shaders, 'common.glsl'), '#if DIAGNOSTICS\nvoid recordCounter(uint index){}\n#else\n#define recordCounter(index)\n#endif\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + const main = cg.getNodesInFile('shaders/main.comp').find((candidate) => candidate.name === 'main'); + const variants = cg.getNodesInFile('shaders/common.glsl').filter((candidate) => candidate.name === 'recordCounter'); + expect(variants).toHaveLength(2); + for (const variant of variants) { + expect(cg.getCallers(variant.id).map((caller) => caller.node.name)).toContain('main'); + } + const variantIds = new Set(variants.map((variant) => variant.id)); + expect(cg.getOutgoingEdges(main!.id).filter((edge) => edge.kind === 'calls' && variantIds.has(edge.target))).toHaveLength(4); + } finally { + cg.close(); + } + }); + + it('re-resolves shader include closures after incremental sync', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-shader-sync-')); + tempDirs.push(dir); + const shaders = path.join(dir, 'shaders'); + fs.mkdirSync(shaders); + fs.writeFileSync(path.join(shaders, 'main.comp'), '#include "common.glsl"\nvoid main() { helper(); }\n'); + fs.writeFileSync(path.join(shaders, 'common.glsl'), 'float oldLeaf(){return 1.0;}\nfloat newLeaf(){return 2.0;}\nfloat helper(){return oldLeaf();}\n'); + const cg = CodeGraph.initSync(dir); + try { + await cg.indexAll(); + let main = cg.getNodesInFile('shaders/main.comp').find((candidate) => candidate.name === 'main'); + let helper = cg.getNodesInFile('shaders/common.glsl').find((candidate) => candidate.name === 'helper'); + expect(cg.getCallees(main!.id).map((callee) => callee.node.name)).toContain('helper'); + expect(cg.getCallees(helper!.id).map((callee) => callee.node.name)).toContain('oldLeaf'); + + fs.writeFileSync(path.join(shaders, 'common.glsl'), 'float oldLeaf(){return 1.0;}\nfloat newLeaf(){return 2.0;}\nfloat helper(){return newLeaf();}\n'); + const synced = await cg.sync(); + expect(synced.filesModified).toBe(1); + main = cg.getNodesInFile('shaders/main.comp').find((candidate) => candidate.name === 'main'); + helper = cg.getNodesInFile('shaders/common.glsl').find((candidate) => candidate.name === 'helper'); + expect(cg.getCallees(main!.id).map((callee) => callee.node.name)).toContain('helper'); + expect(cg.getCallees(helper!.id).map((callee) => callee.node.name)).toContain('newLeaf'); + expect(cg.getCallees(helper!.id).map((callee) => callee.node.name)).not.toContain('oldLeaf'); + } finally { + cg.close(); + } + }); +}); + +describe('C++ shader integration synthesis', () => { + it('links shader filenames, entry points, bindings, specialization IDs, and push constants', async () => { + const cpp = `void buildPipeline() { + loadShader("compiled/path.rgen.variant.spv"); + VkDescriptorSetLayoutBinding binding = { 4, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR }; + VkSpecializationMapEntry spec{ 3, 0, 4 }; + VkVertexInputAttributeDescription attribute{ 2, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 }; + vkCmdPushConstants(cmd, layout, stages, 0, sizeof(Push), &push); +}`; + const files = new Map([ + ['src/pipeline.cpp', cpp], + ['shaders/path.rgen', 'void main() {}'], + ]); + const cppFn = node({ id: 'cpp-fn', kind: 'function', name: 'buildPipeline', qualifiedName: 'buildPipeline', filePath: 'src/pipeline.cpp', language: 'cpp', startLine: 1, endLine: 7 }); + const shaderFile = node({ id: 'shader-file', kind: 'file', name: 'path.rgen', filePath: 'shaders/path.rgen', language: 'glsl' }); + const entry = node({ id: 'entry', kind: 'function', name: 'main', filePath: 'shaders/path.rgen', language: 'glsl', decorators: ['entrypoint', 'shader:ray-generation'] }); + const resource = node({ id: 'resource', kind: 'variable', name: 'scene', filePath: 'shaders/path.rgen', language: 'glsl', decorators: ['binding:4', 'resource:acceleration-structure'] }); + const spec = node({ id: 'spec', kind: 'constant', name: 'MODE', filePath: 'shaders/path.rgen', language: 'glsl', decorators: ['constant_id:3'] }); + const vertex = node({ id: 'vertex', kind: 'variable', name: 'position', filePath: 'shaders/path.rgen', language: 'glsl', decorators: ['location:2', 'interface:in'] }); + const push = node({ id: 'push', kind: 'struct', name: 'Push', filePath: 'shaders/path.rgen', language: 'glsl', decorators: ['push_constant'] }); + const byFile = new Map([['src/pipeline.cpp', [node({ id: 'cpp-file', kind: 'file', name: 'pipeline.cpp', filePath: 'src/pipeline.cpp', language: 'cpp' }), cppFn]], ['shaders/path.rgen', [shaderFile, entry, resource, spec, vertex, push]]]); + const context = fakeContext(files, byFile, [...byFile.values()].flat(), process.cwd()); + const queries = { getOutgoingEdges: () => [], getNodeById: () => null } as any; + const edges = await shaderIntegrationEdges(queries, context, async () => {}); + expect(edges.some((edge) => edge.kind === 'calls' && edge.target === 'entry')).toBe(true); + expect(edges.some((edge) => edge.target === 'shader-file' && edge.metadata?.synthesizedBy === 'shader-file')).toBe(true); + expect(edges.some((edge) => edge.target === 'resource')).toBe(true); + expect(edges.some((edge) => edge.target === 'spec')).toBe(true); + expect(edges.some((edge) => edge.target === 'vertex')).toBe(true); + expect(edges.some((edge) => edge.target === 'push')).toBe(true); + }); + + it('keeps passive shader filenames as file references without calling the entry point', async () => { + const cpp = `bool resolveProjectRoot() { + return std::filesystem::exists("shaders/pathtrace.rgen"); +} +Contract buildPipelineContract() { + const char* dependency = "shaders/pathtrace.rgen"; + return rendererContractArray({"shaders/pathtrace.rgen"}); +} +void buildPipeline() { + compileShaderVariant(shaderDirectory / "shaders/pathtrace.rgen", ".variant", defines); +}`; + const files = new Map([ + ['src/pipeline.cpp', cpp], + ['shaders/pathtrace.rgen', 'void main() {}'], + ]); + const passive = node({ id: 'passive', kind: 'function', name: 'resolveProjectRoot', filePath: 'src/pipeline.cpp', language: 'cpp', startLine: 1, endLine: 3 }); + const contract = node({ id: 'contract', kind: 'function', name: 'buildPipelineContract', filePath: 'src/pipeline.cpp', language: 'cpp', startLine: 4, endLine: 7 }); + const loader = node({ id: 'loader', kind: 'function', name: 'buildPipeline', filePath: 'src/pipeline.cpp', language: 'cpp', startLine: 8, endLine: 10 }); + const shaderFile = node({ id: 'shader-file', kind: 'file', name: 'pathtrace.rgen', filePath: 'shaders/pathtrace.rgen', language: 'glsl' }); + const entry = node({ id: 'entry', kind: 'function', name: 'main', filePath: 'shaders/pathtrace.rgen', language: 'glsl', decorators: ['entrypoint'] }); + const byFile = new Map([ + ['src/pipeline.cpp', [node({ id: 'cpp-file', kind: 'file', name: 'pipeline.cpp', filePath: 'src/pipeline.cpp', language: 'cpp' }), passive, contract, loader]], + ['shaders/pathtrace.rgen', [shaderFile, entry]], + ]); + const edges = await shaderIntegrationEdges( + { getOutgoingEdges: () => [], getNodeById: () => null } as any, + fakeContext(files, byFile, [...byFile.values()].flat(), process.cwd()), + async () => {}, + ); + + expect(edges.filter((edge) => edge.target === 'shader-file').map((edge) => edge.source).sort()) + .toEqual(['contract', 'loader', 'passive']); + expect(edges.filter((edge) => edge.target === 'entry').map((edge) => edge.source)).toEqual(['loader']); + expect(edges.find((edge) => edge.source === 'loader' && edge.target === 'entry')?.line).toBe(9); + }); + + it('emits no interface edge when a C++ symbol is associated with multiple shaders', async () => { + const cpp = `void buildPipelines() { + loadShader("shaders/a.comp"); + loadShader("shaders/b.comp"); + VkDescriptorSetLayoutBinding binding = { 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER }; +}`; + const files = new Map([ + ['src/pipeline.cpp', cpp], + ['shaders/a.comp', 'void main() {}'], + ['shaders/b.comp', 'void main() {}'], + ]); + const cppFn = node({ id: 'cpp-fn', kind: 'function', name: 'buildPipelines', filePath: 'src/pipeline.cpp', language: 'cpp', startLine: 1, endLine: 5 }); + const shaderNodes = (name: string) => [ + node({ id: `${name}-file`, kind: 'file', name: `${name}.comp`, filePath: `shaders/${name}.comp`, language: 'glsl' }), + node({ id: `${name}-entry`, kind: 'function', name: 'main', filePath: `shaders/${name}.comp`, language: 'glsl', decorators: ['entrypoint'] }), + node({ id: `${name}-resource`, kind: 'struct', name: `${name}Buffer`, filePath: `shaders/${name}.comp`, language: 'glsl', decorators: ['binding:0', 'storage:buffer'] }), + ]; + const byFile = new Map([ + ['src/pipeline.cpp', [node({ id: 'cpp-file', kind: 'file', name: 'pipeline.cpp', filePath: 'src/pipeline.cpp', language: 'cpp' }), cppFn]], + ['shaders/a.comp', shaderNodes('a')], + ['shaders/b.comp', shaderNodes('b')], + ]); + const context = fakeContext(files, byFile, [...byFile.values()].flat(), process.cwd()); + const edges = await shaderIntegrationEdges({ getOutgoingEdges: () => [], getNodeById: () => null } as any, context, async () => {}); + expect(edges.filter((edge) => edge.metadata?.synthesizedBy === 'shader-interface')).toEqual([]); + }); +}); + +function fakeContext( + files: Map, + byFile: Map, + allNodes: Node[], + root: string, +): ResolutionContext { + const byName = (name: string) => allNodes.filter((candidate) => candidate.name === name); + return { + getNodesInFile: (file) => byFile.get(file) ?? [], + getNodesByName: byName, + getNodesByQualifiedName: (name) => allNodes.filter((candidate) => candidate.qualifiedName === name), + getNodesByKind: (kind) => allNodes.filter((candidate) => candidate.kind === kind), + iterateNodesByKind: function* (kind) { yield* allNodes.filter((candidate) => candidate.kind === kind); }, + fileExists: (file) => files.has(file), + readFile: (file) => files.get(file) ?? null, + getFileLines: (file) => files.get(file)?.split(/\r?\n/) ?? null, + getProjectRoot: () => root, + getAllFiles: () => [...files.keys()], + getNodesByLowerName: (name) => allNodes.filter((candidate) => candidate.name.toLowerCase() === name), + getImportMappings: () => [], + }; +} diff --git a/__tests__/status-json.test.ts b/__tests__/status-json.test.ts index 292ca209f..1ecab5aa7 100644 --- a/__tests__/status-json.test.ts +++ b/__tests__/status-json.test.ts @@ -59,6 +59,21 @@ describe('codegraph status --json — CI fields (#329)', () => { cg.close(); }); + it('advances lastIndexed after a pure deletion sync', async () => { + fs.writeFileSync(path.join(tempDir, 'a.ts'), 'export const a = 1;\n'); + fs.writeFileSync(path.join(tempDir, 'b.ts'), 'export const b = 2;\n'); + const cg = CodeGraph.initSync(tempDir); + await cg.indexAll(); + const indexed = cg.getLastIndexedAt()!; + + fs.unlinkSync(path.join(tempDir, 'b.ts')); + const beforeDelete = Date.now(); + const result = await cg.sync(); + expect(result.filesRemoved).toBe(1); + expect(cg.getLastIndexedAt()).toBeGreaterThanOrEqual(Math.max(indexed, beforeDelete)); + cg.close(); + }); + it('status --json on an UNINITIALIZED project reports version + indexPath + lastIndexed:null', () => { const out = runStatusJson(tempDir); expect(out.initialized).toBe(false); @@ -81,6 +96,15 @@ describe('codegraph status --json — CI fields (#329)', () => { expect(out.version).toBe(PKG_VERSION); expect(out.indexPath as string).toContain('.codegraph'); expect(typeof out.lastIndexed).toBe('string'); + expect(out.graphFileNodeCount).toBe(out.fileCount); + expect(out.filesWithoutNodes).toBe(0); + expect(out.index).toMatchObject({ + failedRefs: { + total: expect.any(Number), + byLanguage: expect.any(Object), + byKind: expect.any(Object), + }, + }); // ISO string that round-trips back into the index window. const ms = Date.parse(out.lastIndexed as string); expect(ms).toBeGreaterThanOrEqual(before - 1000); diff --git a/package.json b/package.json index c77f9bb5e..d9735bd1b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "files": [ "dist", "scripts", - "README.md" + "README.md", + "THIRD_PARTY_NOTICES.md" ], "scripts": { "build": "tsc && npm run copy-assets && node -e \"require('fs').chmodSync('dist/bin/codegraph.js', 0o755)\"", diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts index 4d85e204f..12b840be9 100644 --- a/src/bin/codegraph.ts +++ b/src/bin/codegraph.ts @@ -43,6 +43,7 @@ import { relaunchWithWasmRuntimeFlagsIfNeeded } from '../extraction/wasm-runtime import { installCommandSupervision } from './command-supervision'; import { EXTRACTION_VERSION } from '../extraction/extraction-version'; import { getTelemetry, TELEMETRY_DOCS, recordIndexEvent } from '../telemetry'; +import type { Node } from '../types'; // Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast. async function loadCodeGraph(): Promise { @@ -909,6 +910,8 @@ program // Zero on a healthy index; non-zero at rest means a resolution pass was // interrupted, so some files' call edges are missing (#1187). const pendingRefs = cg.getPendingReferenceCount(); + const failedRefs = cg.getFailedReferenceStats(); + const graphFileNodeCount = stats.nodesByKind.file ?? 0; // JSON output mode if (options.json) { @@ -920,6 +923,8 @@ program indexPath: getCodeGraphDir(projectPath), lastIndexed: lastIndexedMs != null ? new Date(lastIndexedMs).toISOString() : null, fileCount: stats.fileCount, + graphFileNodeCount, + filesWithoutNodes: Math.max(0, stats.fileCount - graphFileNodeCount), nodeCount: stats.nodeCount, edgeCount: stats.edgeCount, dbSizeBytes: stats.dbSizeBytes, @@ -948,6 +953,10 @@ program // interrupted resolution pass left edges missing; the next // sync sweeps them (#1187). pendingRefs, + // Attempted but unresolved references are expected to include + // external APIs/intrinsics; expose them for semantic auditing + // without mislabeling the structurally complete index as partial. + failedRefs, }, })); cg.destroy(); @@ -975,7 +984,8 @@ program // Index stats console.log(chalk.bold('Index Statistics:')); - console.log(` Files: ${formatNumber(stats.fileCount)}`); + const fileNodeGap = Math.max(0, stats.fileCount - graphFileNodeCount); + console.log(` Files: ${formatNumber(stats.fileCount)}${fileNodeGap ? ` (${formatNumber(graphFileNodeCount)} graph nodes; ${formatNumber(fileNodeGap)} metadata-only)` : ''}`); console.log(` Nodes: ${formatNumber(stats.nodeCount)}`); console.log(` Edges: ${formatNumber(stats.edgeCount)}`); console.log(` DB Size: ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`); @@ -991,6 +1001,7 @@ program ? chalk.green('wal') : chalk.yellow(`${journalMode || 'unknown'} ${getGlyphs().dash} WAL inactive; reads can block on writes`); console.log(` Journal: ${journalLabel}`); + console.log(` Unresolved:${String(formatNumber(failedRefs.total)).padStart(10)} attempted refs`); console.log(); // Node breakdown @@ -1540,6 +1551,33 @@ function normalizeIndexPath(filePath: string, projectPath: string): string { return f; } +function isGloballyAmbiguous(matches: Array<{ node: { filePath: string } }>): boolean { + return matches.length > 20 && new Set(matches.map((m) => m.node.filePath)).size > 8; +} + +function searchSymbolMatches(cg: { getNodesByName(name: string): Node[]; searchNodes(name: string, options: { limit: number }): Array<{ node: Node }> }, symbol: string): Array<{ node: Node }> { + return /^[A-Za-z_$][\w$]*$/.test(symbol) + ? cg.getNodesByName(symbol).map((node) => ({ node })) + : cg.searchNodes(symbol, { limit: 50 }); +} + +function narrowSymbolMatchesByLine( + matches: Array<{ node: Node }>, + line: number | undefined +): Array<{ node: Node }> { + if (!line || !Number.isFinite(line) || line < 1) return matches; + const exact = matches.filter((match) => match.node.startLine === line); + if (exact.length > 0) return exact; + return matches.filter( + (match) => match.node.startLine <= line && match.node.endLine >= line + ); +} + +function repeatedCppLambdaMatches(matches: Array<{ node: Node }>): Array<{ node: Node }> { + const lambdas = matches.filter((match) => match.node.decorators?.includes('cpp:lambda')); + return lambdas.length > 1 ? lambdas : []; +} + /** * Convert glob pattern to regex */ @@ -1789,9 +1827,11 @@ program .command('callers ') .description('Find all functions/methods that call a specific symbol') .option('-p, --path ', 'Project path') + .option('--file ', 'Narrow to the definition in this file (path or suffix)') + .option('--line ', 'Narrow to the definition starting at or containing this line') .option('-l, --limit ', 'Maximum results', '20') .option('-j, --json', 'Output as JSON') - .action(async (symbol: string, options: { path?: string; limit?: string; json?: boolean }) => { + .action(async (symbol: string, options: { path?: string; file?: string; line?: string; limit?: string; json?: boolean }) => { const projectPath = resolveProjectPath(options.path); try { @@ -1803,16 +1843,44 @@ program const { default: CodeGraph } = await loadCodeGraph(); const cg = await CodeGraph.open(projectPath); const limit = parseInt(options.limit || '20', 10); + const lineFilter = options.line ? parseInt(options.line, 10) : undefined; - const matches = cg.searchNodes(symbol, { limit: 50 }); + const fileFilter = options.file ? normalizeIndexPath(options.file, projectPath) : undefined; + let matches = searchSymbolMatches(cg, symbol).filter((match) => + !fileFilter || match.node.filePath === fileFilter || match.node.filePath.endsWith(`/${fileFilter}`) + ); + matches = narrowSymbolMatchesByLine(matches, lineFilter); if (matches.length === 0) { - info(`Symbol "${symbol}" not found`); + const location = fileFilter + ? ` in ${fileFilter}${lineFilter ? ` at line ${lineFilter}` : ''}` + : lineFilter ? ` at line ${lineFilter}` : ''; + info(`Symbol "${symbol}" not found${location}`); + cg.destroy(); + return; + } + const repeatedLambdas = fileFilter && !lineFilter ? repeatedCppLambdaMatches(matches) : []; + if (repeatedLambdas.length > 0) { + const definitions = repeatedLambdas.map((match) => ({ + qualifiedName: match.node.qualifiedName, + filePath: match.node.filePath, + startLine: match.node.startLine, + })); + const hint = 'Pass --line to select one local lambda definition'; + if (options.json) console.log(JSON.stringify({ symbol, ambiguous: true, definitionCount: definitions.length, hint, definitions }, null, 2)); + else info(`"${symbol}" has ${definitions.length} local lambda definitions in ${fileFilter}; pass --line to select one: ${definitions.map((definition) => definition.startLine).join(', ')}`); + cg.destroy(); + return; + } + if (!fileFilter && isGloballyAmbiguous(matches)) { + const definitions = matches.slice(0, 50).map((m) => ({ name: m.node.name, kind: m.node.kind, filePath: m.node.filePath, startLine: m.node.startLine })); + if (options.json) console.log(JSON.stringify({ symbol, ambiguous: true, definitionCount: matches.length, hint: 'Pass --file to select one definition', definitions }, null, 2)); + else info(`"${symbol}" has ${matches.length} definitions across the project; pass --file to select one.`); cg.destroy(); return; } const seen = new Set(); - const allCallers: Array<{ name: string; kind: string; filePath: string; startLine?: number }> = []; + const allCallers: Array<{ name: string; kind: string; filePath: string; startLine?: number; callLine?: number }> = []; for (const match of matches) { const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`); @@ -1820,7 +1888,7 @@ program for (const c of cg.getCallers(match.node.id)) { if (!seen.has(c.node.id)) { seen.add(c.node.id); - allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine }); + allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, callLine: c.edge.line ?? undefined }); } } } @@ -1830,7 +1898,7 @@ program for (const c of cg.getCallers(matches[0].node.id)) { if (!seen.has(c.node.id)) { seen.add(c.node.id); - allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine }); + allCallers.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, callLine: c.edge.line ?? undefined }); } } } @@ -1838,13 +1906,13 @@ program const limited = allCallers.slice(0, limit); if (options.json) { - console.log(JSON.stringify({ symbol, callers: limited }, null, 2)); + console.log(JSON.stringify({ symbol, file: fileFilter, line: lineFilter, callers: limited }, null, 2)); } else if (limited.length === 0) { info(`No callers found for "${symbol}"`); } else { console.log(chalk.bold(`\nCallers of "${symbol}" (${limited.length}):\n`)); for (const node of limited) { - const loc = node.startLine ? `:${node.startLine}` : ''; + const loc = node.callLine || node.startLine ? `:${node.callLine ?? node.startLine}` : ''; console.log( chalk.cyan(node.kind.padEnd(12)) + chalk.white(node.name) @@ -1868,9 +1936,11 @@ program .command('callees ') .description('Find all functions/methods that a specific symbol calls') .option('-p, --path ', 'Project path') + .option('--file ', 'Narrow to the definition in this file (path or suffix)') + .option('--line ', 'Narrow to the definition starting at or containing this line') .option('-l, --limit ', 'Maximum results', '20') .option('-j, --json', 'Output as JSON') - .action(async (symbol: string, options: { path?: string; limit?: string; json?: boolean }) => { + .action(async (symbol: string, options: { path?: string; file?: string; line?: string; limit?: string; json?: boolean }) => { const projectPath = resolveProjectPath(options.path); try { @@ -1882,16 +1952,44 @@ program const { default: CodeGraph } = await loadCodeGraph(); const cg = await CodeGraph.open(projectPath); const limit = parseInt(options.limit || '20', 10); + const lineFilter = options.line ? parseInt(options.line, 10) : undefined; - const matches = cg.searchNodes(symbol, { limit: 50 }); + const fileFilter = options.file ? normalizeIndexPath(options.file, projectPath) : undefined; + let matches = searchSymbolMatches(cg, symbol).filter((match) => + !fileFilter || match.node.filePath === fileFilter || match.node.filePath.endsWith(`/${fileFilter}`) + ); + matches = narrowSymbolMatchesByLine(matches, lineFilter); if (matches.length === 0) { - info(`Symbol "${symbol}" not found`); + const location = fileFilter + ? ` in ${fileFilter}${lineFilter ? ` at line ${lineFilter}` : ''}` + : lineFilter ? ` at line ${lineFilter}` : ''; + info(`Symbol "${symbol}" not found${location}`); + cg.destroy(); + return; + } + const repeatedLambdas = fileFilter && !lineFilter ? repeatedCppLambdaMatches(matches) : []; + if (repeatedLambdas.length > 0) { + const definitions = repeatedLambdas.map((match) => ({ + qualifiedName: match.node.qualifiedName, + filePath: match.node.filePath, + startLine: match.node.startLine, + })); + const hint = 'Pass --line to select one local lambda definition'; + if (options.json) console.log(JSON.stringify({ symbol, ambiguous: true, definitionCount: definitions.length, hint, definitions }, null, 2)); + else info(`"${symbol}" has ${definitions.length} local lambda definitions in ${fileFilter}; pass --line to select one: ${definitions.map((definition) => definition.startLine).join(', ')}`); + cg.destroy(); + return; + } + if (!fileFilter && isGloballyAmbiguous(matches)) { + const definitions = matches.slice(0, 50).map((m) => ({ name: m.node.name, kind: m.node.kind, filePath: m.node.filePath, startLine: m.node.startLine })); + if (options.json) console.log(JSON.stringify({ symbol, ambiguous: true, definitionCount: matches.length, hint: 'Pass --file to select one definition', definitions }, null, 2)); + else info(`"${symbol}" has ${matches.length} definitions across the project; pass --file to select one.`); cg.destroy(); return; } const seen = new Set(); - const allCallees: Array<{ name: string; kind: string; filePath: string; startLine?: number }> = []; + const allCallees: Array<{ name: string; kind: string; filePath: string; startLine?: number; callLine?: number }> = []; for (const match of matches) { const exactMatch = match.node.name === symbol || match.node.name.endsWith(`.${symbol}`) || match.node.name.endsWith(`::${symbol}`); @@ -1899,7 +1997,7 @@ program for (const c of cg.getCallees(match.node.id)) { if (!seen.has(c.node.id)) { seen.add(c.node.id); - allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine }); + allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, callLine: c.edge.line ?? undefined }); } } } @@ -1908,7 +2006,7 @@ program for (const c of cg.getCallees(matches[0].node.id)) { if (!seen.has(c.node.id)) { seen.add(c.node.id); - allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine }); + allCallees.push({ name: c.node.name, kind: c.node.kind, filePath: c.node.filePath, startLine: c.node.startLine, callLine: c.edge.line ?? undefined }); } } } @@ -1916,13 +2014,13 @@ program const limited = allCallees.slice(0, limit); if (options.json) { - console.log(JSON.stringify({ symbol, callees: limited }, null, 2)); + console.log(JSON.stringify({ symbol, file: fileFilter, line: lineFilter, callees: limited }, null, 2)); } else if (limited.length === 0) { info(`No callees found for "${symbol}"`); } else { console.log(chalk.bold(`\nCallees of "${symbol}" (${limited.length}):\n`)); for (const node of limited) { - const loc = node.startLine ? `:${node.startLine}` : ''; + const loc = node.callLine || node.startLine ? `:${node.callLine ?? node.startLine}` : ''; console.log( chalk.cyan(node.kind.padEnd(12)) + chalk.white(node.name) @@ -1946,9 +2044,11 @@ program .command('impact ') .description('Analyze what code is affected by changing a symbol') .option('-p, --path ', 'Project path') + .option('--file ', 'Narrow to the definition in this file (path or suffix)') + .option('--line ', 'Narrow to the definition starting at or containing this line') .option('-d, --depth ', 'Traversal depth', '2') .option('-j, --json', 'Output as JSON') - .action(async (symbol: string, options: { path?: string; depth?: string; json?: boolean }) => { + .action(async (symbol: string, options: { path?: string; file?: string; line?: string; depth?: string; json?: boolean }) => { const projectPath = resolveProjectPath(options.path); try { @@ -1960,16 +2060,44 @@ program const { default: CodeGraph } = await loadCodeGraph(); const cg = await CodeGraph.open(projectPath); const depth = Math.min(Math.max(parseInt(options.depth || '2', 10), 1), 10); + const lineFilter = options.line ? parseInt(options.line, 10) : undefined; - const matches = cg.searchNodes(symbol, { limit: 50 }); + const fileFilter = options.file ? normalizeIndexPath(options.file, projectPath) : undefined; + let matches = searchSymbolMatches(cg, symbol).filter((match) => + !fileFilter || match.node.filePath === fileFilter || match.node.filePath.endsWith(`/${fileFilter}`) + ); + matches = narrowSymbolMatchesByLine(matches, lineFilter); if (matches.length === 0) { - info(`Symbol "${symbol}" not found`); + const location = fileFilter + ? ` in ${fileFilter}${lineFilter ? ` at line ${lineFilter}` : ''}` + : lineFilter ? ` at line ${lineFilter}` : ''; + info(`Symbol "${symbol}" not found${location}`); + cg.destroy(); + return; + } + const repeatedLambdas = fileFilter && !lineFilter ? repeatedCppLambdaMatches(matches) : []; + if (repeatedLambdas.length > 0) { + const definitions = repeatedLambdas.map((match) => ({ + qualifiedName: match.node.qualifiedName, + filePath: match.node.filePath, + startLine: match.node.startLine, + })); + const hint = 'Pass --line to select one local lambda definition'; + if (options.json) console.log(JSON.stringify({ symbol, ambiguous: true, definitionCount: definitions.length, hint, definitions }, null, 2)); + else info(`"${symbol}" has ${definitions.length} local lambda definitions in ${fileFilter}; pass --line to select one: ${definitions.map((definition) => definition.startLine).join(', ')}`); + cg.destroy(); + return; + } + if (!fileFilter && isGloballyAmbiguous(matches)) { + const definitions = matches.slice(0, 50).map((m) => ({ name: m.node.name, kind: m.node.kind, filePath: m.node.filePath, startLine: m.node.startLine })); + if (options.json) console.log(JSON.stringify({ symbol, ambiguous: true, definitionCount: matches.length, hint: 'Pass --file to select one definition', definitions }, null, 2)); + else info(`"${symbol}" has ${matches.length} definitions across the project; pass --file to select one.`); cg.destroy(); return; } // Merge impact subgraphs across all exact-matching symbols - const mergedNodes = new Map(); + const mergedNodes = new Map(); const seenEdges = new Set(); let edgeCount = 0; @@ -1978,7 +2106,7 @@ program if (!exactMatch && matches.length > 1) continue; const impact = cg.getImpactRadius(match.node.id, depth); for (const [id, n] of impact.nodes) { - mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine }); + mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine, decorators: n.decorators }); } for (const e of impact.edges) { const key = `${e.source}->${e.target}:${e.kind}`; @@ -1993,17 +2121,23 @@ program if (mergedNodes.size === 0 && matches[0]) { const impact = cg.getImpactRadius(matches[0].node.id, depth); for (const [id, n] of impact.nodes) { - mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine }); + mergedNodes.set(id, { name: n.name, kind: n.kind, filePath: n.filePath, startLine: n.startLine, decorators: n.decorators }); } edgeCount = impact.edges.length; } if (options.json) { + const variantRoots = Array.from(mergedNodes.values()) + .filter((node) => node.decorators?.some((decorator) => decorator.startsWith('pp:'))) + .map((node) => ({ filePath: node.filePath, name: node.name, startLine: node.startLine, conditions: node.decorators?.filter((decorator) => decorator.startsWith('pp:')) })); console.log(JSON.stringify({ symbol, + file: fileFilter, + line: lineFilter, depth, nodeCount: mergedNodes.size, edgeCount, + ...(variantRoots.length > 0 ? { preprocessorVariants: variantRoots } : {}), affected: Array.from(mergedNodes.values()), }, null, 2)); } else if (mergedNodes.size === 0) { @@ -2095,9 +2229,10 @@ program /\.spec\./, /\.test\./, /\/__tests__\//, - /\/tests?\//, + /\/tests?\//i, /\/e2e\//, /\/spec\//, + /(?:^|\/)[^/]*(?:Test|Tests)\.(?:c|cc|cpp|cxx|m|mm|h|hpp|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|hlsl|hlsli|fx|fxh)$/i, ]; // Custom filter pattern @@ -2112,16 +2247,85 @@ program customFilter = new RegExp(regex); } + const vendoredPath = /(?:^|\/)(?:External|vendor|vendors|third[_-]?party|deps)(?:\/|$)/i; + const changedVendoredCode = changedFiles.some((file) => vendoredPath.test(file)); + const shaderPath = /\.(?:glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh)$/i; + const shaderEntryPath = /\.(?:glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|fx)$/i; + + function isShaderTestEntry(filePath: string): boolean { + if (!shaderEntryPath.test(filePath)) return false; + const inTestDirectory = /(?:^|\/)(?:tests?|specs?|__tests__)(?:\/|$)/i.test(filePath); + if (!inTestDirectory) return false; + if (/(?:Test|Tests)\.[^/]+$/i.test(filePath)) return true; + return cg.getNodesInFile(filePath).some((node) => + node.kind === 'function' && (node.decorators?.includes('entrypoint') || node.name === 'main') + ); + } + function isTestFile(filePath: string): boolean { if (customFilter) return customFilter.test(filePath); + // A project-owned change should not return dozens of dependency-suite + // tests merely because an `External/` subtree is indexed. Keep vendored + // tests available when the changed file itself is vendored, and let an + // explicit --filter opt back in for custom workflows. + if (!changedVendoredCode && vendoredPath.test(filePath)) return false; + if (shaderPath.test(filePath)) return isShaderTestEntry(filePath); + if (/(?:^|\/)CMakeLists\.txt$/i.test(filePath)) return false; return defaultTestPatterns.some(p => p.test(filePath)); } + function shaderCompilationRoots(filePath: string): string[] { + const fileNode = cg.getNodesInFile(filePath).find((node) => node.kind === 'file'); + if (!fileNode || (fileNode.language !== 'glsl' && fileNode.language !== 'hlsl')) return []; + const files = new Set([filePath]); + const hasShaderParent = new Set(); + const queue = [filePath]; + for (let index = 0; index < queue.length && index < 4096; index++) { + const current = queue[index]!; + const currentFile = cg.getNodesInFile(current).find((node) => node.kind === 'file'); + if (!currentFile) continue; + for (const edge of cg.getIncomingEdges(currentFile.id)) { + if (edge.kind !== 'imports') continue; + const source = cg.getNode(edge.source); + if (source?.kind !== 'file' || (source.language !== 'glsl' && source.language !== 'hlsl')) continue; + hasShaderParent.add(current); + if (!files.has(source.filePath)) { + files.add(source.filePath); + queue.push(source.filePath); + } + } + } + return [...files].filter((candidate) => !hasShaderParent.has(candidate) && shaderEntryPath.test(candidate)).sort(); + } + // BFS to find all transitive dependents of changed files, filtered to test files const affectedTests = new Set(); const allDependents = new Set(); for (const file of changedFiles) { + if (shaderPath.test(file)) { + const roots = shaderCompilationRoots(file); + if (roots.length > 0) { + for (const root of roots) { + allDependents.add(root); + if (isTestFile(root)) affectedTests.add(root); + const queue: Array<{ file: string; depth: number }> = [{ file: root, depth: 0 }]; + const visited = new Set([root]); + while (queue.length > 0) { + const current = queue.shift()!; + if (current.depth >= maxDepth) continue; + for (const dep of cg.getFileDependents(current.file)) { + if (visited.has(dep) || shaderPath.test(dep)) continue; + visited.add(dep); + allDependents.add(dep); + if (isTestFile(dep)) affectedTests.add(dep); + else queue.push({ file: dep, depth: current.depth + 1 }); + } + } + } + continue; + } + } // If the changed file is itself a test file, include it if (isTestFile(file)) { affectedTests.add(file); diff --git a/src/db/queries.ts b/src/db/queries.ts index 15f261162..84775eb68 100644 --- a/src/db/queries.ts +++ b/src/db/queries.ts @@ -1742,7 +1742,11 @@ export class QueryBuilder { const row = this.db .prepare('SELECT MAX(indexed_at) AS last FROM files') .get() as { last: number | null } | undefined; - return row?.last ?? null; + const fileLast = row?.last ?? null; + const operationRaw = this.getMetadata('last_indexed_at'); + const operationLast = operationRaw == null ? NaN : Number(operationRaw); + if (!Number.isFinite(operationLast) || operationLast <= 0) return fileLast; + return fileLast == null ? operationLast : Math.max(fileLast, operationLast); } /** @@ -1866,6 +1870,28 @@ export class QueryBuilder { return row.count; } + /** Attempted references that could not be resolved, summarized for status. */ + getFailedReferenceStats(): { + total: number; + byLanguage: Record; + byKind: Record; + } { + const total = (this.db.prepare( + "SELECT COUNT(*) AS count FROM unresolved_refs WHERE status = 'failed'" + ).get() as { count: number }).count; + const languageRows = this.db.prepare( + "SELECT language, COUNT(*) AS count FROM unresolved_refs WHERE status = 'failed' GROUP BY language ORDER BY count DESC" + ).all() as Array<{ language: string; count: number }>; + const kindRows = this.db.prepare( + "SELECT reference_kind AS kind, COUNT(*) AS count FROM unresolved_refs WHERE status = 'failed' GROUP BY reference_kind ORDER BY count DESC" + ).all() as Array<{ kind: string; count: number }>; + return { + total, + byLanguage: Object.fromEntries(languageRows.map((row) => [row.language, row.count])), + byKind: Object.fromEntries(kindRows.map((row) => [row.kind, row.count])), + }; + } + /** * Get a batch of PENDING unresolved references using LIMIT/OFFSET * pagination. Used to process references in bounded memory chunks; failed diff --git a/src/extraction/build-script-extractors.ts b/src/extraction/build-script-extractors.ts new file mode 100644 index 000000000..68bf8fe31 --- /dev/null +++ b/src/extraction/build-script-extractors.ts @@ -0,0 +1,245 @@ +import * as path from 'path'; +import type { ExtractionResult, Language, Node, NodeKind, UnresolvedReference } from '../types'; +import { extractPowershellDependencies } from '../powershell-dependencies'; +import { generateNodeId } from './tree-sitter-helpers'; + +const SHADER_PATH_RE = /\b[^\s"']+\.(?:glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|hlsl|hlsli|fx|fxh|spv)\b/gi; + +function lineOf(source: string, index: number): number { + return source.slice(0, index).split(/\r?\n/).length; +} + +function findMatchingBrace(source: string, open: number): number { + let depth = 0; + let quote: "'" | '"' | null = null; + let lineComment = false; + let blockComment = false; + for (let i = open; i < source.length; i++) { + const ch = source[i]!; + const next = source[i + 1]; + if (lineComment) { + if (ch === '\n') lineComment = false; + continue; + } + if (blockComment) { + if (ch === '#' && next === '>') { blockComment = false; i++; } + continue; + } + if (quote) { + if (quote === '"' && ch === '`') { i++; continue; } + if (ch === quote) { + if (quote === "'" && next === "'") { i++; continue; } + quote = null; + } + continue; + } + if (ch === '<' && next === '#') { blockComment = true; i++; continue; } + if (ch === '#') { lineComment = true; continue; } + if (ch === "'" || ch === '"') { quote = ch; continue; } + if (ch === '{') depth++; + else if (ch === '}' && --depth === 0) return i; + } + return source.length; +} + +function fileNode(filePath: string, source: string, language: Language): Node { + const lines = source.split(/\r?\n/); + return { + id: generateNodeId(filePath, 'file', filePath, 1), + kind: 'file', + name: path.basename(filePath), + qualifiedName: filePath, + filePath, + language, + startLine: 1, + endLine: lines.length, + startColumn: 0, + endColumn: lines.at(-1)?.length ?? 0, + updatedAt: Date.now(), + }; +} + +function createNode(filePath: string, language: Language, kind: NodeKind, name: string, line: number, signature?: string): Node { + return { + id: generateNodeId(filePath, kind, name, line), + kind, + name, + qualifiedName: name, + filePath, + language, + signature, + startLine: line, + endLine: line, + startColumn: 0, + endColumn: signature?.length ?? name.length, + updatedAt: Date.now(), + }; +} + +function addShaderReferences(filePath: string, language: Language, source: string, fromNodeId: string, refs: UnresolvedReference[]): void { + for (const match of source.matchAll(SHADER_PATH_RE)) { + const name = match[0]; + refs.push({ + fromNodeId, + referenceName: name, + referenceKind: 'references', + line: lineOf(source, match.index ?? 0), + column: 0, + filePath, + language, + }); + } +} + +export function extractCmake(filePath: string, source: string): ExtractionResult { + const language: Language = 'cmake'; + const file = fileNode(filePath, source, language); + const nodes: Node[] = [file]; + const edges: ExtractionResult['edges'] = []; + const unresolvedReferences: UnresolvedReference[] = []; + const add = (kind: NodeKind, name: string, line: number, signature?: string) => { + const n = createNode(filePath, language, kind, name, line, signature); + nodes.push(n); + edges.push({ source: file.id, target: n.id, kind: 'contains' as const }); + return n; + }; + + for (const match of source.matchAll(/\b(function|macro)\s*\(\s*([A-Za-z_]\w*)/gi)) { + add('function', match[2]!, lineOf(source, match.index ?? 0), match[0]); + } + for (const match of source.matchAll(/\b(?:set|option|set_property)\s*\(\s*([A-Za-z_]\w*)/gi)) { + add('variable', match[1]!, lineOf(source, match.index ?? 0), match[0]); + } + addShaderReferences(filePath, language, source, file.id, unresolvedReferences); + for (const match of source.matchAll(/\b(?:include|add_subdirectory|find_package)\s*\(\s*["']?([^\s"')]+)/gi)) { + unresolvedReferences.push({ + fromNodeId: file.id, + referenceName: match[1]!, + referenceKind: 'imports', + line: lineOf(source, match.index ?? 0), + column: 0, + filePath, + language, + }); + } + return { nodes, edges, unresolvedReferences, errors: [], durationMs: 0 }; +} + +export function extractPowershell(filePath: string, source: string): ExtractionResult { + const language: Language = 'powershell'; + const file = fileNode(filePath, source, language); + const nodes: Node[] = [file]; + const edges: ExtractionResult['edges'] = []; + const unresolvedReferences: UnresolvedReference[] = []; + const importedScripts = new Set(); + for (const dependency of extractPowershellDependencies(source)) { + const dependencyPath = path.posix.normalize(path.posix.join( + path.posix.dirname(filePath.replace(/\\/g, '/')), + dependency.relativePath.replace(/\\/g, '/'), + )); + if (dependencyPath.startsWith('../') || importedScripts.has(dependencyPath.toLowerCase())) continue; + importedScripts.add(dependencyPath.toLowerCase()); + const line = lineOf(source, dependency.index); + const imported = createNode(filePath, language, 'import', dependency.kind === 'module' ? 'Import-Module' : 'dot-source', line, dependency.signature); + nodes.push(imported); + edges.push({ source: file.id, target: imported.id, kind: 'contains' as const }); + unresolvedReferences.push({ + fromNodeId: file.id, + referenceName: dependencyPath, + referenceKind: 'imports', + line, + column: 0, + filePath, + language, + }); + } + const functionNodes = new Map(); + const functionRanges = new Map(); + for (const match of source.matchAll(/^[ \t]*function\s+([A-Za-z_]\w*(?:-[A-Za-z_]\w*)*)/gim)) { + const start = match.index ?? 0; + const name = match[1]!; + const open = source.indexOf('{', start + match[0].length); + const close = open >= 0 ? findMatchingBrace(source, open) : start + match[0].length; + const n = createNode(filePath, language, 'function', name, lineOf(source, start + match[0].indexOf('function')), match[0].trim()); + n.endLine = lineOf(source, close); + n.endColumn = close - source.lastIndexOf('\n', close) - 1; + nodes.push(n); + edges.push({ source: file.id, target: n.id, kind: 'contains' as const }); + functionNodes.set(name.toLowerCase(), n); + functionRanges.set(n.id, { start: open, end: close }); + } + const variables = new Set(); + for (const match of source.matchAll(/(?:^|[;\r\n])[ \t]*\$([A-Za-z_]\w*)\s*=/g)) { + const name = match[1]!; + const tokenOffset = match[0].lastIndexOf(`$${name}`); + const absolute = (match.index ?? 0) + Math.max(0, tokenOffset); + if ([...functionRanges.values()].some((range) => absolute >= range.start && absolute <= range.end)) continue; + if (variables.has(name.toLowerCase())) continue; + variables.add(name.toLowerCase()); + const n = createNode(filePath, language, 'variable', name, lineOf(source, absolute), match[0].trim()); + nodes.push(n); + edges.push({ source: file.id, target: n.id, kind: 'contains' as const }); + } + // Resolve direct PowerShell command invocations against functions declared in + // the same file. The resolver then extends this to same-language definitions + // in included/project scripts without treating every external cmdlet as user code. + for (const fn of functionNodes.values()) { + const range = functionRanges.get(fn.id); + const bodyStart = range?.start ?? -1; + const bodyEnd = range?.end ?? source.length; + const body = source.slice(bodyStart + 1, bodyEnd); + for (const call of body.matchAll(/(? absolute >= range.start && absolute <= range.end)) continue; + const lineStart = source.lastIndexOf('\n', absolute) + 1; + if (/^[ \t]*function\s+/i.test(source.slice(lineStart, absolute))) continue; + const lineEnd = source.indexOf('\n', absolute); + const lineText = source.slice(lineStart, lineEnd < 0 ? source.length : lineEnd); + if (/^[ \t]*Export-ModuleMember\b/i.test(lineText)) continue; + unresolvedReferences.push({ + fromNodeId: file.id, + referenceName: call[1]!, + referenceKind: 'calls', + line: lineOf(source, absolute), + column: 0, + filePath, + language, + }); + } + addShaderReferences(filePath, language, source, file.id, unresolvedReferences); + for (const match of source.matchAll(/(?:&|Start-Process|Invoke-Expression)\s+["']([^"']+)["']/gi)) { + const invokedPath = match[1]!; + if (invokedPath.includes('$') || /\.(?:exe|cmd|bat|com)$/i.test(invokedPath)) continue; + if (!/\.(?:ps1|psm1)$/i.test(invokedPath)) continue; + unresolvedReferences.push({ + fromNodeId: file.id, + referenceName: invokedPath, + referenceKind: 'references', + line: lineOf(source, match.index ?? 0), + column: 0, + filePath, + language, + }); + } + return { nodes, edges, unresolvedReferences, errors: [], durationMs: 0 }; +} diff --git a/src/extraction/extraction-version.ts b/src/extraction/extraction-version.ts index 618a1b1c3..754afe274 100644 --- a/src/extraction/extraction-version.ts +++ b/src/extraction/extraction-version.ts @@ -21,4 +21,4 @@ * turns the re-index hint into noise — keep it honest (see CLAUDE.md, "Honesty * in the product is load-bearing"). */ -export const EXTRACTION_VERSION = 24; +export const EXTRACTION_VERSION = 52; diff --git a/src/extraction/glslfx-extractor.ts b/src/extraction/glslfx-extractor.ts new file mode 100644 index 000000000..91f217d25 --- /dev/null +++ b/src/extraction/glslfx-extractor.ts @@ -0,0 +1,257 @@ +import { Edge, ExtractionError, ExtractionResult, Node, UnresolvedReference } from '../types'; +import { generateNodeId } from './tree-sitter-helpers'; +import { TreeSitterExtractor } from './tree-sitter'; + +interface Section { + name: string; + content: string; + startLine: number; + markerLine: number; +} + +export class GlslfxExtractor { + private readonly nodes: Node[] = []; + private readonly edges: Edge[] = []; + private readonly unresolvedReferences: UnresolvedReference[] = []; + private readonly errors: ExtractionError[] = []; + private readonly fileNode: Node; + + constructor(private readonly filePath: string, private readonly source: string) { + const lines = source.split('\n'); + this.fileNode = { + id: `file:${filePath}`, + kind: 'file', + name: filePath.split(/[/\\]/).pop() || filePath, + qualifiedName: filePath, + filePath, + language: 'glsl', + startLine: 1, + endLine: lines.length, + startColumn: 0, + endColumn: lines[lines.length - 1]?.length || 0, + updatedAt: Date.now(), + }; + } + + extract(): ExtractionResult { + const started = Date.now(); + this.extractVersion(); + this.nodes.push(this.fileNode); + try { + this.extractImports(); + const sections = this.sections(); + const sectionNodes = new Map(); + for (const section of sections) { + const node = this.extractSection(section); + sectionNodes.set(section.name, [...(sectionNodes.get(section.name) ?? []), node]); + } + this.extractConfiguration(sectionNodes); + } catch (error) { + this.errors.push({ + message: `GLSLFX extraction error: ${error instanceof Error ? error.message : String(error)}`, + filePath: this.filePath, + severity: 'error', + code: 'parse_error', + }); + } + return { + nodes: this.nodes, + edges: this.edges, + unresolvedReferences: this.unresolvedReferences, + errors: this.errors, + durationMs: Date.now() - started, + }; + } + + private extractVersion(): void { + const match = this.source.match(/^--\s+glslfx\s+version\s+([^\s]+)\s*$/m); + if (!match) return; + const version = match[1]!; + this.fileNode.decorators = [`glslfx:${version}`]; + this.fileNode.signature = match[0]; + if (version !== '0.1') { + this.errors.push({ + message: `Unsupported GLSLFX version ${version}; parsing as the 0.1 container dialect`, + filePath: this.filePath, + line: this.lineAt(match.index ?? 0), + severity: 'warning', + code: 'glslfx_version', + }); + } + } + + private sections(): Section[] { + const markers = [...this.source.matchAll(/^--\s+glsl\s+([^\r\n]+?)\s*$/gm)]; + return markers.map((marker, index) => { + const contentStart = (marker.index ?? 0) + marker[0].length; + const next = markers[index + 1]?.index ?? this.source.length; + const content = this.source.slice(contentStart, next).replace(/^\r?\n/, ''); + const markerLine = this.lineAt(marker.index ?? 0); + return { name: marker[1]!.trim(), content, markerLine, startLine: markerLine + 1 }; + }); + } + + private extractSection(section: Section): Node { + const sectionId = generateNodeId(this.filePath, 'module', section.name, section.markerLine); + const sectionNode: Node = { + id: sectionId, + kind: 'module', + name: section.name, + qualifiedName: `${this.filePath}::${section.name}`, + filePath: this.filePath, + language: 'glsl', + signature: `-- glsl ${section.name}`, + decorators: ['glslfx:section'], + startLine: section.markerLine, + endLine: section.startLine + section.content.split('\n').length - 1, + startColumn: 0, + endColumn: 0, + updatedAt: Date.now(), + }; + this.nodes.push(sectionNode); + this.edges.push({ source: this.fileNode.id, target: sectionId, kind: 'contains' }); + + const embedded = new TreeSitterExtractor(`${this.filePath}#${section.name}.glsl`, section.content, 'glsl').extract(); + const idMap = new Map(); + const embeddedFileId = `file:${this.filePath}#${section.name}.glsl`; + idMap.set(embeddedFileId, sectionId); + for (const node of embedded.nodes) { + if (node.kind === 'file') continue; + const oldId = node.id; + node.startLine += section.startLine - 1; + node.endLine += section.startLine - 1; + node.filePath = this.filePath; + node.language = 'glsl'; + node.qualifiedName = `${section.name}::${node.qualifiedName}`; + node.id = generateNodeId(this.filePath, node.kind, `${section.name}::${node.name}`, node.startLine); + idMap.set(oldId, node.id); + this.nodes.push(node); + } + for (const edge of embedded.edges) { + const source = idMap.get(edge.source); + const target = idMap.get(edge.target); + if (!source || !target) continue; + this.edges.push({ ...edge, source, target, line: edge.line ? edge.line + section.startLine - 1 : undefined }); + } + for (const ref of embedded.unresolvedReferences) { + const fromNodeId = idMap.get(ref.fromNodeId); + if (!fromNodeId) continue; + this.unresolvedReferences.push({ + ...ref, + fromNodeId, + filePath: this.filePath, + language: 'glsl', + line: ref.line + section.startLine - 1, + }); + } + for (const error of embedded.errors) { + this.errors.push({ ...error, filePath: this.filePath, line: error.line ? error.line + section.startLine - 1 : undefined }); + } + return sectionNode; + } + + private extractImports(): void { + for (const match of this.source.matchAll(/^\s*#import\s+[<"]?([^>"\r\n]+)[>"]?\s*$/gm)) { + const moduleName = match[1]!.trim(); + const line = this.lineAt(match.index ?? 0); + const id = generateNodeId(this.filePath, 'import', moduleName, line); + this.nodes.push({ + id, + kind: 'import', + name: moduleName, + qualifiedName: `${this.filePath}::import:${moduleName}`, + filePath: this.filePath, + language: 'glsl', + signature: match[0].trim(), + startLine: line, + endLine: line, + startColumn: 0, + endColumn: match[0].length, + updatedAt: Date.now(), + }); + this.edges.push({ source: this.fileNode.id, target: id, kind: 'contains' }); + this.unresolvedReferences.push({ + fromNodeId: this.fileNode.id, + referenceName: moduleName, + referenceKind: 'imports', + filePath: this.filePath, + language: 'glsl', + line, + column: 0, + }); + } + } + + private extractConfiguration(sectionNodes: Map): void { + const marker = /^--\s+configuration\s*$/gm.exec(this.source); + if (!marker) return; + const start = marker.index + marker[0].length; + const tail = this.source.slice(start); + const nextMarker = /^--\s+/m.exec(tail); + const raw = tail.slice(0, nextMarker?.index ?? tail.length).trim(); + if (!raw) return; + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + this.errors.push({ + message: 'GLSLFX configuration block is not valid JSON', + filePath: this.filePath, + line: this.lineAt(marker.index), + severity: 'warning', + code: 'glslfx_config', + }); + return; + } + const techniques = (parsed as { techniques?: Record }).techniques; + if (!techniques || typeof techniques !== 'object') return; + for (const [name, value] of Object.entries(techniques)) { + const line = this.lineAt(marker.index); + const id = generateNodeId(this.filePath, 'component', name, line); + const technique: Node = { + id, + kind: 'component', + name, + qualifiedName: `${this.filePath}::technique:${name}`, + filePath: this.filePath, + language: 'glsl', + signature: JSON.stringify(value).slice(0, 400), + decorators: ['glslfx:technique'], + startLine: line, + endLine: line, + startColumn: 0, + endColumn: 0, + updatedAt: Date.now(), + }; + this.nodes.push(technique); + this.edges.push({ source: this.fileNode.id, target: id, kind: 'contains' }); + const strings: string[] = []; + const walk = (item: unknown): void => { + if (typeof item === 'string') strings.push(item); + else if (Array.isArray(item)) item.forEach(walk); + else if (item && typeof item === 'object') Object.values(item).forEach(walk); + }; + walk(value); + for (const sectionName of new Set(strings)) { + const sections = sectionNodes.get(sectionName) ?? []; + if (sections.length === 1) { + this.edges.push({ source: id, target: sections[0]!.id, kind: 'references' }); + } else if (/\.(?:glslfx|glsl|hlsl|hlsli)$/i.test(sectionName)) { + this.unresolvedReferences.push({ + fromNodeId: id, + referenceName: sectionName, + referenceKind: 'imports', + filePath: this.filePath, + language: 'glsl', + line, + column: 0, + }); + } + } + } + } + + private lineAt(index: number): number { + return (this.source.slice(0, index).match(/\n/g) || []).length + 1; + } +} diff --git a/src/extraction/grammars.ts b/src/extraction/grammars.ts index a26d232ae..3a8fe43ef 100644 --- a/src/extraction/grammars.ts +++ b/src/extraction/grammars.ts @@ -11,7 +11,7 @@ import * as fsp from 'fs/promises'; import { Parser, Language as WasmLanguage } from 'web-tree-sitter'; import { Language } from '../types'; -export type GrammarLanguage = Exclude; +export type GrammarLanguage = Exclude; /** * WASM filename map — maps each language to its .wasm grammar file @@ -50,6 +50,8 @@ const WASM_GRAMMAR_FILES: Record = { terraform: 'tree-sitter-terraform.wasm', arkts: 'tree-sitter-arkts.wasm', nix: 'tree-sitter-nix.wasm', + glsl: 'tree-sitter-glsl.wasm', + hlsl: 'tree-sitter-hlsl.wasm', }; /** @@ -170,6 +172,32 @@ export const EXTENSION_MAP: Record = { '.tf': 'terraform', '.tfvars': 'terraform', '.tofu': 'terraform', + // OpenGL/Vulkan shading languages, including Vulkan ray tracing and mesh stages. + '.glsl': 'glsl', + '.vert': 'glsl', + '.frag': 'glsl', + '.comp': 'glsl', + '.geom': 'glsl', + '.tesc': 'glsl', + '.tese': 'glsl', + '.rgen': 'glsl', + '.rmiss': 'glsl', + '.rchit': 'glsl', + '.rahit': 'glsl', + '.rint': 'glsl', + '.rcall': 'glsl', + '.mesh': 'glsl', + '.task': 'glsl', + // OpenUSD GLSLFX is a container around named GLSL sections. + '.glslfx': 'glsl', + '.hlsl': 'hlsl', + '.hlsli': 'hlsl', + '.fx': 'hlsl', + '.fxh': 'hlsl', + '.cmake': 'cmake', + '.ps1': 'powershell', + '.psm1': 'powershell', + '.psd1': 'powershell', }; /** @@ -182,6 +210,7 @@ export const EXTENSION_MAP: Record = { * to the built-ins. Omitting it is byte-identical to the zero-config behavior. */ export function isSourceFile(filePath: string, overrides?: Record): boolean { + if (/(^|[\\/])CMakeLists\.txt$/i.test(filePath)) return true; if (isPlayRoutesFile(filePath)) return true; // Play `conf/routes` is extensionless if (isShopifyLiquidJson(filePath)) return true; // Shopify OS 2.0 JSON templates / section groups if (isErlangAppFile(filePath)) return true; // OTP `.app`/`.app.src` resource files @@ -274,7 +303,7 @@ export async function initGrammars(): Promise { */ const VENDORED_WASM_LANGS: ReadonlySet = new Set([ 'pascal', 'scala', 'lua', 'luau', 'csharp', 'r', 'cfml', 'cfscript', 'cfquery', - 'cobol', 'vbnet', 'erlang', 'terraform', 'arkts', 'nix', + 'cobol', 'vbnet', 'erlang', 'terraform', 'arkts', 'nix', 'glsl', 'hlsl', ]); /** Absolute path of a language's grammar WASM (vendored or tree-sitter-wasms). */ @@ -412,6 +441,7 @@ export function getParser(language: Language): Parser | null { * `EXTENSION_MAP`. Omitting it is byte-identical to the zero-config behavior. */ export function detectLanguage(filePath: string, source?: string, overrides?: Record): Language { + if (/(^|[\\/])CMakeLists\.txt$/i.test(filePath)) return 'cmake'; // Play `conf/routes` has no grammar — route through the no-symbol path; the // Play framework resolver extracts route nodes from it. if (isPlayRoutesFile(filePath)) return 'yaml'; @@ -481,7 +511,7 @@ export function isLanguageSupported(language: Language): boolean { * Check if a grammar has been loaded and is ready for parsing. */ export function isGrammarLoaded(language: Language): boolean { - if (language === 'svelte' || language === 'vue' || language === 'astro' || language === 'liquid' || language === 'razor') return true; + if (language === 'svelte' || language === 'vue' || language === 'astro' || language === 'liquid' || language === 'razor' || language === 'cmake' || language === 'powershell') return true; if (language === 'yaml' || language === 'twig') return true; // no WASM grammar needed if (language === 'xml' || language === 'properties') return true; // no WASM grammar needed return languageCache.has(language); @@ -504,7 +534,7 @@ export function isFileLevelOnlyLanguage(language: Language): boolean { * Get all supported languages (those with grammar definitions). */ export function getSupportedLanguages(): Language[] { - return [...(Object.keys(WASM_GRAMMAR_FILES) as GrammarLanguage[]), 'svelte', 'vue', 'astro', 'liquid']; + return [...(Object.keys(WASM_GRAMMAR_FILES) as GrammarLanguage[]), 'svelte', 'vue', 'astro', 'liquid', 'cmake', 'powershell']; } /** @@ -592,6 +622,10 @@ export function getLanguageDisplayName(language: Language): string { erlang: 'Erlang', terraform: 'Terraform', arkts: 'ArkTS', + glsl: 'GLSL / Vulkan GLSL', + hlsl: 'HLSL', + cmake: 'CMake', + powershell: 'PowerShell', unknown: 'Unknown', }; return names[language] || language; diff --git a/src/extraction/languages/glsl.ts b/src/extraction/languages/glsl.ts new file mode 100644 index 000000000..a77ecdf6d --- /dev/null +++ b/src/extraction/languages/glsl.ts @@ -0,0 +1,264 @@ +import type { Node as SyntaxNode } from 'web-tree-sitter'; +import type { LanguageExtractor } from '../tree-sitter-types'; +import { getChildByField, getNodeText, getPrecedingDocstring } from '../tree-sitter-helpers'; +import { + isShaderBuiltinCall, + previousShaderPrefix, + shaderDeclarationNames, + shaderDecorators, + shaderFunctionName, + shaderImport, + shaderMacroName, + recoverShaderMacroBodyReferences, + recoverShaderMacroDeclarations, + shaderSignature, +} from './shader-common'; + +function createFields(node: SyntaxNode, ctx: Parameters>[1]): void { + for (const field of node.descendantsOfType('field_declaration')) { + for (const item of shaderDeclarationNames(field, ctx.source)) { + ctx.createNode('field', item.name, item.node, { + signature: shaderSignature(field, ctx.source), + decorators: shaderDecorators(field, ctx.source, ctx.filePath, 'glsl'), + }); + } + } +} + +const recoveredSiblingKeys = new Set(); + +function recoveryKey(node: SyntaxNode): string { + return `${node.type}:${node.startIndex}:${node.endIndex}`; +} + +function isOrphanFunctionNode(node: SyntaxNode, source: string): boolean { + if (node.type === 'function_declarator') { + return node.parent?.type !== 'function_definition' && node.parent?.type !== 'function_declaration'; + } + return node.type === 'ERROR' && + node.namedChild(0)?.type === 'declarator' && + !!shaderFunctionName(node, source); +} + +function containsOrphanFunction(node: SyntaxNode, source: string): boolean { + return node.descendantsOfType(['function_declarator', 'ERROR']).some((candidate) => + isOrphanFunctionNode(candidate, source) + ); +} + +function functionSignature(node: SyntaxNode, source: string): string | undefined { + const declarator = node.type === 'function_declarator' + ? node + : node.namedChildren.find((child) => child.type === 'function_declarator'); + const params = declarator ? getChildByField(declarator, 'parameters') : getChildByField(node, 'parameters'); + const ret = declarator ? getChildByField(declarator, 'return_type') : getChildByField(node, 'return_type'); + const name = shaderFunctionName(node, source); + if (!name) return undefined; + const parameterText = params ? getNodeText(params, source).trim() : ''; + const parameterList = parameterText.startsWith('(') ? parameterText : `(${parameterText})`; + return `${ret ? getNodeText(ret, source).trim() + ' ' : ''}${name}${parameterList}`.trim(); +} + +function recoveredFunctionSignature(node: SyntaxNode, source: string): string | undefined { + const name = shaderFunctionName(node, source); + if (!name) return undefined; + const text = source.slice(node.startIndex, node.endIndex); + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const match = text.match(new RegExp(`(?:^|[;{}\\n])\\s*(.*?)\\b${escaped}\\s*\\(([\\s\\S]*?)\\)`, 'm')); + let returnType = match?.[1] ?? ''; + let parameters = match?.[2] ?? ''; + if (!match) { + // ERROR nodes can end before the declarator's closing parenthesis. Fall + // back to the source span around the recovered name so the signature is + // still useful even when the grammar's node boundaries are incomplete. + const nameIndex = source.indexOf(`${name}(`, node.startIndex); + const open = nameIndex >= 0 ? source.indexOf('(', nameIndex + name.length) : -1; + const close = open >= 0 ? source.indexOf(')', open + 1) : -1; + if (nameIndex < 0 || open < 0 || close < 0) return undefined; + const before = source.slice(Math.max(0, nameIndex - 120), nameIndex); + returnType = before.match(/(?:^|[;{}])\s*([A-Za-z_]\w*(?:\s+[A-Za-z_]\w*)*)\s*$/)?.[1] ?? ''; + parameters = source.slice(open + 1, close); + } + returnType = returnType.replace(/[{};]/g, ' ').replace(/\s+/g, ' ').trim(); + parameters = parameters.replace(/\s+/g, ' ').trim(); + return `${returnType ? `${returnType} ` : ''}${name}(${parameters})`.trim(); +} + +/** + * The GLSL grammar can detach a valid function declarator from its body when + * error recovery spans preprocessor-heavy code. Recover only the narrow AST + * shape it emits: an orphan declarator immediately followed by a statement list. + */ +function recoverOrphanFunction( + node: SyntaxNode, + ctx: Parameters>[1], +): boolean { + if (node.type !== 'function_declarator' && node.type !== 'ERROR') return false; + if (node.type === 'function_declarator' && + (node.parent?.type === 'function_definition' || node.parent?.type === 'function_declaration')) return false; + if (node.type === 'ERROR' && node.namedChild(0)?.type !== 'declarator') return false; + const parts: SyntaxNode[] = []; + const directBody = node.nextNamedSibling; + if (directBody && (directBody.type === 'statement_list' || directBody.type === 'compound_statement')) { + parts.push(directBody); + } else { + const parent = node.parent; + if (!parent) return false; + let after = false; + for (let i = 0; i < parent.namedChildCount; i++) { + const child = parent.namedChild(i); + if (!child) continue; + if (child.equals(node)) { + after = true; + continue; + } + if (!after) continue; + if ( + child.type === 'function_definition' || + child.type === 'function_declaration' || + child.type === 'function_declarator' || + (child.type === 'ERROR' && child.namedChild(0)?.type === 'declarator') + ) break; + if (child.type === 'comment' && /^#\s*endif\b/.test(getNodeText(child, ctx.source).trim())) break; + parts.push(child); + } + } + if (parts.length === 0) return false; + const name = shaderFunctionName(node, ctx.source); + if (!name) return false; + const lastPart = parts[parts.length - 1]!; + const created = ctx.createNode('function', name, node, { + docstring: getPrecedingDocstring(node, ctx.source), + signature: node.type === 'ERROR' ? recoveredFunctionSignature(node, ctx.source) : functionSignature(node, ctx.source), + endLine: lastPart.endPosition.row + 1, + endColumn: lastPart.endPosition.column, + }); + if (created) { + ctx.pushScope(created.id); + for (const part of parts) { + recoveredSiblingKeys.add(recoveryKey(part)); + visitRecoveredRegion(part, ctx, created.id); + } + ctx.popScope(); + } + return true; +} + +function visitRecoveredRegion( + node: SyntaxNode, + ctx: Parameters>[1], + ownerId: string, +): void { + if (!containsOrphanFunction(node, ctx.source)) { + ctx.visitFunctionBody(node, ownerId); + return; + } + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child || recoveredSiblingKeys.delete(recoveryKey(child))) continue; + if (isOrphanFunctionNode(child, ctx.source)) { + recoverOrphanFunction(child, ctx); + } else if (containsOrphanFunction(child, ctx.source)) { + visitRecoveredRegion(child, ctx, ownerId); + } else { + ctx.visitFunctionBody(child, ownerId); + } + } +} + +export const glslExtractor: LanguageExtractor = { + functionTypes: ['function_definition', 'function_declaration'], + classTypes: [], + methodTypes: [], + interfaceTypes: [], + structTypes: [], + enumTypes: [], + typeAliasTypes: [], + importTypes: ['preproc_call'], + callTypes: ['function_call', 'macro_invocation'], + variableTypes: [], + fieldTypes: [], + nameField: 'name', + bodyField: 'body', + resolveBody: (node) => node.namedChildren.find((child) => child.type === 'compound_statement') ?? null, + paramsField: 'parameters', + returnField: 'return_type', + resolveName: shaderFunctionName, + getSignature: functionSignature, + extractDecorators: (node, source, filePath) => { + const decorators = shaderDecorators(node, source, filePath, 'glsl') ?? []; + if (node.type.startsWith('function_') && shaderFunctionName(node, source) === 'main') decorators.push('entrypoint'); + return [...new Set(decorators)]; + }, + extractImport: shaderImport, + isBuiltinCall: isShaderBuiltinCall, + visitNode: (node, ctx) => { + if (recoveredSiblingKeys.delete(recoveryKey(node))) return true; + if (node.type === 'translation_unit') recoverShaderMacroDeclarations(node, ctx, 'glsl'); + if (recoverOrphanFunction(node, ctx)) return true; + + if (node.type === 'struct_specifier') { + const nameNode = getChildByField(node, 'name') || node.namedChildren.find((c) => c.type === 'identifier'); + const fieldList = node.namedChildren.find((c) => c.type === 'field_declaration_list'); + if (!nameNode || !fieldList) return false; + const struct = ctx.createNode('struct', getNodeText(nameNode, ctx.source), node, { + signature: shaderSignature(node, ctx.source), + decorators: shaderDecorators(node, ctx.source, ctx.filePath, 'glsl'), + }); + if (struct) { + ctx.pushScope(struct.id); + createFields(fieldList, ctx); + ctx.popScope(); + } + return true; + } + + if (node.type === 'preproc_def' || node.type === 'preproc_function_def') { + const name = shaderMacroName(node, ctx.source); + if (!name) return false; + const kind = node.type === 'preproc_function_def' ? 'function' : 'constant'; + const created = ctx.createNode(kind, name, node, { + signature: shaderSignature(node, ctx.source), + decorators: shaderDecorators(node, ctx.source, ctx.filePath, 'glsl'), + }); + if (created && kind === 'function') { + recoverShaderMacroBodyReferences(node, created.id, ctx); + } + return true; + } + + if (node.type === 'field_declaration') { + createFields(node, ctx); + return true; + } + + if (node.type !== 'declaration') return false; + if (node.descendantsOfType('struct_specifier').length > 0) return false; + + const fieldList = node.namedChildren.find((c) => c.type === 'field_declaration_list'); + if (fieldList) { + const nameNode = getChildByField(node, 'name') || node.namedChildren.find((c) => c.type === 'identifier'); + if (!nameNode) return false; + const block = ctx.createNode('struct', getNodeText(nameNode, ctx.source), node, { + signature: shaderSignature(node, ctx.source), + decorators: shaderDecorators(node, ctx.source, ctx.filePath, 'glsl', previousShaderPrefix(node, ctx.source)), + }); + if (block) { + ctx.pushScope(block.id); + createFields(fieldList, ctx); + ctx.popScope(); + } + return true; + } + + const decorators = shaderDecorators(node, ctx.source, ctx.filePath, 'glsl', previousShaderPrefix(node, ctx.source)); + const isConst = /\bconst\b/.test(getNodeText(node, ctx.source)); + for (const item of shaderDeclarationNames(node, ctx.source)) { + ctx.createNode(isConst ? 'constant' : 'variable', item.name, item.node, { + signature: shaderSignature(node, ctx.source), + decorators, + }); + } + return true; + }, +}; diff --git a/src/extraction/languages/hlsl.ts b/src/extraction/languages/hlsl.ts new file mode 100644 index 000000000..7b7d62592 --- /dev/null +++ b/src/extraction/languages/hlsl.ts @@ -0,0 +1,198 @@ +import type { Node as SyntaxNode } from 'web-tree-sitter'; +import type { LanguageExtractor } from '../tree-sitter-types'; +import { getChildByField, getNodeText } from '../tree-sitter-helpers'; +import { + isShaderBuiltinCall, + previousShaderPrefix, + recoverShaderMacroBodyReferences, + recoverShaderMacroDeclarations, + shaderDeclarationNames, + shaderDecorators, + shaderFunctionName, + shaderImport, + shaderMacroName, + shaderSignature, + splitMacroArgs, +} from './shader-common'; + +type ShaderCtx = Parameters>[1]; + +function declarationType(node: SyntaxNode, source: string): string { + const type = getChildByField(node, 'type') || node.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'primitive_type' || c.type === 'template_type'); + return type ? getNodeText(type, source).trim() : ''; +} + +function createBufferFields(body: SyntaxNode, ctx: ShaderCtx): void { + for (const declaration of body.namedChildren.filter((c) => c.type === 'declaration')) { + for (const item of shaderDeclarationNames(declaration, ctx.source)) { + ctx.createNode('field', item.name, item.node, { + signature: shaderSignature(declaration, ctx.source), + decorators: shaderDecorators(declaration, ctx.source, ctx.filePath, 'hlsl'), + }); + } + } +} + +function bufferDeclarationBefore(body: SyntaxNode, source: string): SyntaxNode | null { + const previous = body.previousNamedSibling; + return previous?.type === 'declaration' && /^(?:cbuffer|tbuffer)\b/.test(declarationType(previous, source)) ? previous : null; +} + +function recoverNrdResourceText(text: string, node: SyntaxNode, ctx: ShaderCtx): boolean { + text = text.trim(); + const name = text.match(/^(NRD_(?:INPUT|OUTPUT|SAMPLER|CONSTANTS_START))\s*\(/)?.[1]; + if (!name) return false; + const args = splitMacroArgs(text); + if (name === 'NRD_CONSTANTS_START') { + if (args[0]) ctx.createNode('struct', args[0], node, { signature: text, decorators: ['resource:constant-buffer'] }); + return true; + } + if (args.length < 4) return false; + const resourceName = args[2] || args[1]; + const registerClass = args[args.length - 2]; + const registerIndex = args[args.length - 1]; + if (!resourceName) return false; + const decorators = shaderDecorators(node, ctx.source, ctx.filePath, 'hlsl') ?? []; + if (/^[tsub]$/.test(registerClass || '') && /^\d+$/.test(registerIndex || '')) decorators.push(`register:${registerClass}${registerIndex}`); + decorators.push(name === 'NRD_OUTPUT' ? 'resource:storage' : name === 'NRD_SAMPLER' ? 'resource:sampler' : 'resource:texture'); + ctx.createNode('variable', resourceName, node, { signature: text, decorators: [...new Set(decorators)] }); + return true; +} + +function recoverNrdResourcesFromRoot(root: SyntaxNode, ctx: ShaderCtx): void { + const lines = ctx.source.split(/\r?\n/); + for (let row = 0; row < lines.length; row++) { + const text = lines[row]!.trim(); + if (!/^NRD_(?:INPUT|OUTPUT|SAMPLER|CONSTANTS_START)\s*\(/.test(text)) continue; + const positionNode = root.namedDescendantForPosition({ row, column: Math.max(0, lines[row]!.search(/\S/)) }); + if (positionNode) recoverNrdResourceText(text, positionNode, ctx); + } +} + +function containsNrdResourceMacro(node: SyntaxNode, source: string): boolean { + return /\bNRD_(?:INPUT|OUTPUT|SAMPLER|CONSTANTS_START)\s*\(/.test(getNodeText(node, source)); +} + +export const hlslExtractor: LanguageExtractor = { + functionTypes: ['function_definition'], + classTypes: ['class_specifier'], + methodTypes: [], + interfaceTypes: [], + structTypes: [], + enumTypes: ['enum_specifier'], + typeAliasTypes: ['alias_declaration', 'type_definition'], + importTypes: ['preproc_include'], + callTypes: ['call_expression', 'macro_invocation'], + variableTypes: [], + fieldTypes: [], + nameField: 'declarator', + bodyField: 'body', + paramsField: 'parameters', + returnField: 'type', + resolveName: shaderFunctionName, + getSignature: (node, source) => { + const declarator = getChildByField(node, 'declarator') || node.namedChildren.find((c) => c.type === 'function_declarator'); + const params = declarator ? getChildByField(declarator, 'parameters') : null; + const ret = getChildByField(node, 'type'); + const name = shaderFunctionName(node, source); + if (!name) return undefined; + const parameterText = params ? getNodeText(params, source).trim() : ''; + const parameterList = parameterText.startsWith('(') ? parameterText : `(${parameterText})`; + return `${ret ? getNodeText(ret, source).trim() + ' ' : ''}${name}${parameterList}`.trim(); + }, + extractDecorators: (node, source, filePath) => { + const prefix = source.slice(Math.max(0, node.startIndex - 240), node.startIndex); + const attributes = `${prefix} ${node.descendantsOfType('hlsl_attribute').map((attr) => getNodeText(attr, source)).join(' ')}`; + const decorators = shaderDecorators(node, source, filePath, 'hlsl', attributes) ?? []; + const name = shaderFunctionName(node, source); + if (node.type === 'function_definition' && (name === 'main' || name === 'NRD_CS_MAIN' || /\b(?:numthreads|shader\s*\()/.test(getNodeText(node, source)))) { + decorators.push('entrypoint'); + } + return [...new Set(decorators)]; + }, + extractImport: shaderImport, + isBuiltinCall: isShaderBuiltinCall, + visitNode: (node, ctx) => { + if (node.type === 'translation_unit') { + recoverNrdResourcesFromRoot(node, ctx); + recoverShaderMacroDeclarations(node, ctx, 'hlsl'); + } + + if (node.type === 'struct_specifier') { + const nameNode = getChildByField(node, 'name') || node.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'identifier'); + const body = getChildByField(node, 'body') || node.namedChildren.find((c) => c.type === 'field_declaration_list'); + if (!nameNode || !body) return false; + const struct = ctx.createNode('struct', getNodeText(nameNode, ctx.source), node, { + signature: shaderSignature(node, ctx.source), + decorators: shaderDecorators(node, ctx.source, ctx.filePath, 'hlsl'), + }); + if (struct) { + ctx.pushScope(struct.id); + for (const field of body.descendantsOfType(['field_declaration', 'declaration'])) { + for (const item of shaderDeclarationNames(field, ctx.source)) { + ctx.createNode('field', item.name, item.node, { signature: shaderSignature(field, ctx.source) }); + } + } + ctx.popScope(); + } + return true; + } + + if (node.type === 'preproc_def' || node.type === 'preproc_function_def') { + const name = shaderMacroName(node, ctx.source); + if (!name) return false; + const kind = node.type === 'preproc_function_def' ? 'function' : 'constant'; + const created = ctx.createNode(kind, name, node, { + signature: shaderSignature(node, ctx.source), + decorators: shaderDecorators(node, ctx.source, ctx.filePath, 'hlsl'), + }); + if (created && kind === 'function') { + recoverShaderMacroBodyReferences(node, created.id, ctx); + } + return true; + } + + if ( + containsNrdResourceMacro(node, ctx.source) && + (node.type === 'expression_statement' || node.type === 'declaration' || node.type === 'ERROR' || node.type === 'call_expression') + ) return true; + + if (node.type === 'compound_statement' && bufferDeclarationBefore(node, ctx.source)) return true; + if (node.type !== 'declaration') return false; + + if (node.parent?.type === 'compound_statement' && bufferDeclarationBefore(node.parent, ctx.source)) return true; + for (let parent = node.parent; parent; parent = parent.parent) { + if (parent.type === 'function_definition') return false; + } + if (node.descendantsOfType(['struct_specifier', 'class_specifier', 'enum_specifier']).length > 0) return false; + + const type = declarationType(node, ctx.source); + if (/^(?:cbuffer|tbuffer)\b/.test(type)) { + const names = shaderDeclarationNames(node, ctx.source); + const name = names[0]?.name; + if (!name) return false; + const block = ctx.createNode('struct', name, node, { + signature: shaderSignature(node, ctx.source), + decorators: shaderDecorators(node, ctx.source, ctx.filePath, 'hlsl', previousShaderPrefix(node, ctx.source)), + }); + const body = node.nextNamedSibling; + if (block && body?.type === 'compound_statement') { + ctx.pushScope(block.id); + createBufferFields(body, ctx); + ctx.popScope(); + } + return true; + } + + const text = getNodeText(node, ctx.source); + const decorators = shaderDecorators(node, ctx.source, ctx.filePath, 'hlsl', previousShaderPrefix(node, ctx.source)); + const isConst = /\b(?:const|static\s+const)\b/.test(text); + for (const item of shaderDeclarationNames(node, ctx.source)) { + ctx.createNode(isConst ? 'constant' : 'variable', item.name, item.node, { + signature: shaderSignature(node, ctx.source), + decorators, + }); + } + return true; + }, +}; diff --git a/src/extraction/languages/index.ts b/src/extraction/languages/index.ts index 6b760b01d..abc045240 100644 --- a/src/extraction/languages/index.ts +++ b/src/extraction/languages/index.ts @@ -36,6 +36,8 @@ import { solidityExtractor } from './solidity'; import { terraformExtractor } from './terraform'; import { arktsExtractor } from './arkts'; import { nixExtractor } from './nix'; +import { glslExtractor } from './glsl'; +import { hlslExtractor } from './hlsl'; export const EXTRACTORS: Partial> = { typescript: typescriptExtractor, @@ -69,4 +71,6 @@ export const EXTRACTORS: Partial> = { terraform: terraformExtractor, arkts: arktsExtractor, nix: nixExtractor, + glsl: glslExtractor, + hlsl: hlslExtractor, }; diff --git a/src/extraction/languages/shader-common.ts b/src/extraction/languages/shader-common.ts new file mode 100644 index 000000000..582ae808b --- /dev/null +++ b/src/extraction/languages/shader-common.ts @@ -0,0 +1,445 @@ +import type { Node as SyntaxNode } from 'web-tree-sitter'; +import type { Language } from '../../types'; +import type { ExtractorContext } from '../tree-sitter-types'; +import { getChildByField, getNodeText } from '../tree-sitter-helpers'; + +const SHADER_STAGES: Record = { + '.vert': 'vertex', + '.frag': 'fragment', + '.comp': 'compute', + '.geom': 'geometry', + '.tesc': 'tess-control', + '.tese': 'tess-evaluation', + '.rgen': 'ray-generation', + '.rmiss': 'ray-miss', + '.rchit': 'closest-hit', + '.rahit': 'any-hit', + '.rint': 'intersection', + '.rcall': 'callable', + '.mesh': 'mesh', + '.task': 'task', +}; + +const BUILTIN_CALLS = new Set([ + 'abs', 'acos', 'all', 'any', 'asfloat', 'asint', 'asuint', 'atan', 'atan2', + 'barrier', 'bitCount', 'ceil', 'clamp', 'cos', 'cross', 'ddx', 'ddy', + 'determinant', 'discard', 'distance', 'dot', 'dFdx', 'dFdy', 'exp', 'exp2', + 'faceforward', 'floor', 'fma', 'frac', 'fract', 'frexp', 'fwidth', 'InterlockedAdd', + 'InterlockedAnd', 'InterlockedCompareExchange', 'InterlockedExchange', 'InterlockedMax', + 'InterlockedMin', 'InterlockedOr', 'InterlockedXor', 'isinf', 'isnan', 'ldexp', + 'length', 'lerp', 'lit', 'log', 'log2', 'mad', 'max', 'min', 'mix', 'mod', + 'modf', 'mul', 'normalize', 'pow', 'printf', 'reflect', 'refract', 'round', + 'rsqrt', 'saturate', 'sign', 'sin', 'sincos', 'smoothstep', 'sqrt', 'step', + 'tan', 'texelFetch', 'texture', 'textureGrad', 'textureLod', 'textureSize', + 'transpose', 'trunc', 'WaveActiveAllTrue', 'WaveActiveAnyTrue', 'WaveActiveBallot', + 'WaveActiveCountBits', 'WaveActiveMax', 'WaveActiveMin', 'WaveActiveProduct', + 'WaveActiveSum', 'WaveGetLaneCount', 'WaveGetLaneIndex', 'WaveReadLaneAt', + 'WaveReadLaneFirst', 'GroupMemoryBarrier', 'GroupMemoryBarrierWithGroupSync', + 'DeviceMemoryBarrier', 'DeviceMemoryBarrierWithGroupSync', 'AllMemoryBarrier', + 'AllMemoryBarrierWithGroupSync', 'Sample', 'SampleBias', 'SampleCmp', 'SampleCmpLevelZero', + 'SampleGrad', 'SampleLevel', 'Gather', 'GatherRed', 'GatherGreen', 'GatherBlue', + 'GatherAlpha', 'Load', 'Store', 'GetDimensions', 'CalculateLevelOfDetail', + 'traceRayEXT', 'executeCallableEXT', 'rayQueryInitializeEXT', 'rayQueryProceedEXT', +]); + +export function shaderStageForPath(filePath: string): string | undefined { + const lower = filePath.toLowerCase(); + for (const [ext, stage] of Object.entries(SHADER_STAGES)) { + if (lower.endsWith(ext)) return stage; + } + const hlslStage = lower.match(/\.([a-z]{2,4})\.hlsl$/)?.[1]; + if (!hlslStage) return undefined; + return ({ vs: 'vertex', ps: 'fragment', fs: 'fragment', cs: 'compute', gs: 'geometry', + hs: 'tess-control', ds: 'tess-evaluation', ms: 'mesh', as: 'task', + rgen: 'ray-generation', rmiss: 'ray-miss', rchit: 'closest-hit', + rahit: 'any-hit', rint: 'intersection', rcall: 'callable' } as Record)[hlslStage]; +} + +function normalizeToken(value: string): string { + return value.trim().replace(/\s+/g, ''); +} + +let cachedConditionSource = ''; +let cachedConditions: string[][] = []; +let cachedFunctionSource = ''; +let cachedFunctionNames = new Set(); + +function buildConditionCache(source: string): string[][] { + if (source === cachedConditionSource) return cachedConditions; + const lines = source.split(/\r?\n/); + const result: string[][] = Array.from({ length: lines.length + 1 }, () => []); + const stack: Array<{ alternatives: string[]; active: string }> = []; + for (let index = 0; index < lines.length; index++) { + result[index + 1] = stack.map((frame) => `pp:${frame.active}`); + const line = lines[index]!; + let match = line.match(/^\s*#\s*if\s+(.+)$/); + if (match) { + const condition = normalizeToken(match[1]!); + stack.push({ alternatives: [condition], active: condition }); + continue; + } + match = line.match(/^\s*#\s*ifdef\s+([A-Za-z_]\w*)/); + if (match) { + const condition = `defined(${match[1]})`; + stack.push({ alternatives: [condition], active: condition }); + continue; + } + match = line.match(/^\s*#\s*ifndef\s+([A-Za-z_]\w*)/); + if (match) { + const condition = `!defined(${match[1]})`; + stack.push({ alternatives: [condition], active: condition }); + continue; + } + match = line.match(/^\s*#\s*elif\s+(.+)$/); + if (match && stack.length > 0) { + const frame = stack[stack.length - 1]!; + const condition = normalizeToken(match[1]!); + frame.active = `!(${frame.alternatives.join('||')})&&(${condition})`; + frame.alternatives.push(condition); + continue; + } + if (/^\s*#\s*else\b/.test(line) && stack.length > 0) { + const frame = stack[stack.length - 1]!; + frame.active = `!(${frame.alternatives.join('||')})`; + continue; + } + if (/^\s*#\s*endif\b/.test(line)) stack.pop(); + } + cachedConditionSource = source; + cachedConditions = result; + return result; +} + +function conditionDecorators(node: SyntaxNode, source: string): string[] { + return buildConditionCache(source)[node.startPosition.row + 1] ?? []; +} + +export function shaderDecorators( + node: SyntaxNode, + source: string, + filePath: string, + language: Extract, + extraText = '', +): string[] | undefined { + const text = `${extraText}\n${getNodeText(node, source)}`; + const out = new Set(conditionDecorators(node, source)); + const stage = shaderStageForPath(filePath); + if (stage) out.add(`shader:${stage}`); + + for (const match of text.matchAll(/layout\s*\(([^)]*)\)/g)) { + for (const raw of match[1]!.split(',')) { + const part = raw.trim(); + if (!part) continue; + const kv = part.match(/^([A-Za-z_]\w*)\s*=\s*([^,]+)$/); + if (kv) out.add(`${kv[1]}:${normalizeToken(kv[2]!)}`); + else out.add(`layout:${normalizeToken(part)}`); + } + } + + for (const match of text.matchAll(/register\s*\(\s*([tsub]\d+)\s*(?:,\s*space(\d+))?\s*\)/gi)) { + out.add(`register:${match[1]!.toLowerCase()}`); + if (match[2]) out.add(`space:${match[2]}`); + } + for (const match of text.matchAll(/vk::binding\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)/g)) { + out.add(`binding:${match[1]}`); + out.add(`set:${match[2]}`); + } + for (const match of text.matchAll(/vk::constant_id\s*\(\s*(\d+)\s*\)/g)) out.add(`constant_id:${match[1]}`); + for (const match of text.matchAll(/vk::([A-Za-z_]\w*)\s*(?:\(([^)]*)\))?/g)) { + out.add(`vk:${match[1]}${match[2] ? ':' + normalizeToken(match[2]) : ''}`); + } + if (language === 'hlsl') { + for (const match of text.matchAll(/:\s*([A-Za-z_]\w*)\b/g)) { + if (!/^(?:register|packoffset)$/i.test(match[1]!)) out.add(`semantic:${match[1]}`); + } + for (const match of text.matchAll(/(? d.startsWith('constant_id:'))) out.add('specialization_constant'); + if (/\buniform\b/.test(text)) out.add('storage:uniform'); + if (/\bbuffer\b/.test(text)) out.add('storage:buffer'); + if (/\b(groupshared|shared)\b/.test(text)) out.add('storage:shared'); + if (/\bin\b/.test(text)) out.add('interface:in'); + if (/\bout\b/.test(text)) out.add('interface:out'); + if (/\b(rayPayload|rayPayloadEXT)\b/.test(text)) out.add('resource:ray-payload'); + if (/\b(hitAttribute|hitAttributeEXT)\b/.test(text)) out.add('resource:hit-attribute'); + if (/\b(callableData|callableDataEXT)\b/.test(text)) out.add('resource:callable-data'); + if (/\b(accelerationStructureEXT|RaytracingAccelerationStructure)\b/.test(text)) out.add('resource:acceleration-structure'); + if (/\bRW(?:Texture|Buffer)|\bimage\w*\b/.test(text)) out.add('resource:storage'); + else if (/\bTexture\w*\b|\btexture\w*\b/.test(text)) out.add('resource:texture'); + if (/\bSampler\w*\b|\bsampler\w*\b/.test(text)) out.add('resource:sampler'); + if (/\b(?:StructuredBuffer|ByteAddressBuffer|buffer_reference)\b/.test(text)) out.add('resource:buffer'); + if (language === 'hlsl' && /\bcbuffer\b/.test(text)) out.add('resource:constant-buffer'); + if (language === 'hlsl' && /\btbuffer\b/.test(text)) out.add('resource:texture-buffer'); + } + + return out.size > 0 ? [...out] : undefined; +} + +export function shaderFunctionName(node: SyntaxNode, source: string): string | undefined { + const declarator = getChildByField(node, 'declarator') || node.namedChildren.find((c) => c.type === 'function_declarator'); + const name = declarator ? getChildByField(declarator, 'name') || getChildByField(declarator, 'declarator') : getChildByField(node, 'name'); + if (name) return getNodeText(name, source).replace(/\s*\(.*/, '').trim(); + const id = declarator?.descendantsOfType('identifier')[0] || node.descendantsOfType('identifier')[0]; + return id ? getNodeText(id, source) : undefined; +} + +function unwrapDeclarator(node: SyntaxNode | null): SyntaxNode | null { + let current = node; + for (let i = 0; current && i < 10; i++) { + if (current.type === 'identifier' || current.type === 'field_identifier') return current; + current = getChildByField(current, 'name') || getChildByField(current, 'declarator') || current.namedChild(0); + } + return null; +} + +export function shaderDeclarationNames(node: SyntaxNode, source: string): Array<{ name: string; node: SyntaxNode }> { + const out: Array<{ name: string; node: SyntaxNode }> = []; + const seen = new Set(); + const add = (candidate: SyntaxNode | null): void => { + const id = unwrapDeclarator(candidate); + if (!id) return; + const name = getNodeText(id, source).trim(); + if (!name || seen.has(name)) return; + seen.add(name); + out.push({ name, node: id }); + }; + + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + const field = node.fieldNameForNamedChild(i); + if (field === 'declarator' && child.type !== 'semantics') add(child); + } + for (const decl of node.descendantsOfType(['declarator', 'field_declarator'])) add(getChildByField(decl, 'name') || decl); + return out; +} + +export function shaderSignature(node: SyntaxNode, source: string, max = 400): string { + return getNodeText(node, source).trim().replace(/\s+/g, ' ').slice(0, max); +} + +export function shaderImport(node: SyntaxNode, source: string): { moduleName: string; signature: string } | null { + const text = getNodeText(node, source).trim(); + const match = text.match(/^#\s*(?:include|import)\s*[<"]([^>"]+)[>"]/m); + return match ? { moduleName: match[1]!, signature: text } : null; +} + +export function shaderMacroName(node: SyntaxNode, source: string): string | undefined { + const name = getChildByField(node, 'name') || node.namedChildren.find((c) => c.type === 'identifier'); + return name ? getNodeText(name, source) : undefined; +} + +export function previousShaderPrefix(node: SyntaxNode, source: string): string { + const previous = node.previousNamedSibling; + if (!previous) return ''; + const text = getNodeText(previous, source).trim(); + return /^(?:\[\[|[A-Z][A-Z0-9_]*\s*\()/.test(text) ? text : ''; +} + +export function isShaderBuiltinCall(name: string, node: SyntaxNode): boolean { + const simple = name.includes('.') ? name.slice(name.lastIndexOf('.') + 1) : name; + let root = node; + while (root.parent) root = root.parent; + const rootSource = root.text; + if (rootSource !== cachedFunctionSource) { + cachedFunctionSource = rootSource; + cachedFunctionNames = new Set(root.descendantsOfType(['function_definition', 'function_declaration', 'function_declarator']) + .map((candidate) => shaderFunctionName(candidate, rootSource)).filter((value): value is string => !!value)); + } + if (cachedFunctionNames.has(simple)) return false; + const fn = getChildByField(node, 'function') || node.namedChild(0); + if (fn && (fn.type === 'type_specifier' || fn.type === 'type_identifier' || fn.type === 'primitive_type' || fn.type === 'template_type')) return true; + if (/^(?:[biud]?vec[234]|d?mat[234](?:x[234])?|bool|int|uint|float|double|half)(?:[1-4](?:x[1-4])?)?$/.test(simple)) return true; + return BUILTIN_CALLS.has(simple); +} + +function isShaderBuiltinName(name: string): boolean { + return BUILTIN_CALLS.has(name) || /^(?:[biud]?vec[234]|d?mat[234](?:x[234])?|bool|int|uint|float|double|half)(?:[1-4](?:x[1-4])?)?$/.test(name); +} + +interface ShaderMacro { + name: string; + params: string[] | null; + body: string; +} + +function shaderMacros(source: string): Map { + const macros = new Map(); + const lines = source.split(/\r?\n/); + for (let index = 0; index < lines.length; index++) { + let logical = lines[index]!; + while (/\\\s*$/.test(logical) && index + 1 < lines.length) { + logical = logical.replace(/\\\s*$/, '') + '\n' + lines[++index]!; + } + const match = logical.match(/^\s*#\s*define\s+([A-Za-z_]\w*)(?:\s*\(([^)]*)\))?\s*([\s\S]*)$/); + if (!match) continue; + macros.set(match[1]!, { + name: match[1]!, + params: match[2] === undefined ? null : match[2].split(',').map((value) => value.trim()).filter(Boolean), + body: match[3] ?? '', + }); + } + return macros; +} + +function invocationAt(text: string, open: number): { args: string[]; end: number } | null { + let depth = 0; + for (let index = open; index < text.length; index++) { + const char = text[index]!; + if (char === '(') depth++; + else if (char === ')' && --depth === 0) { + return { args: splitMacroArgs(text.slice(open, index + 1)), end: index + 1 }; + } + } + return null; +} + +function expandShaderMacros(text: string, macros: Map, depth = 0): string { + if (depth >= 8) return text; + let output = text; + let changed = false; + for (const macro of macros.values()) { + if (macro.params) { + const regex = new RegExp(`\\b${macro.name}\\s*\\(`, 'g'); + let match: RegExpExecArray | null; + while ((match = regex.exec(output))) { + const open = output.indexOf('(', match.index); + const invocation = invocationAt(output, open); + if (!invocation) break; + let replacement = macro.body; + macro.params.forEach((param, index) => { + replacement = replacement.replace(new RegExp(`\\b${param}\\b`, 'g'), invocation.args[index] ?? ''); + }); + replacement = replacement.replace(/\s*##\s*/g, ''); + output = output.slice(0, match.index) + replacement + output.slice(invocation.end); + regex.lastIndex = match.index + replacement.length; + changed = true; + } + } else { + const next = output.replace(new RegExp(`\\b${macro.name}\\b`, 'g'), macro.body); + if (next !== output) { + output = next; + changed = true; + } + } + } + return changed ? expandShaderMacros(output, macros, depth + 1) : output; +} + +function addTextReferences(ownerId: string, text: string, line: number, ctx: ExtractorContext, params: string[] = []): void { + const ignored = new Set([...params, 'if', 'for', 'while', 'switch', 'return', 'sizeof']); + const calls = new Set(); + for (const match of text.matchAll(/\b([A-Za-z_]\w*)\s*\(/g)) { + const name = match[1]!; + if (ignored.has(name) || isShaderBuiltinName(name)) continue; + calls.add(name); + ctx.addUnresolvedReference({ fromNodeId: ownerId, referenceName: name, referenceKind: 'calls', + filePath: ctx.filePath, language: ctx.nodes.find((node) => node.id === ownerId)?.language ?? 'glsl', line, column: match.index }); + } + for (const match of text.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g)) { + const name = match[0]; + if (ignored.has(name) || calls.has(name)) continue; + ctx.addUnresolvedReference({ fromNodeId: ownerId, referenceName: name, referenceKind: 'references', + filePath: ctx.filePath, language: ctx.nodes.find((node) => node.id === ownerId)?.language ?? 'glsl', line, column: match.index }); + } +} + +export function recoverShaderMacroBodyReferences(node: SyntaxNode, createdId: string, ctx: ExtractorContext): void { + if (node.descendantsOfType(['function_call', 'call_expression', 'macro_invocation']).length > 0) return; + const text = getNodeText(node, ctx.source); + const body = text.replace(/^\s*#\s*define\s+[A-Za-z_]\w*(?:\s*\(([^)]*)\))?\s*/, ''); + const params = text.match(/^\s*#\s*define\s+[A-Za-z_]\w*\s*\(([^)]*)\)/)?.[1] + ?.split(',').map((value) => value.trim()).filter(Boolean) ?? []; + addTextReferences(createdId, body, node.startPosition.row + 1, ctx, params); +} + +export function recoverShaderMacroDeclarations( + root: SyntaxNode, + ctx: ExtractorContext, + language: Extract, +): void { + const macros = shaderMacros(ctx.source); + const lines = ctx.source.split(/\r?\n/); + for (let row = 0; row < lines.length; row++) { + const first = lines[row]!.match(/^\s*([A-Z][A-Z0-9_]*)\s*\(/); + if (!first || !macros.get(first[1]!)?.params || /^NRD_/.test(first[1]!)) continue; + let invocation = lines[row]!; + let balance = (invocation.match(/\(/g) || []).length - (invocation.match(/\)/g) || []).length; + let endRow = row; + while (balance > 0 && endRow + 1 < lines.length && endRow - row < 16) { + invocation += '\n' + lines[++endRow]!; + balance += (lines[endRow]!.match(/\(/g) || []).length - (lines[endRow]!.match(/\)/g) || []).length; + } + const expanded = expandShaderMacros(invocation, macros).trim(); + if (!expanded || expanded === invocation.trim()) continue; + const column = Math.max(0, lines[row]!.search(/\S/)); + const positionNode = root.namedDescendantForPosition({ row, column }); + if (!positionNode) continue; + let insideFunction = false; + for (let parent: SyntaxNode | null = positionNode; parent; parent = parent.parent) { + if (parent.type === 'function_definition') { insideFunction = true; break; } + } + if (insideFunction) continue; + + let kind: 'function' | 'struct' | 'type_alias' | 'variable' | 'constant' | null = null; + let name = ''; + let match = expanded.match(/\b(?:struct|class)\s+([A-Za-z_]\w*)/); + if (match) { kind = 'struct'; name = match[1]!; } + if (!kind && (match = expanded.match(/\b(?:cbuffer|tbuffer|uniform|buffer)\s+([A-Za-z_]\w*)\s*\{/))) { + kind = 'struct'; name = match[1]!; + } + if (!kind && (match = expanded.match(/\b([A-Za-z_]\w*)\s*\([^;{}]*\)\s*\{/))) { + kind = 'function'; name = match[1]!; + } + if (!kind && (match = expanded.match(/\btypedef\b[\s\S]*?\b([A-Za-z_]\w*)\s*;/))) { + kind = 'type_alias'; name = match[1]!; + } + if (!kind && (match = expanded.match(/(?:^|[;}])\s*(?:layout\s*\([^)]*\)\s*)?(?:\[\[[^\]]+\]\]\s*)*(?:static\s+|const\s+|uniform\s+|groupshared\s+|in\s+|out\s+)*[A-Za-z_]\w*(?:\s*<[^;{}]+>)?(?:\s*[*&])?\s+([A-Za-z_]\w*)\b[^;{}]*;/))) { + kind = /\bconst\b/.test(expanded) ? 'constant' : 'variable'; + name = match[1]!; + } + if (!kind || !name || ctx.nodes.some((node) => node.kind === kind && node.name === name && node.startLine === row + 1)) continue; + const created = ctx.createNode(kind, name, positionNode, { + signature: `${invocation.trim()} => ${expanded.replace(/\s+/g, ' ').slice(0, 300)}`, + decorators: shaderDecorators(positionNode, ctx.source, ctx.filePath, language, expanded), + }); + if (created && kind === 'function') addTextReferences(created.id, expanded, row + 1, ctx); + row = endRow; + } +} + +export function splitMacroArgs(text: string): string[] { + const open = text.indexOf('('); + const close = text.lastIndexOf(')'); + if (open < 0 || close <= open) return []; + const out: string[] = []; + let depth = 0; + let start = open + 1; + for (let i = open + 1; i < close; i++) { + const ch = text[i]!; + if (ch === '(' || ch === '<' || ch === '[') depth++; + else if (ch === ')' || ch === '>' || ch === ']') depth--; + else if (ch === ',' && depth === 0) { + out.push(text.slice(start, i).trim()); + start = i + 1; + } + } + out.push(text.slice(start, close).trim()); + return out; +} diff --git a/src/extraction/tree-sitter-types.ts b/src/extraction/tree-sitter-types.ts index 2a02b47b7..9833c0b5b 100644 --- a/src/extraction/tree-sitter-types.ts +++ b/src/extraction/tree-sitter-types.ts @@ -168,6 +168,10 @@ export interface LanguageExtractor { * implementations across source sets. */ extractModifiers?: (node: SyntaxNode) => string[] | undefined; + /** Extract decorators with access to source and file path (shader stage/layout metadata). */ + extractDecorators?: (node: SyntaxNode, source: string, filePath: string) => string[] | undefined; + /** Return true for compiler intrinsics/type constructors that should not emit call refs. */ + isBuiltinCall?: (name: string, node: SyntaxNode, source: string) => boolean; // --- New config properties --- diff --git a/src/extraction/tree-sitter.ts b/src/extraction/tree-sitter.ts index fe3664ae8..a79a31dc8 100644 --- a/src/extraction/tree-sitter.ts +++ b/src/extraction/tree-sitter.ts @@ -30,6 +30,8 @@ import { DfmExtractor } from './dfm-extractor'; import { VueExtractor } from './vue-extractor'; import { MyBatisExtractor } from './mybatis-extractor'; import { CfmlExtractor } from './cfml-extractor'; +import { GlslfxExtractor } from './glslfx-extractor'; +import { extractCmake, extractPowershell } from './build-script-extractors'; import { getAllFrameworkResolvers, getApplicableFrameworks, @@ -1053,6 +1055,13 @@ export class TreeSitterExtractor { this.scanFnRefSubtree(node, 0); skipChildren = true; } + // C++ named lambdas are local variables syntactically, but callable symbols + // semantically: `auto parse = [](auto x) { ... };`. Handle them before the + // top-level-variable gate so lambdas inside both free functions and methods + // become scoped function nodes without indexing ordinary local variables. + else if (this.language === 'cpp' && nodeType === 'declaration' && this.extractCppLambdaDeclarations(node)) { + skipChildren = true; + } // Check for variable declarations (const, let, var, etc.) // Only extract top-level variables (not inside functions/methods) — plus // class/module-scope CONSTANTS, which Ruby (and other const-in-class @@ -1320,6 +1329,10 @@ export class TreeSitterExtractor { if (mods && mods.length > 0) { newNode.decorators = [...(newNode.decorators ?? []), ...mods]; } + const decorators = this.extractor?.extractDecorators?.(node, this.source, this.filePath); + if (decorators && decorators.length > 0) { + newNode.decorators = [...new Set([...(newNode.decorators ?? []), ...decorators])]; + } this.nodes.push(newNode); @@ -1452,7 +1465,11 @@ export class TreeSitterExtractor { /** * Extract a function */ - private extractFunction(node: SyntaxNode, nameOverride?: string): void { + private extractFunction( + node: SyntaxNode, + nameOverride?: string, + extraProps?: Partial + ): void { if (!this.extractor) return; // If the language provides getReceiverType and this function has a receiver @@ -1516,6 +1533,9 @@ export class TreeSitterExtractor { const isAsync = this.extractor.isAsync?.(node); const isStatic = this.extractor.isStatic?.(node); const returnType = this.extractor.getReturnType?.(node, this.source); + const body = this.extractor.resolveBody?.(node, this.extractor.bodyField) + ?? getChildByField(node, this.extractor.bodyField); + const correctedBody = body ? this.correctMalformedCppBodyRange(body) : null; const funcNode = this.createNode('function', name, node, { docstring, @@ -1525,6 +1545,11 @@ export class TreeSitterExtractor { isAsync, isStatic, returnType, + ...(correctedBody ? { + endLine: correctedBody.endLine, + endColumn: correctedBody.endColumn, + } : {}), + ...extraProps, }); if (!funcNode) return; @@ -1538,12 +1563,13 @@ export class TreeSitterExtractor { // Push to stack and visit body this.nodeStack.push(funcNode.id); - const body = this.extractor.resolveBody?.(node, this.extractor.bodyField) - ?? getChildByField(node, this.extractor.bodyField); if (body) { - this.visitFunctionBody(body, funcNode.id); + this.visitFunctionBody(body, funcNode.id, correctedBody?.endIndex); } this.nodeStack.pop(); + if (body && correctedBody) { + this.recoverCppFunctionsAfterMalformedBody(body, correctedBody.endIndex); + } } /** @@ -1717,6 +1743,9 @@ export class TreeSitterExtractor { const isAsync = this.extractor.isAsync?.(node); const isStatic = this.extractor.isStatic?.(node); const returnType = this.extractor.getReturnType?.(node, this.source); + const body = this.extractor.resolveBody?.(node, this.extractor.bodyField) + ?? getChildByField(node, this.extractor.bodyField); + const correctedBody = body ? this.correctMalformedCppBodyRange(body) : null; const extraProps: Partial = { docstring, signature, @@ -1724,6 +1753,10 @@ export class TreeSitterExtractor { isAsync, isStatic, returnType, + ...(correctedBody ? { + endLine: correctedBody.endLine, + endColumn: correctedBody.endColumn, + } : {}), }; if (receiverType) { extraProps.qualifiedName = `${receiverType}::${name}`; @@ -1758,12 +1791,13 @@ export class TreeSitterExtractor { // Push to stack and visit body this.nodeStack.push(methodNode.id); - const body = this.extractor.resolveBody?.(node, this.extractor.bodyField) - ?? getChildByField(node, this.extractor.bodyField); if (body) { - this.visitFunctionBody(body, methodNode.id); + this.visitFunctionBody(body, methodNode.id, correctedBody?.endIndex); } this.nodeStack.pop(); + if (body && correctedBody) { + this.recoverCppFunctionsAfterMalformedBody(body, correctedBody.endIndex); + } } /** @@ -2473,6 +2507,38 @@ export class TreeSitterExtractor { * Extracts top-level and module-level variable declarations. * Captures the variable name and first 100 chars of initializer in signature for searchability. */ + private extractCppLambdaDeclarations(node: SyntaxNode): boolean { + let extracted = false; + for (let i = 0; i < node.namedChildCount; i++) { + const declarator = node.namedChild(i); + if (declarator?.type !== 'init_declarator') continue; + const value = getChildByField(declarator, 'value'); + if (value?.type !== 'lambda_expression') continue; + const nameNode = cDeclaratorIdentifier(getChildByField(declarator, 'declarator')); + if (!nameNode) continue; + const name = getNodeText(nameNode, this.source); + if (!name) continue; + const ownerId = this.nodeStack[this.nodeStack.length - 1]; + const owner = ownerId ? this.nodes.find((candidate) => candidate.id === ownerId) : undefined; + let ownerQualifiedName = owner && owner.kind !== 'file' ? owner.qualifiedName : ''; + const namespace = this.namespacePrefix.join('::'); + if ( + namespace && + ownerQualifiedName && + ownerQualifiedName !== namespace && + !ownerQualifiedName.startsWith(`${namespace}::`) + ) { + ownerQualifiedName = `${namespace}::${ownerQualifiedName}`; + } + this.extractFunction(value, name, { + ...(ownerQualifiedName ? { qualifiedName: `${ownerQualifiedName}::${name}` } : {}), + decorators: ['cpp:lambda'], + }); + extracted = true; + } + return extracted; + } + private extractVariable(node: SyntaxNode): void { if (!this.extractor) return; @@ -3619,6 +3685,48 @@ export class TreeSitterExtractor { return this.erlangAtomMacros.get(macroName) ?? null; } + private isPlausibleShaderType(name: string): boolean { + return /^(?:(?:bool|int|uint|dword|half|float|double|min16float|min10float|min16int|min12int|min16uint)(?:[1-4](?:x[1-4])?)?|[A-Z][A-Za-z0-9_]*|(?:RTXDI|RAB)_[A-Za-z0-9_]+)$/.test(name); + } + + private inferShaderArgumentType(argument: SyntaxNode, call: SyntaxNode): string | null { + let arg = argument; + while (arg.type === 'parenthesized_expression' && arg.namedChildCount === 1) { + const child = arg.namedChild(0); + if (!child) break; + arg = child; + } + if (arg.type === 'true' || arg.type === 'false') return 'bool'; + if (/^(?:float|double)_literal$/.test(arg.type)) return 'float'; + if (arg.type === 'call_expression') { + const fn = getChildByField(arg, 'function'); + if (fn?.type === 'identifier') { + const name = getNodeText(fn, this.source); + if (this.isPlausibleShaderType(name)) return name; + } + } + if (arg.type !== 'identifier') return null; + const identifier = getNodeText(arg, this.source); + if (!identifier) return null; + let scope: SyntaxNode | null = call; + while (scope.parent && !this.extractor?.functionTypes.includes(scope.type) && !this.extractor?.methodTypes.includes(scope.type)) { + scope = scope.parent; + } + const before = this.source.slice(scope?.startIndex ?? 0, call.startIndex); + const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const declaration = new RegExp( + `(?:^|[({;,])\\s*(?:(?:const|static|uniform|groupshared|inout|out|in)\\s+)*([A-Za-z_]\\w*)\\s+${escaped}\\b`, + 'gm', + ); + let inferred: string | null = null; + let match: RegExpExecArray | null; + while ((match = declaration.exec(before))) { + const type = match[1]!; + if (this.isPlausibleShaderType(type)) inferred = type; + } + return inferred; + } + private extractCall(node: SyntaxNode): void { if (this.nodeStack.length === 0) return; @@ -4482,13 +4590,37 @@ export class TreeSitterExtractor { } } + if ( + calleeName && + this.extractor?.isBuiltinCall?.(calleeName, node, this.source) && + !this.nodes.some((n) => + (n.kind === 'function' || n.kind === 'method') && + (n.name === calleeName || n.name === calleeName.slice(calleeName.lastIndexOf('.') + 1)) + ) + ) { + return; + } + if (calleeName) { + const shaderArgs = (this.language === 'glsl' || this.language === 'hlsl') + ? getChildByField(node, 'arguments') + : null; + const shaderCandidates = shaderArgs ? [`arity:${shaderArgs.namedChildCount}`] : undefined; + if (shaderArgs && shaderCandidates) { + for (let index = 0; index < shaderArgs.namedChildCount; index++) { + const argument = shaderArgs.namedChild(index); + if (!argument) continue; + const type = this.inferShaderArgumentType(argument, node); + if (type) shaderCandidates.push(`argtype:${index}:${type}`); + } + } this.unresolvedReferences.push({ fromNodeId: callerId, referenceName: calleeName, referenceKind: 'calls', line: node.startPosition.row + 1, column: node.startPosition.column, + candidates: shaderCandidates, }); } } @@ -5039,10 +5171,126 @@ export class TreeSitterExtractor { targets.add(target); } - private visitFunctionBody(body: SyntaxNode, _functionId: string): void { + /** + * tree-sitter-cpp can pair a nested conditional directive with a distant + * `#endif`, causing one function body to consume the rest of a file. When the + * body is erroneous, prefer the first lexically balanced closing brace. The + * scanner ignores comments and every C++ string form so braces in text do not + * truncate a valid body. + */ + private correctMalformedCppBodyRange( + body: SyntaxNode + ): { endIndex: number; endLine: number; endColumn: number } | null { + if (this.language !== 'cpp' || !body.hasError) return null; + + const openIndex = this.source.indexOf('{', body.startIndex); + if (openIndex < 0 || openIndex >= body.endIndex) return null; + const closeIndex = this.findMatchingCppBrace(openIndex); + if (closeIndex < 0 || closeIndex + 1 >= body.endIndex) return null; + + const throughClose = this.source.slice(body.startIndex, closeIndex + 1); + const newlineCount = (throughClose.match(/\n/g) ?? []).length; + const lastNewline = throughClose.lastIndexOf('\n'); + const endColumn = lastNewline >= 0 + ? throughClose.length - lastNewline - 1 + : body.startPosition.column + throughClose.length; + return { + endIndex: closeIndex + 1, + endLine: body.startPosition.row + newlineCount + 1, + endColumn, + }; + } + + private findMatchingCppBrace(openIndex: number): number { + let depth = 0; + let i = openIndex; + while (i < this.source.length) { + const rawPrefix = ['u8R"', 'uR"', 'UR"', 'LR"', 'R"'] + .find((prefix) => this.source.startsWith(prefix, i)); + if (rawPrefix) { + const delimiterStart = i + rawPrefix.length; + const paren = this.source.indexOf('(', delimiterStart); + if (paren >= 0 && paren - delimiterStart <= 16) { + const delimiter = this.source.slice(delimiterStart, paren); + const rawEnd = this.source.indexOf(`)${delimiter}"`, paren + 1); + if (rawEnd >= 0) { + i = rawEnd + delimiter.length + 2; + continue; + } + } + } + + const char = this.source[i]; + const next = this.source[i + 1]; + if (char === '/' && next === '/') { + const newline = this.source.indexOf('\n', i + 2); + i = newline < 0 ? this.source.length : newline + 1; + continue; + } + if (char === '/' && next === '*') { + const commentEnd = this.source.indexOf('*/', i + 2); + i = commentEnd < 0 ? this.source.length : commentEnd + 2; + continue; + } + if (char === '"' || char === "'") { + const quote = char; + i++; + while (i < this.source.length) { + if (this.source[i] === '\\') { + i += 2; + continue; + } + if (this.source[i] === quote) { + i++; + break; + } + i++; + } + continue; + } + if (char === '{') depth++; + if (char === '}' && --depth === 0) return i; + i++; + } + return -1; + } + + /** + * Later top-level definitions are descendants of the corrupted body in the + * AST, so the normal root walk never sees them. Revisit only the outermost + * function definitions after the recovered close at the original file scope. + */ + private recoverCppFunctionsAfterMalformedBody(body: SyntaxNode, cutoff: number): void { + const visit = (node: SyntaxNode): void => { + if (node.endIndex <= cutoff) return; + if ( + node.startIndex >= cutoff && + this.extractor?.functionTypes.includes(node.type) + ) { + this.visitNode(node); + return; + } + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (child) visit(child); + } + }; + + for (let i = 0; i < body.namedChildCount; i++) { + const child = body.namedChild(i); + if (child) visit(child); + } + } + + private visitFunctionBody( + body: SyntaxNode, + _functionId: string, + maxEndIndex?: number + ): void { if (!this.extractor) return; const visitForCallsAndStructure = (node: SyntaxNode): void => { + if (maxEndIndex !== undefined && node.startIndex >= maxEndIndex) return; const nodeType = node.type; // Function-as-value capture (#756) — function bodies are walked here, @@ -5085,6 +5333,24 @@ export class TreeSitterExtractor { } } + if ( + (this.language === 'glsl' || this.language === 'hlsl') && + nodeType === 'identifier' && + this.nodeStack.length > 0 + ) { + const macroName = getNodeText(node, this.source); + if (/^[A-Z][A-Z0-9_]{2,}$/.test(macroName)) { + const ownerId = this.nodeStack[this.nodeStack.length - 1]; + if (ownerId) this.unresolvedReferences.push({ + fromNodeId: ownerId, + referenceName: macroName, + referenceKind: 'references', + line: node.startPosition.row + 1, + column: node.startPosition.column, + }); + } + } + // C++ stack / direct-initialization construction — `Calculator calc(0)` // and `Widget w{1, 2}`. Unlike heap `new Calculator(0)` (a new_expression // handled above), these carry the constructor arguments directly on the @@ -5155,6 +5421,10 @@ export class TreeSitterExtractor { // recursion below, keeping their inner calls attributed to the enclosing // function: this bounds the new nodes to NAMED functions only (no explosion, // no lost edges). extractFunction walks the nested body itself, so we return. + if (this.language === 'cpp' && nodeType === 'declaration' && this.extractCppLambdaDeclarations(node)) { + return; + } + if (this.extractor!.functionTypes.includes(nodeType)) { const nestedName = extractName(node, this.source, this.extractor!); if (nestedName && nestedName !== '') { @@ -6611,6 +6881,13 @@ export function extractFromSource( // CFML never spells one in source) stays ``. const extractor = new CfmlExtractor(filePath, source, detectedLanguage); result = extractor.extract(); + } else if (detectedLanguage === 'glsl' && fileExtension === '.glslfx') { + const extractor = new GlslfxExtractor(filePath, source); + result = extractor.extract(); + } else if (detectedLanguage === 'cmake') { + result = extractCmake(filePath, source); + } else if (detectedLanguage === 'powershell') { + result = extractPowershell(filePath, source); } else if (isFileLevelOnlyLanguage(detectedLanguage)) { // No symbol extraction at this stage — files are tracked at the file-record // level only. Framework extractors (Drupal routing yml, Spring `@Value` diff --git a/src/extraction/wasm/tree-sitter-glsl.wasm b/src/extraction/wasm/tree-sitter-glsl.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f9bec788e11fba8377d5b0907562b659b412e224 GIT binary patch literal 853461 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 BeTe`7 literal 0 HcmV?d00001 diff --git a/src/extraction/wasm/tree-sitter-hlsl.wasm b/src/extraction/wasm/tree-sitter-hlsl.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9c7241092f6cd31428a610f76e91ae5e784693fe GIT binary patch literal 4281939 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 @VOAIiWO`~BsR87N2X;^Zzh9#IyETKw4 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 Ab*>93 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 t0N4`h3wx1pEL9sD?P4Oq#b_7lCIw!oo`#868z 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 literal 0 HcmV?d00001 diff --git a/src/graph/traversal.ts b/src/graph/traversal.ts index 6cb00ac6b..744421a5c 100644 --- a/src/graph/traversal.ts +++ b/src/graph/traversal.ts @@ -28,6 +28,12 @@ interface TraversalStep { depth: number; } +interface ShaderImpactContext { + roots: Set; + files: Set; + rootSites: Map; +} + /** * Graph traverser for BFS and DFS traversal */ @@ -527,11 +533,54 @@ export class GraphTraverser { const edges: Edge[] = []; const visited = new Set(); + const shaderContext = this.getShaderImpactContext(focalNode); + // Add focal node nodes.set(focalNode.id, focalNode); // Traverse incoming edges to find all dependents - this.getImpactRecursive(nodeId, maxDepth, 0, nodes, edges, visited); + this.getImpactRecursive(nodeId, maxDepth, 0, nodes, edges, visited, shaderContext); + if (shaderContext) { + for (const root of shaderContext.roots) { + const line = shaderContext.rootSites.get(root); + const rootNodes = this.queries.getNodesByFile(root); + const source = rootNodes.find((node) => node.kind === 'import' && line !== undefined && node.startLine === line) + ?? rootNodes.find((node) => node.kind === 'file'); + if (source && !nodes.has(source.id)) { + nodes.set(source.id, source); + edges.push({ + source: source.id, + target: focalNode.id, + kind: 'imports', + line, + provenance: 'heuristic', + metadata: { + synthesizedBy: 'shader-context-root', + contextRoot: root, + targetFile: focalNode.filePath, + registeredAt: `${root}:${line ?? source.startLine}`, + }, + }); + } + const entry = this.findShaderRootEntryCaller(focalNode.id, root); + if (entry && !nodes.has(entry.node.id)) nodes.set(entry.node.id, entry.node); + if (entry) { + edges.push({ + source: entry.node.id, + target: focalNode.id, + kind: 'calls', + line: entry.line, + provenance: 'heuristic', + metadata: { + synthesizedBy: 'shader-context-entry', + contextRoot: root, + targetFile: focalNode.filePath, + registeredAt: `${root}:${entry.line ?? entry.node.startLine}`, + }, + }); + } + } + } return { nodes, @@ -540,13 +589,138 @@ export class GraphTraverser { }; } + private getShaderFileClosure(root: string): Set { + const files = new Set(); + const queue = [root]; + for (let index = 0; index < queue.length && index < 4096; index++) { + const filePath = queue[index]!; + if (files.has(filePath)) continue; + files.add(filePath); + const fileNode = this.queries.getNodesByFile(filePath).find((node) => node.kind === 'file'); + if (!fileNode) continue; + const imports = this.queries.getOutgoingEdges(fileNode.id, ['imports']); + const targets = this.queries.getNodesByIds(imports.map((edge) => edge.target)); + for (const edge of imports) { + const target = targets.get(edge.target); + if (target?.kind === 'file' && (target.language === 'glsl' || target.language === 'hlsl') && !files.has(target.filePath)) { + queue.push(target.filePath); + } + } + } + return files; + } + + private findShaderRootEntryCaller(focalId: string, root: string): { node: Node; line?: number } | null { + const allowedFiles = this.getShaderFileClosure(root); + const queue = [focalId]; + const visited = new Set([focalId]); + for (let index = 0; index < queue.length && index < 4096; index++) { + const current = queue[index]!; + const incoming = this.queries.getIncomingEdges(current).filter((edge) => edge.kind === 'calls'); + const sources = this.queries.getNodesByIds(incoming.map((edge) => edge.source)); + for (const edge of incoming) { + const source = sources.get(edge.source); + if (!source || (source.language !== 'glsl' && source.language !== 'hlsl') || !allowedFiles.has(source.filePath)) continue; + if (source.filePath === root && source.kind === 'function' && (source.name === 'main' || source.decorators?.includes('entrypoint'))) { + return { node: source, line: edge.line }; + } + if (!visited.has(source.id)) { + visited.add(source.id); + queue.push(source.id); + } + } + } + return null; + } + + private getShaderImpactContext(focalNode: Node): ShaderImpactContext | null { + if (focalNode.language !== 'glsl' && focalNode.language !== 'hlsl') return null; + const roots = new Set(); + for (const edge of this.queries.getIncomingEdges(focalNode.id)) { + const metadata = edge.metadata as Record | undefined; + if (metadata?.synthesizedBy !== 'shader-context-variant' || !Array.isArray(metadata.contextRoots)) continue; + for (const root of metadata.contextRoots) if (typeof root === 'string') roots.add(root); + } + if (roots.size === 0) { + const reverseFiles = new Set([focalNode.filePath]); + const hasShaderParent = new Set(); + const queue = [focalNode.filePath]; + for (let index = 0; index < queue.length && index < 4096; index++) { + const filePath = queue[index]!; + const fileNode = this.queries.getNodesByFile(filePath).find((node) => node.kind === 'file'); + if (!fileNode) continue; + const incoming = this.queries.getIncomingEdges(fileNode.id).filter((edge) => edge.kind === 'imports'); + const sources = this.queries.getNodesByIds(incoming.map((edge) => edge.source)); + for (const edge of incoming) { + const source = sources.get(edge.source); + if (source?.kind !== 'file' || (source.language !== 'glsl' && source.language !== 'hlsl')) continue; + hasShaderParent.add(filePath); + if (!reverseFiles.has(source.filePath)) { + reverseFiles.add(source.filePath); + queue.push(source.filePath); + } + } + } + const entryExtension = /\.(?:glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|fx)$/i; + for (const filePath of reverseFiles) { + if (!hasShaderParent.has(filePath) && entryExtension.test(filePath)) roots.add(filePath); + } + } + if (roots.size === 0) return null; + const files = new Set(); + const queue = [...roots]; + for (let index = 0; index < queue.length && index < 4096; index++) { + const filePath = queue[index]!; + if (files.has(filePath)) continue; + files.add(filePath); + const fileNode = this.queries.getNodesByFile(filePath).find((node) => node.kind === 'file'); + if (!fileNode) continue; + const imports = this.queries.getOutgoingEdges(fileNode.id, ['imports']); + const targets = this.queries.getNodesByIds(imports.map((edge) => edge.target)); + for (const edge of imports) { + const target = targets.get(edge.target); + if (target?.kind === 'file' && (target.language === 'glsl' || target.language === 'hlsl') && !files.has(target.filePath)) { + queue.push(target.filePath); + } + } + } + files.add(focalNode.filePath); + const rootSites = new Map(); + for (const root of roots) { + const queue: Array<{ filePath: string; firstLine?: number }> = [{ filePath: root }]; + const visited = new Set(); + for (let index = 0; index < queue.length && index < 4096; index++) { + const current = queue[index]!; + if (visited.has(current.filePath)) continue; + visited.add(current.filePath); + const fileNode = this.queries.getNodesByFile(current.filePath).find((node) => node.kind === 'file'); + if (!fileNode) continue; + const imports = this.queries.getOutgoingEdges(fileNode.id, ['imports']); + const targets = this.queries.getNodesByIds(imports.map((edge) => edge.target)); + for (const edge of imports) { + const target = targets.get(edge.target); + if (target?.kind !== 'file' || (target.language !== 'glsl' && target.language !== 'hlsl')) continue; + const firstLine = current.firstLine ?? edge.line; + if (target.filePath === focalNode.filePath) { + if (firstLine !== undefined) rootSites.set(root, firstLine); + queue.length = 0; + break; + } + if (!visited.has(target.filePath)) queue.push({ filePath: target.filePath, firstLine }); + } + } + } + return { roots, files, rootSites }; + } + private getImpactRecursive( nodeId: string, maxDepth: number, currentDepth: number, nodes: Map, edges: Edge[], - visited: Set + visited: Set, + shaderContext: ShaderImpactContext | null, ): void { // Mark visited before the depth check so a node collected at the depth // boundary still lands in `visited`. Otherwise it could sit in `nodes` but @@ -575,7 +749,7 @@ export class GraphTraverser { nodes.set(childNode.id, childNode); edges.push(edge); // Recurse into children at the same depth (they're part of the same symbol) - this.getImpactRecursive(childNode.id, maxDepth, currentDepth, nodes, edges, visited); + this.getImpactRecursive(childNode.id, maxDepth, currentDepth, nodes, edges, visited, shaderContext); } } } @@ -593,6 +767,14 @@ export class GraphTraverser { for (const edge of incomingEdges) { const sourceNode = sources.get(edge.source); if (!sourceNode) continue; + if (shaderContext && (sourceNode.language === 'glsl' || sourceNode.language === 'hlsl')) { + if (!shaderContext.files.has(sourceNode.filePath)) continue; + const metadata = edge.metadata as Record | undefined; + if (metadata?.synthesizedBy === 'shader-context-variant' && Array.isArray(metadata.contextRoots)) { + const overlaps = metadata.contextRoots.some((root) => typeof root === 'string' && shaderContext.roots.has(root)); + if (!overlaps) continue; + } + } // Record the dependency edge unconditionally. The gate used to also gate // edge collection (`!nodes.has(...)`), so a second incoming edge into a // node already collected via another path was silently dropped from @@ -601,7 +783,7 @@ export class GraphTraverser { edges.push(edge); if (!visited.has(sourceNode.id)) { nodes.set(sourceNode.id, sourceNode); - this.getImpactRecursive(sourceNode.id, maxDepth, currentDepth + 1, nodes, edges, visited); + this.getImpactRecursive(sourceNode.id, maxDepth, currentDepth + 1, nodes, edges, visited, shaderContext); } } } diff --git a/src/index.ts b/src/index.ts index eacf3128a..1e000d885 100644 --- a/src/index.ts +++ b/src/index.ts @@ -565,6 +565,7 @@ export class CodeGraph { try { this.queries.setMetadata('indexed_with_version', CodeGraphPackageVersion); this.queries.setMetadata('indexed_with_extraction_version', String(EXTRACTION_VERSION)); + this.queries.setMetadata('last_indexed_at', String(Date.now())); } catch { /* metadata is advisory — never fail an index over it */ } } @@ -798,11 +799,15 @@ export class CodeGraph { // rows the sync just wrote are fine). Batched + yielding — sync can // run on the daemon's liveness-watchdog thread (#850/#1091). try { - if (vocabWasEmpty && this.queries.getNodeAndEdgeCount().nodes > 0) { + if (vocabWasEmpty && this.queries.getNodeAndEdgeCount().nodes > 0) { await this.rebuildNameSegmentVocab(); } } catch { /* vocab is advisory — never fail a sync over it */ } + if (result.filesAdded > 0 || result.filesModified > 0 || result.filesRemoved > 0) { + try { this.queries.setMetadata('last_indexed_at', String(Date.now())); } catch { /* advisory */ } + } + return result; } finally { this.fileLock.release(); @@ -1016,6 +1021,15 @@ export class CodeGraph { return this.queries.getUnresolvedReferencesCount(); } + /** Attempted references with no target, exposed so status is semantically honest. */ + getFailedReferenceStats(): { + total: number; + byLanguage: Record; + byKind: Record; + } { + return this.queries.getFailedReferenceStats(); + } + /** * Get detected frameworks in the project */ diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts index 367be138e..d21757e2b 100644 --- a/src/mcp/tools.ts +++ b/src/mcp/tools.ts @@ -126,6 +126,27 @@ export function normalizeQuerySpelling(query: string): string { ); } +interface SourceLocationHint { + file: string; + line: number; +} + +function sourceLocationHints(query: string): SourceLocationHint[] { + const pattern = /(?:^|\s)([A-Za-z0-9_./\\-]+\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh))(?::(\d+)|\s+(?:at\s+)?line\s+(\d+)|\s*\(\s*line\s+(\d+)\s*\))/gi; + const hints: SourceLocationHint[] = []; + const seen = new Set(); + for (const match of query.matchAll(pattern)) { + const file = match[1]!.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase(); + const line = Number(match[2] ?? match[3] ?? match[4]); + if (!Number.isFinite(line) || line <= 0) continue; + const key = `${file}:${line}`; + if (seen.has(key)) continue; + seen.add(key); + hints.push({ file, line }); + } + return hints; +} + /** * Calculate the recommended number of codegraph_explore calls based on project size. * Larger codebases need more exploration calls to cover their surface area, @@ -574,6 +595,10 @@ export const tools: ToolDefinition[] = [ type: 'string', description: 'Narrow to the definition in this file (path or suffix) when several same-named symbols exist (e.g. one UserService per app in a monorepo)', }, + line: { + type: 'number', + description: 'Narrow to the definition starting at, or containing, this 1-based line (use with file for repeated local lambdas)', + }, limit: { type: 'number', description: 'Maximum number of callers to return (default: 20)', @@ -599,6 +624,10 @@ export const tools: ToolDefinition[] = [ type: 'string', description: 'Narrow to the definition in this file (path or suffix) when several same-named symbols exist', }, + line: { + type: 'number', + description: 'Narrow to the definition starting at, or containing, this 1-based line (use with file for repeated local lambdas)', + }, limit: { type: 'number', description: 'Maximum number of callees to return (default: 20)', @@ -624,6 +653,10 @@ export const tools: ToolDefinition[] = [ type: 'string', description: 'Narrow to the definition in this file (path or suffix) when several same-named symbols exist', }, + line: { + type: 'number', + description: 'Narrow to the definition starting at, or containing, this 1-based line (use with file for repeated local lambdas)', + }, depth: { type: 'number', description: 'How many levels of dependencies to traverse (default: 2)', @@ -1530,11 +1563,12 @@ export class ToolHandler { * (filePath, qualifiedName), so same-file overloads stay together while * unrelated same-named classes across a monorepo's apps (#764: one * `UserService` per NestJS app) are kept apart. Optionally narrowed by a - * `file` path/suffix first. + * `file` path/suffix and then a precise source `line` when supplied. */ private groupDefinitions( nodes: Node[], - fileFilter: string | undefined + fileFilter: string | undefined, + lineFilter: number | undefined ): { groups: Node[][]; filteredOut: boolean } { let pool = nodes; let filteredOut = false; @@ -1547,11 +1581,32 @@ export class ToolHandler { pool = narrowed; } else { filteredOut = true; + // Preserve the historical fallback for ordinary application symbols, + // but never fall back for shader names: a non-matching shader file hint + // must not resurrect an unrelated `main`/`luminance` definition. A line + // is also an exact location request, so never fall back past it. + if (lineFilter || pool.some((n) => n.language === 'glsl' || n.language === 'hlsl')) pool = []; + } + } + if (lineFilter && pool.length > 0) { + const exact = pool.filter((n) => n.startLine === lineFilter); + const containing = exact.length > 0 + ? exact + : pool.filter((n) => n.startLine <= lineFilter && n.endLine >= lineFilter); + if (containing.length > 0) pool = containing; + else { + pool = []; + filteredOut = true; } } const byDef = new Map(); for (const n of pool) { - const key = `${n.filePath}|${n.qualifiedName}`; + // Repeated local C++ lambdas can share both file and qualified name (two + // block-local `bufferResource` declarations in one method). Their source + // line is part of their identity; overloads and conditional variants keep + // the historical grouping behavior. + const lineIdentity = n.decorators?.includes('cpp:lambda') ? `|${n.startLine}` : ''; + const key = `${n.filePath}|${n.qualifiedName}${lineIdentity}`; const group = byDef.get(key); if (group) group.push(n); else byDef.set(key, [n]); @@ -1576,13 +1631,22 @@ export class ToolHandler { const cg = this.getCodeGraph(args.projectPath as string | undefined); const limit = clamp((args.limit as number) || 20, 1, 100); const fileFilter = typeof args.file === 'string' ? args.file : undefined; + const lineFilter = typeof args.line === 'number' && Number.isFinite(args.line) && args.line >= 1 + ? Math.floor(args.line) + : undefined; const allMatches = this.findAllSymbols(cg, symbol); if (allMatches.nodes.length === 0) { return this.textResult(`Symbol "${symbol}" not found in the codebase`); } - const { groups, filteredOut } = this.groupDefinitions(allMatches.nodes, fileFilter); + const { groups, filteredOut } = this.groupDefinitions(allMatches.nodes, fileFilter, lineFilter); + if (filteredOut && groups.length === 0) { + const location = fileFilter + ? `file "${fileFilter}"${lineFilter ? ` at line ${lineFilter}` : ''}` + : `line ${lineFilter}`; + return this.textResult(`Symbol "${symbol}" not found at ${location}`); + } const filterNote = filteredOut ? `\n\n> **Note:** no definition of "${symbol}" matches file "${fileFilter}" — showing all definitions instead.` : ''; @@ -1612,7 +1676,7 @@ export class ToolHandler { } // A successful `file` narrowing makes the multi-symbol aggregation note // stale — suppress it. - const note = fileFilter && !filteredOut ? '' : allMatches.note; + const note = (fileFilter || lineFilter) && !filteredOut ? '' : allMatches.note; const formatted = this.formatNodeList(callers.slice(0, limit), `Callers of ${symbol}`, labels) + note + filterNote; return this.textResult(this.truncateOutput(formatted)); } @@ -1621,7 +1685,7 @@ export class ToolHandler { // agent never mistakes one app's callers for another's. Narrow with // `file` to focus a single definition. const lines: string[] = [ - `**Callers of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\`)**`, + `**Callers of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\` + \`line\`)**`, ]; for (const group of groups) { const { callers, labels } = collect(group); @@ -1649,13 +1713,22 @@ export class ToolHandler { const cg = this.getCodeGraph(args.projectPath as string | undefined); const limit = clamp((args.limit as number) || 20, 1, 100); const fileFilter = typeof args.file === 'string' ? args.file : undefined; + const lineFilter = typeof args.line === 'number' && Number.isFinite(args.line) && args.line >= 1 + ? Math.floor(args.line) + : undefined; const allMatches = this.findAllSymbols(cg, symbol); if (allMatches.nodes.length === 0) { return this.textResult(`Symbol "${symbol}" not found in the codebase`); } - const { groups, filteredOut } = this.groupDefinitions(allMatches.nodes, fileFilter); + const { groups, filteredOut } = this.groupDefinitions(allMatches.nodes, fileFilter, lineFilter); + if (filteredOut && groups.length === 0) { + const location = fileFilter + ? `file "${fileFilter}"${lineFilter ? ` at line ${lineFilter}` : ''}` + : `line ${lineFilter}`; + return this.textResult(`Symbol "${symbol}" not found at ${location}`); + } const filterNote = filteredOut ? `\n\n> **Note:** no definition of "${symbol}" matches file "${fileFilter}" — showing all definitions instead.` : ''; @@ -1684,14 +1757,14 @@ export class ToolHandler { } // A successful `file` narrowing makes the multi-symbol aggregation note // stale — suppress it. - const note = fileFilter && !filteredOut ? '' : allMatches.note; + const note = (fileFilter || lineFilter) && !filteredOut ? '' : allMatches.note; const formatted = this.formatNodeList(callees.slice(0, limit), `Callees of ${symbol}`, labels) + note + filterNote; return this.textResult(this.truncateOutput(formatted)); } // Multiple DISTINCT definitions (#764): per-definition sections. const lines: string[] = [ - `**Callees of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\`)**`, + `**Callees of ${symbol} — ${groups.length} distinct definitions (narrow with \`file\` + \`line\`)**`, ]; for (const group of groups) { const { callees, labels } = collect(group); @@ -1719,13 +1792,22 @@ export class ToolHandler { const cg = this.getCodeGraph(args.projectPath as string | undefined); const depth = clamp((args.depth as number) || 2, 1, 10); const fileFilter = typeof args.file === 'string' ? args.file : undefined; + const lineFilter = typeof args.line === 'number' && Number.isFinite(args.line) && args.line >= 1 + ? Math.floor(args.line) + : undefined; const allMatches = this.findAllSymbols(cg, symbol); if (allMatches.nodes.length === 0) { return this.textResult(`Symbol "${symbol}" not found in the codebase`); } - const { groups, filteredOut } = this.groupDefinitions(allMatches.nodes, fileFilter); + const { groups, filteredOut } = this.groupDefinitions(allMatches.nodes, fileFilter, lineFilter); + if (filteredOut && groups.length === 0) { + const location = fileFilter + ? `file "${fileFilter}"${lineFilter ? ` at line ${lineFilter}` : ''}` + : `line ${lineFilter}`; + return this.textResult(`Symbol "${symbol}" not found at ${location}`); + } const filterNote = filteredOut ? `\n\n> **Note:** no definition of "${symbol}" matches file "${fileFilter}" — showing all definitions instead.` : ''; @@ -1752,7 +1834,7 @@ export class ToolHandler { // Single definition (or same-file overloads): the familiar merged report. if (groups.length === 1) { - const formatted = this.formatImpact(symbol, impactOf(groups[0]!)) + (fileFilter && !filteredOut ? "" : allMatches.note) + filterNote; + const formatted = this.formatImpact(symbol, impactOf(groups[0]!)) + ((fileFilter || lineFilter) && !filteredOut ? "" : allMatches.note) + filterNote; return this.textResult(this.truncateOutput(formatted)); } @@ -1760,7 +1842,7 @@ export class ToolHandler { // merging unrelated same-named classes (one UserService per monorepo app) // overstated impact and confused agents. Narrow with `file`. const sections: string[] = [ - `**Impact of ${symbol} — ${groups.length} distinct definitions (each with its own blast radius; narrow with \`file\`)**`, + `**Impact of ${symbol} — ${groups.length} distinct definitions (each with its own blast radius; narrow with \`file\` + \`line\`)**`, ]; for (const group of groups) { const head = group[0]!; @@ -1879,6 +1961,100 @@ export class ToolHandler { * whose qualifiedName contains another named token (`PmsProductServiceImpl::list`), * dropping unrelated `OmsOrderService::list`. */ + private sourceLocationCallPaths(cg: CodeGraph, query: string): Array<{ source: Node; target: Node; edge: Edge }> { + const CALLABLE = new Set(['method', 'function', 'component', 'constructor']); + const paths: Array<{ source: Node; target: Node; edge: Edge }> = []; + const seen = new Set(); + const files = cg.getFiles().map((file) => file.path); + for (const hint of sourceLocationHints(query)) { + const matchingFiles = files.filter((filePath) => { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + return normalized === hint.file || normalized.endsWith(`/${hint.file}`) || + (!hint.file.includes('/') && normalized.endsWith(`/${hint.file}`)); + }); + if (matchingFiles.length !== 1) continue; + let candidates: Node[] = []; + try { + candidates = cg.getNodesInFile(matchingFiles[0]!).filter((node) => + CALLABLE.has(node.kind) && node.startLine <= hint.line && (node.endLine ?? node.startLine) >= hint.line + ); + } catch { continue; } + if (candidates.length === 0) continue; + const source = [...candidates].sort((a, b) => + ((a.endLine ?? a.startLine) - a.startLine) - ((b.endLine ?? b.startLine) - b.startLine) || + Math.abs(a.startLine - hint.line) - Math.abs(b.startLine - hint.line) + )[0]!; + for (const callee of cg.getCallees(source.id)) { + if (callee.edge.kind !== 'calls' || callee.edge.line !== hint.line) continue; + const key = `${source.id}>${callee.node.id}@${hint.line}`; + if (seen.has(key)) continue; + seen.add(key); + paths.push({ source, target: callee.node, edge: callee.edge }); + } + } + return paths; + } + + /** + * Resolve direct calls between symbol names explicitly present in the query. + * This lets a proven edge disambiguate a large family such as shader `main` + * without relaxing the normal natural-language stopword safeguards. + */ + private queryDirectCallPaths(cg: CodeGraph, query: string): Array<{ source: Node; target: Node; edge: Edge }> { + const CALLABLE = new Set(['method', 'function', 'component', 'constructor']); + const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh)$/i; + const fileHints = [...query.matchAll(/(?:^|\s)([A-Za-z0-9_./\\-]+\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh))\b/gi)] + .map((match) => match[1]!.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()); + const matchesHint = (filePath: string): boolean => { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + return fileHints.some((hint) => + normalized === hint || normalized.endsWith(`/${hint}`) || + (!hint.includes('/') && normalized.endsWith(`/${hint}`)) + ); + }; + const tokens = [...new Set( + query.split(/(?:->|[\s,()[\]\u2192]+)/) + .map((token) => token.replace(/^[`'"{]+|[`'"}:;?!]+$/g, '').trim()) + .filter((token) => token.length >= 3 && !FILE_EXT.test(token) && /^[A-Za-z_$][\w$]*(?:(?:::|\.)[\w$]+)*$/.test(token)) + )].slice(0, 16); + const families = tokens.map((token) => { + const qualified = /::|\./.test(token); + const nodes = (qualified ? this.findAllSymbols(cg, token).nodes : cg.getNodesByName(token)) + .filter((node) => CALLABLE.has(node.kind)); + return { token, nodes }; + }).filter((family) => family.nodes.length > 0); + if (families.length < 2) return []; + + const paths = new Map(); + const addPath = (source: Node, target: Node, edge: Edge) => { + if (edge.kind !== 'calls') return; + if (fileHints.length > 0 && !matchesHint(source.filePath) && !matchesHint(target.filePath)) return; + paths.set(`${source.id}\0${target.id}`, { source, target, edge }); + }; + for (let left = 0; left < families.length; left++) { + for (let right = left + 1; right < families.length; right++) { + const a = families[left]!; + const b = families[right]!; + // One endpoint must be narrow enough to inspect without turning a generic + // natural-language query into an all-to-all overload scan. + if (a.nodes.length > 6 && b.nodes.length > 6) continue; + const inspected = a.nodes.length <= b.nodes.length ? a : b; + const other = inspected === a ? b : a; + const otherIds = new Set(other.nodes.map((node) => node.id)); + for (const node of inspected.nodes) { + for (const callee of cg.getCallees(node.id)) { + if (otherIds.has(callee.node.id)) addPath(node, callee.node, callee.edge); + } + for (const caller of cg.getCallers(node.id)) { + if (otherIds.has(caller.node.id)) addPath(caller.node, node, caller.edge); + } + if (paths.size > 12) return []; + } + } + } + return [...paths.values()]; + } + private buildFlowFromNamedSymbols(cg: CodeGraph, query: string): { text: string; pathNodeIds: Set; namedNodeIds: Set; uniqueNamedNodeIds: Set; spineCallSites: Map } { // spineCallSites: for each spine node, the line where it CALLS the next hop — // lets the source assembler window an oversize spine method (e.g. n8n's 962-line @@ -1890,13 +2066,44 @@ export class ToolHandler { // names (Class.method / Class::method) — the agent's most precise input, // resolved exactly by findAllSymbols. (The old strip mangled Class.method // into Class, throwing the method away.) - const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl)$/i; + const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh)$/i; + const fileHints = [...query.matchAll(/(?:^|\s)([A-Za-z0-9_./\\-]+\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh))\b/gi)].map((m) => m[1]!.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()); + const sourceCallPaths = this.sourceLocationCallPaths(cg, query); + const namedDirectCallPaths = this.queryDirectCallPaths(cg, query).filter((path) => + !sourceCallPaths.some((sourcePath) => sourcePath.source.id === path.source.id && sourcePath.target.id === path.target.id) + ); + const pinnedCallPaths = [...sourceCallPaths, ...namedDirectCallPaths]; + const matchesHint = (filePath: string): boolean => { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + return fileHints.some((hint) => + normalized === hint || normalized.endsWith(`/${hint}`) || + (!hint.includes('/') && normalized.endsWith(`/${hint}`)) + ); + }; + const shaderContextFiles = new Set(); + const shaderRoots = cg.getFiles().filter((file) => + (file.language === 'glsl' || file.language === 'hlsl') && matchesHint(file.path) + ).map((file) => file.path); + const shaderQueue = [...shaderRoots]; + for (let index = 0; index < shaderQueue.length && index < 4096; index++) { + const filePath = shaderQueue[index]!; + if (shaderContextFiles.has(filePath)) continue; + shaderContextFiles.add(filePath); + const fileNode = cg.getNodesInFile(filePath).find((node) => node.kind === 'file'); + if (!fileNode) continue; + for (const edge of cg.getOutgoingEdges(fileNode.id).filter((candidate) => candidate.kind === 'imports')) { + const target = cg.getNode(edge.target); + if (target?.kind === 'file' && (target.language === 'glsl' || target.language === 'hlsl') && !shaderContextFiles.has(target.filePath)) { + shaderQueue.push(target.filePath); + } + } + } const tokens = [...new Set( query.split(/[\s,()[\]]+/) .map((t) => t.replace(FILE_EXT, '').trim()) .filter((t) => t.length >= 3 && /^[A-Za-z_$][\w$]*(?:(?:::|\.)[\w$]+)*$/.test(t)) )].slice(0, 16); - if (tokens.length < 2) return EMPTY; + if (tokens.length < 2 && pinnedCallPaths.length === 0) return EMPTY; // Pool of name SEGMENTS (Class + method from every token) used to // disambiguate an ambiguous SIMPLE name: keep a candidate only if its // CONTAINER class is itself named in the query. @@ -1926,11 +2133,25 @@ export class ToolHandler { // fed only to the dynamic-dispatch-links scan below. const dynNamed = new Map(); const DYN_KINDS = new Set(['constant', 'variable', 'field', 'property']); + for (const path of pinnedCallPaths) { + named.set(path.source.id, path.source); + named.set(path.target.id, path.target); + uniqueNamedNodeIds.add(path.source.id); + uniqueNamedNodeIds.add(path.target.id); + } const hasHeuristicEdge = (id: string): boolean => [...cg.getCallers(id), ...cg.getCallees(id)].some(({ edge }) => edge.provenance === 'heuristic'); for (const t of tokens) { const hits = this.findAllSymbols(cg, t).nodes; - const cands = hits.filter((n) => CALLABLE.has(n.kind)); + const allCands = hits.filter((n) => CALLABLE.has(n.kind)); + const hintedCands = fileHints.length ? allCands.filter((n) => matchesHint(n.filePath)) : []; + const shaderCands = allCands.filter((n) => n.language === 'glsl' || n.language === 'hlsl'); + const contextualCands = shaderContextFiles.size > 0 + ? shaderCands.filter((n) => shaderContextFiles.has(n.filePath)) + : []; + const cands = shaderContextFiles.size > 0 && shaderCands.length > 0 + ? contextualCands + : (hintedCands.length > 0 ? hintedCands : allCands); tokenFamily.set(t, cands); // A qualified or otherwise-specific name (<=3 hits) keeps all; an // ambiguous simple name keeps only candidates whose container is named. @@ -1966,6 +2187,15 @@ export class ToolHandler { } if (named.size > 40) break; } + const sourcePinnedPaths: Array> = sourceCallPaths.map((path) => [ + { node: path.source, edge: null }, + { node: path.target, edge: path.edge }, + ]); + const namedPinnedPaths: Array> = namedDirectCallPaths.map((path) => [ + { node: path.source, edge: null }, + { node: path.target, edge: path.edge }, + ]); + const pinnedPaths = [...sourcePinnedPaths, ...namedPinnedPaths]; // Surface synthesized (heuristic) edges incident to a named symbol — INCLUDING // the non-callable CONSTANT endpoints in `dynNamed`. `skipInChain` drops a hop // already shown in the rendered main chain (a 2-node chain renders nothing, so a @@ -2039,8 +2269,11 @@ export class ToolHandler { chain.reverse(); if (!best || chain.length > best.length) best = chain; } - const hasMain = !!best && best.length >= 3; - const pathIds = new Set((best ?? []).map((s) => s.node.id)); + const hasMainChain = !!best && best.length >= 3; + const hasPinnedFlow = pinnedPaths.length > 0; + const hasMain = hasMainChain || hasPinnedFlow; + const pathIds = new Set((hasMainChain ? best ?? [] : []).map((s) => s.node.id)); + for (const path of pinnedPaths) for (const step of path) pathIds.add(step.node.id); // Where each spine node calls the NEXT hop (best[i+1].edge is the edge from // best[i] → best[i+1]; its line is the call site inside best[i]'s body). Lets // the assembler window an oversize spine method to the call instead of dumping it. @@ -2049,6 +2282,10 @@ export class ToolHandler { const ln = best[i + 1]?.edge?.line; if (ln && ln > 0 && !spineCallSites.has(best[i]!.node.id)) spineCallSites.set(best[i]!.node.id, ln); } + for (const path of pinnedPaths) { + const line = path[1]?.edge?.line; + if (line && !spineCallSites.has(path[0]!.node.id)) spineCallSites.set(path[0]!.node.id, line); + } // Dynamic-boundary scan (#687) — fires ONLY when the flow the agent // asked about did not fully connect: some token resolved to nodes but @@ -2073,7 +2310,7 @@ export class ToolHandler { } if (uncovered.length > 0) { const scanList: Node[] = []; - if (hasMain) scanList.push(best![best!.length - 1]!.node); + if (hasMainChain) scanList.push(best![best!.length - 1]!.node); scanList.push(...uncovered.sort((a, b) => (uniqueNamedNodeIds.has(b.id) ? 1 : 0) - (uniqueNamedNodeIds.has(a.id) ? 1 : 0))); boundaryText = this.buildDynamicBoundaries(cg, scanList, named); @@ -2115,7 +2352,29 @@ export class ToolHandler { if (!hasMain && synthLines.length === 0 && !boundaryText && !polyText) return EMPTY; const out: string[] = []; - if (hasMain) { + if (sourcePinnedPaths.length > 0) { + out.push('**Flow (call paths at the source locations you queried)**', ''); + for (const path of sourcePinnedPaths) { + for (let i = 0; i < path.length; i++) { + const step = path[i]!; + if (step.edge) out.push(` ↓ ${step.edge.kind} @${path[0]!.node.filePath}:${step.edge.line}`); + out.push(`${i + 1}. ${step.node.name} (${step.node.filePath}:${step.node.startLine})`); + } + out.push(''); + } + } + if (namedPinnedPaths.length > 0) { + out.push('**Flow (direct call paths among the symbols you queried)**', ''); + for (const path of namedPinnedPaths) { + for (let i = 0; i < path.length; i++) { + const step = path[i]!; + if (step.edge) out.push(` ↓ ${step.edge.kind} @${path[0]!.node.filePath}:${step.edge.line}`); + out.push(`${i + 1}. ${step.node.name} (${step.node.filePath}:${step.node.startLine})`); + } + out.push(''); + } + } + if (hasMainChain) { out.push('**Flow (call path among the symbols you queried)**', ''); for (let i = 0; i < best!.length; i++) { const step = best![i]!; @@ -2535,6 +2794,98 @@ export class ToolHandler { return this.textResult(`No relevant code found for "${query}"`); } + // An explicit source filename is a hard disambiguator. Keep the initial + // context anchored to that file before relevance ranking can let a common + // symbol such as shader `main` pull in unrelated entry points. When the + // query also asks for includes/dependencies, retain the transitive shader + // include closure so explore can answer the multi-file question directly. + const explicitShaderRootFiles = new Set(); + const explicitShaderContextFiles = new Set(); + const requestedShaderIncludeFiles = new Set(); + const explicitFileHints = [...query.matchAll(/(?:^|\s)([A-Za-z0-9_./\\-]+\.(?:glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh))\b/gi)] + .map((m) => m[1]!.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()); + if (explicitFileHints.length > 0) { + const expandIncludes = /\b(?:include|included|imports?|dependencies|dependency|closure|transitive|glsl files?|hlsl files?|shader files?)\b/i.test(query); + const isShaderPath = (filePath: string) => /\.(?:glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh)$/i.test(filePath); + const matchesHint = (filePath: string) => { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + return explicitFileHints.some((hint) => + normalized === hint || + normalized.endsWith(`/${hint}`) || + (!hint.includes('/') && normalized.endsWith(`/${hint}`)) + ); + }; + const hintedPaths = cg.getFiles().map((f) => f.path).filter(matchesHint); + for (const filePath of hintedPaths) explicitShaderRootFiles.add(filePath); + const hintedNodes = hintedPaths.flatMap((filePath) => { + try { return cg.getNodesInFile(filePath); } catch { return []; } + }); + const contextQueue = hintedNodes.filter((node) => node.kind === 'file'); + for (let index = 0; index < contextQueue.length && index < 4096; index++) { + const current = contextQueue[index]!; + if (explicitShaderContextFiles.has(current.filePath)) continue; + explicitShaderContextFiles.add(current.filePath); + let outgoing: Edge[] = []; + try { outgoing = cg.getOutgoingEdges(current.id); } catch { continue; } + for (const edge of outgoing) { + if (edge.kind !== 'imports') continue; + const target = cg.getNode(edge.target); + if (!target || target.kind !== 'file' || !isShaderPath(target.filePath) || explicitShaderContextFiles.has(target.filePath)) continue; + contextQueue.push(target); + } + } + const keptById = new Map(); + for (const n of [...subgraph.nodes.values()].filter((n) => matchesHint(n.filePath))) keptById.set(n.id, n); + for (const n of hintedNodes) keptById.set(n.id, n); + const includeRootIds = new Set(); + if (expandIncludes) { + const queue = hintedNodes.filter((node) => node.kind === 'file'); + const visited = new Set(queue.map((node) => node.id)); + const includeFileCap = Math.min(20, Math.max(maxFiles, 8)); + while (queue.length > 0 && visited.size < includeFileCap) { + const current = queue.shift()!; + let outgoing: Edge[] = []; + try { outgoing = cg.getOutgoingEdges(current.id); } catch { continue; } + for (const edge of outgoing) { + if (edge.kind !== 'imports' || visited.size >= includeFileCap) continue; + const target = cg.getNode(edge.target); + if (!target || target.kind !== 'file' || !isShaderPath(target.filePath)) continue; + if (visited.has(target.id)) continue; + visited.add(target.id); + includeRootIds.add(target.id); + requestedShaderIncludeFiles.add(target.filePath); + queue.push(target); + let importedNodes: Node[] = []; + try { importedNodes = cg.getNodesInFile(target.filePath); } catch { /* stale file row */ } + for (const node of importedNodes) keptById.set(node.id, node); + } + } + } + const kept = [...keptById.values()]; + if (kept.length > 0) { + const keepIds = new Set(kept.map((n) => n.id)); + subgraph.nodes = new Map(kept.map((n) => [n.id, n])); + const edgeByKey = new Map(); + const retainEdge = (edge: Edge) => { + if (!keepIds.has(edge.source) || !keepIds.has(edge.target)) return; + edgeByKey.set(`${edge.source}\0${edge.target}\0${edge.kind}`, edge); + }; + for (const edge of subgraph.edges) retainEdge(edge); + if (expandIncludes) { + for (const node of kept) { + let outgoing: Edge[] = []; + try { outgoing = cg.getOutgoingEdges(node.id); } catch { continue; } + for (const edge of outgoing) retainEdge(edge); + } + } + subgraph.edges = [...edgeByKey.values()]; + subgraph.roots = [...new Set([ + ...subgraph.roots.filter((id) => keepIds.has(id)), + ...includeRootIds, + ])]; + } + } + // Graph-aware glue: findRelevantContext builds the subgraph from name/text // search, so a method that BRIDGES named symbols — e.g. App.tsx's // triggerRender, which calls the named triggerUpdate — is never a search hit @@ -2585,7 +2936,16 @@ export class ToolHandler { // overloads (the query also named the type) all earn it. (#1064) const tierSeedIds = new Set(); { - const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl)$/i; + const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh)$/i; + const fileHints = [...query.matchAll(/(?:^|\s)([A-Za-z0-9_./\\-]+\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte|astro|erl|hrl|glsl|vert|frag|comp|geom|tesc|tese|rgen|rmiss|rchit|rahit|rint|rcall|mesh|task|glslfx|hlsl|hlsli|fx|fxh))\b/gi)].map((m) => m[1]!.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase()); + const matchesHint = (filePath: string): boolean => { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + return fileHints.some((hint) => + normalized === hint || + normalized.endsWith(`/${hint}`) || + (!hint.includes('/') && normalized.endsWith(`/${hint}`)) + ); + }; const CALLABLE = new Set(['method', 'function', 'component', 'constructor']); const isTestPath = (p: string) => /(^|\/)(tests?|specs?|__tests__|testdata|mocks?|fixtures?)\//i.test(p) || /\.(test|spec)\.[a-z]+$/i.test(p); const bodyLines = (n: Node) => Math.max(0, (n.endLine ?? n.startLine) - n.startLine); @@ -2651,15 +3011,21 @@ export class ToolHandler { const isQual = /[.\/]|::/.test(t); const raw = isQual ? this.findAllSymbols(cg, t).nodes : cg.getNodesByName(t); let cands = raw - .filter((n) => CALLABLE.has(n.kind) && !isTestPath(n.filePath)) + .filter((n) => CALLABLE.has(n.kind) && (!isTestPath(n.filePath) || matchesHint(n.filePath) || explicitShaderContextFiles.has(n.filePath))) .sort((a, b) => (bodyLines(b) > 1 ? 1 : 0) - (bodyLines(a) > 1 ? 1 : 0) || bodyLines(b) - bodyLines(a)); + const shaderCands = cands.filter((n) => n.language === 'glsl' || n.language === 'hlsl'); + if (explicitShaderContextFiles.size > 0 && shaderCands.length > 0) { + cands = shaderCands.filter((n) => explicitShaderContextFiles.has(n.filePath)); + } + const hintedCands = fileHints.length ? cands.filter((n) => matchesHint(n.filePath)) : []; + if (hintedCands.length > 0) cands = hintedCands; // Bare lowercase words only seed defs their query-siblings corroborate // (see the NL-stopword guard above). Filtering CANDS (not picks) applies // the guard uniformly to both branches below, including the >3-def // single-pick fallback — an uncorroborated bare `run` must not tier its // most-substantive namesake any more than a 1-def `check` may. if (!isPreciseToken(t)) { - cands = cands.filter((n) => coNamedInFile(t, n.filePath)); + cands = cands.filter((n) => matchesHint(n.filePath) || coNamedInFile(t, n.filePath)); } // A specific name (<=3 defs) injects all its defs. An overloaded name // (`validate` = 10, `request` = 44) would flood the subgraph, so inject @@ -2698,7 +3064,102 @@ export class ToolHandler { } } + // Exact source locations are stronger than fuzzy relevance. Reattach the + // proven full-graph call at that line before Relationships and file ranking + // run, so natural queries such as `shader.rgen line 228` surface the same + // edge that callers already knows about. + const explicitSourceCallPaths = this.sourceLocationCallPaths(cg, query); + const explicitNamedCallPaths = this.queryDirectCallPaths(cg, query).filter((path) => + !explicitSourceCallPaths.some((sourcePath) => sourcePath.source.id === path.source.id && sourcePath.target.id === path.target.id) + ); + const priorityCalls = new Map(); + for (const path of [...explicitSourceCallPaths, ...explicitNamedCallPaths]) { + subgraph.nodes.set(path.source.id, path.source); + subgraph.nodes.set(path.target.id, path.target); + namedSeedIds.add(path.source.id); + namedSeedIds.add(path.target.id); + tierSeedIds.add(path.source.id); + tierSeedIds.add(path.target.id); + subgraph.roots.push(path.source.id); + priorityCalls.set(`${path.edge.source}\0${path.edge.target}\0${path.edge.kind}`, path.edge); + } + // A direct edge between symbols the query named is itself part of the + // requested answer. Fuzzy context gathering may omit that edge even after + // finding both endpoints; restore only exact named-to-named calls. + for (const sourceId of namedSeedIds) { + let outgoing: Edge[] = []; + try { outgoing = cg.getOutgoingEdges(sourceId); } catch { continue; } + for (const edge of outgoing) { + if (edge.kind !== 'calls' || !namedSeedIds.has(edge.target)) continue; + const key = `${edge.source}\0${edge.target}\0${edge.kind}`; + if (!priorityCalls.has(key)) priorityCalls.set(key, edge); + } + } + if (priorityCalls.size > 0) { + subgraph.edges = [ + ...priorityCalls.values(), + ...subgraph.edges.filter((edge) => + !priorityCalls.has(`${edge.source}\0${edge.target}\0${edge.kind}`) + ), + ]; + subgraph.roots = [...new Set(subgraph.roots)]; + } + // Step 2: Group nodes by file, score by relevance + // Include closures can contain large utility libraries with hundreds of + // declarations. Render the symbols that actually connect files, plus two + // intra-file hops from that cross-file spine, instead of letting one dense + // include consume the whole explore response. + const shaderIncludeRelevantNodeIds = new Set(); + const shaderClosureLineHints = new Map(); + const addShaderLineHint = (filePath: string, line: number | undefined) => { + if (!line || line <= 0) return; + const lines = shaderClosureLineHints.get(filePath) ?? []; + if (!lines.includes(line)) lines.push(line); + shaderClosureLineHints.set(filePath, lines); + }; + for (const location of sourceLocationHints(query)) { + for (const filePath of explicitShaderRootFiles) { + const normalized = filePath.replace(/\\/g, '/').toLowerCase(); + if (normalized === location.file || normalized.endsWith(`/${location.file}`) || + (!location.file.includes('/') && normalized.endsWith(`/${location.file}`))) { + addShaderLineHint(filePath, location.line); + } + } + } + if (requestedShaderIncludeFiles.size > 0) { + for (const edge of subgraph.edges) { + if (edge.kind === 'contains') continue; + const source = subgraph.nodes.get(edge.source); + const target = subgraph.nodes.get(edge.target); + if (!source || !target || source.filePath === target.filePath) continue; + if (requestedShaderIncludeFiles.has(source.filePath)) { + shaderIncludeRelevantNodeIds.add(source.id); + addShaderLineHint(source.filePath, edge.line ?? source.startLine); + } + if (requestedShaderIncludeFiles.has(target.filePath)) { + shaderIncludeRelevantNodeIds.add(target.id); + addShaderLineHint(target.filePath, target.startLine); + } + if (explicitShaderRootFiles.has(source.filePath)) addShaderLineHint(source.filePath, edge.line ?? source.startLine); + } + for (let depth = 0; depth < 2; depth++) { + const add = new Set(); + for (const edge of subgraph.edges) { + if (edge.kind !== 'calls' && edge.kind !== 'references') continue; + const source = subgraph.nodes.get(edge.source); + const target = subgraph.nodes.get(edge.target); + if (!source || !target || source.filePath !== target.filePath || !requestedShaderIncludeFiles.has(source.filePath)) continue; + if (shaderIncludeRelevantNodeIds.has(source.id)) add.add(target.id); + if (shaderIncludeRelevantNodeIds.has(target.id)) add.add(source.id); + } + for (const id of add) shaderIncludeRelevantNodeIds.add(id); + } + for (const id of shaderIncludeRelevantNodeIds) { + const node = subgraph.nodes.get(id); + if (node) addShaderLineHint(node.filePath, node.startLine); + } + } const fileGroups = new Map(); const entryNodeIds = new Set([...subgraph.roots, ...namedSeedIds]); @@ -2744,6 +3205,9 @@ export class ToolHandler { for (const node of subgraph.nodes.values()) { // Skip import/export nodes — they add noise without information if (node.kind === 'import' || node.kind === 'export') continue; + if (requestedShaderIncludeFiles.has(node.filePath) + && node.kind !== 'file' + && !shaderIncludeRelevantNodeIds.has(node.id)) continue; // SECURITY (#383): never render the on-disk source of a config-leaf // (Spring application.{yml,properties} key) — its line is `key = `, // so whole-file/cluster rendering here would push secrets into context @@ -2772,6 +3236,10 @@ export class ToolHandler { // Only include files that have entry points or nodes directly connected to entry points let relevantFiles = [...fileGroups.entries()].filter(([, group]) => group.score >= 3); + for (const filePath of new Set([...requestedShaderIncludeFiles, ...explicitShaderRootFiles])) { + const group = fileGroups.get(filePath); + if (group && !relevantFiles.some(([candidate]) => candidate === filePath)) relevantFiles.push([filePath, group]); + } // Extract query terms for relevance checking const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length >= 3); @@ -2870,6 +3338,8 @@ export class ToolHandler { const n = subgraph.nodes.get(id); if (n) entryFiles.add(n.filePath); } + for (const filePath of requestedShaderIncludeFiles) entryFiles.add(filePath); + for (const filePath of explicitShaderRootFiles) entryFiles.add(filePath); // Buried-rescue pass (#1064): surface a named method's signature type ONLY // when its file is genuinely buried — near-zero graph mass AND not lexically // matched. That is the invisible case (grpc's `DialOption` → `dialoptions.go`, @@ -2935,6 +3405,7 @@ export class ToolHandler { // buried-rescue pass) is the lexically-dissimilar answer; give it the named // tier so it isn't buried under files that merely share surface words (#1064). for (const fp of changeSurfaceFiles) namedSeedFiles.add(fp); + for (const fp of explicitShaderRootFiles) namedSeedFiles.add(fp); // Multi-term corroboration tier: a file that is BOTH (a) an entry/central file // (a search root, named seed, or graph-central hub — i.e. structurally part of @@ -3133,7 +3604,8 @@ export class ToolHandler { // after the build + validators-exec files and never reached the ranked-in // validate-logic file (Alamofire's Validation.swift). const fileNecessary = group.nodes.some(n => - entryNodeIds.has(n.id) || flow.pathNodeIds.has(n.id) || flow.uniqueNamedNodeIds.has(n.id)); + entryNodeIds.has(n.id) || flow.pathNodeIds.has(n.id) || flow.uniqueNamedNodeIds.has(n.id)) + || requestedShaderIncludeFiles.has(filePath); if (!fileNecessary && totalChars > budget.maxOutputChars * 0.9) continue; const absPath = validatePathWithinRoot(projectRoot, filePath); @@ -3351,7 +3823,7 @@ export class ToolHandler { const n = cg.getNode(id); if (n && n.filePath === filePath && n.startLine > 0 && n.endLine > 0) rangeNodes.set(id, n); } - const ranges: Array<{ start: number; end: number; name: string; kind: string; importance: number; spine: boolean; spineCallLine?: number }> = [...rangeNodes.values()] + const ranges: Array<{ start: number; end: number; name: string; kind: string; importance: number; spine: boolean; spineStartLine?: number; spineCallLine?: number }> = [...rangeNodes.values()] // Drop whole-file envelope nodes (containers covering >50% of the file). .filter(n => !(ENVELOPE_KINDS.has(n.kind) && (n.endLine - n.startLine + 1) > fileLines.length * 0.5)) .map(n => { @@ -3365,7 +3837,8 @@ export class ToolHandler { // processRunExecutionData, the named flow ENTRY at L1562, is a large // low-density method that lost the budget to denser blocks and got cut, so // the agent Read it back — the very thing explore exists to prevent). - return { start: n.startLine, end: n.endLine, name: n.name, kind: n.kind, importance, spine: flow.pathNodeIds.has(n.id), spineCallLine: flow.spineCallSites.get(n.id) }; + const spine = flow.pathNodeIds.has(n.id); + return { start: n.startLine, end: n.endLine, name: n.name, kind: n.kind, importance, spine, spineStartLine: spine ? n.startLine : undefined, spineCallLine: flow.spineCallSites.get(n.id) }; }); // Add edge source locations in this file — captures template references @@ -3391,8 +3864,10 @@ export class ToolHandler { if (ranges.length === 0) continue; - const gapThreshold = budget.gapThreshold; - const clusters: Array<{ start: number; end: number; symbols: string[]; score: number; maxImportance: number; hasSpine: boolean; spineCallLine?: number }> = []; + const shaderClosureView = (requestedShaderIncludeFiles.size > 0 || shaderClosureLineHints.has(filePath)) + && (explicitShaderRootFiles.has(filePath) || requestedShaderIncludeFiles.has(filePath)); + const gapThreshold = shaderClosureView ? 1 : budget.gapThreshold; + const clusters: Array<{ start: number; end: number; symbols: string[]; score: number; maxImportance: number; hasSpine: boolean; spineStartLine?: number; spineCallLine?: number }> = []; let current = { start: ranges[0]!.start, end: ranges[0]!.end, @@ -3400,6 +3875,7 @@ export class ToolHandler { score: ranges[0]!.importance, maxImportance: ranges[0]!.importance, hasSpine: ranges[0]!.spine, + spineStartLine: ranges[0]!.spineStartLine, spineCallLine: ranges[0]!.spineCallLine, }; @@ -3411,6 +3887,7 @@ export class ToolHandler { current.score += r.importance; current.maxImportance = Math.max(current.maxImportance, r.importance); current.hasSpine = current.hasSpine || r.spine; + current.spineStartLine = current.spineStartLine ?? r.spineStartLine; current.spineCallLine = current.spineCallLine ?? r.spineCallLine; } else { clusters.push(current); @@ -3421,6 +3898,7 @@ export class ToolHandler { score: r.importance, maxImportance: r.importance, hasSpine: r.spine, + spineStartLine: r.spineStartLine, spineCallLine: r.spineCallLine, }; } @@ -3447,7 +3925,29 @@ export class ToolHandler { // the spine's call still appears in context. const OVERSIZE_SPINE_LINES = 200; const SPINE_WINDOW = 28; // lines each side of the next-hop call site - const buildSection = (c: { start: number; end: number; hasSpine?: boolean; spineCallLine?: number }): string => { + const buildSection = (c: { start: number; end: number; hasSpine?: boolean; spineStartLine?: number; spineCallLine?: number }): string => { + if (shaderClosureView && (c.end - c.start + 1) > 160) { + const hints = (shaderClosureLineHints.get(filePath) ?? []) + .filter((line) => line >= c.start && line <= c.end) + .sort((a, b) => a - b) + .filter((line, index, all) => index === 0 || line - all[index - 1]! >= 20) + .slice(0, 4); + if (hints.length > 0) { + anyFileTrimmed = true; + const parts: string[] = []; + const headEnd = Math.min(c.end, c.start + 4); + const head = fileLines.slice(c.start - 1, headEnd).join('\n'); + parts.push(withLineNumbers ? numberSourceLines(head, c.start) : head); + for (const line of hints) { + const winStart = Math.max(c.start, line - 10); + const winEnd = Math.min(c.end, line + 10); + if (winStart <= headEnd + 1) continue; + const win = fileLines.slice(winStart - 1, winEnd).join('\n'); + parts.push(withLineNumbers ? numberSourceLines(win, winStart) : win); + } + return parts.join(GAP_MARKER); + } + } if (c.hasSpine && c.spineCallLine && (c.end - c.start + 1) > OVERSIZE_SPINE_LINES) { const call = c.spineCallLine; const winStart = Math.max(c.start, call - SPINE_WINDOW); @@ -3464,6 +3964,15 @@ export class ToolHandler { parts.push(withLineNumbers ? numberSourceLines(win, winStart) : win); return parts.join(GAP_MARKER); } + if (c.hasSpine && c.spineStartLine && (c.end - c.start + 1) > OVERSIZE_SPINE_LINES) { + // A terminal path endpoint has no next-hop call site. Window around its + // definition instead of letting one enormous callee consume the entire + // response and displace the caller source from the same proven path. + const winStart = Math.max(c.start, c.spineStartLine - 3); + const winEnd = Math.min(c.end, c.spineStartLine + SPINE_WINDOW * 2); + const win = fileLines.slice(winStart - 1, winEnd).join('\n'); + return withLineNumbers ? numberSourceLines(win, winStart) : win; + } const startIdx = Math.max(0, c.start - 1 - contextPadding); const endIdx = Math.min(fileLines.length, c.end + contextPadding); const slice = fileLines.slice(startIdx, endIdx).join('\n'); @@ -4491,7 +5000,11 @@ export class ToolHandler { return { nodes, note: '' }; } } - let results = cg.searchNodes(symbol, { limit: 50 }); + // Bare names need the uncapped exact index: shader entry points commonly + // all share `main`, and the FTS limit would hide the file the query named. + let results = /^[A-Za-z_$][\w$]*$/.test(symbol) + ? cg.getNodesByName(symbol).map((node) => ({ node })) + : cg.searchNodes(symbol, { limit: 50 }); // Mirror the fallback in `findSymbol` for qualified queries — FTS // strips colons, so a module-qualified lookup needs a second pass @@ -4610,7 +5123,10 @@ export class ToolHandler { for (const [file, nodes] of byFile) { lines.push(`**${file}:**`); // Compact: inline list - const nodeList = nodes.map(n => `${n.name}:${n.startLine}`).join(', '); + const nodeList = nodes.map((n) => { + const conditions = n.decorators?.filter((d) => d.startsWith('pp:')); + return `${n.name}:${n.startLine}${conditions?.length ? ` [${conditions.join(' && ')}]` : ''}`; + }).join(', '); lines.push(nodeList); lines.push(''); } diff --git a/src/powershell-dependencies.ts b/src/powershell-dependencies.ts new file mode 100644 index 000000000..e39bb8bd2 --- /dev/null +++ b/src/powershell-dependencies.ts @@ -0,0 +1,29 @@ +export interface PowershellDependencySpec { + kind: 'module' | 'dot-source'; + relativePath: string; + index: number; + signature: string; +} + +function relativeScriptPath(args: string): string | undefined { + return args.match(/Join-Path\s+\$PSScriptRoot\s+['"]([^'"]+)['"]/i)?.[1] + ?? args.match(/['"]\$PSScriptRoot[\\/]([^'"]+)['"]/i)?.[1] + ?? args.match(/^['"]([^'"]+\.(?:psm1|psd1|ps1))['"]/i)?.[1] + ?? args.match(/^([^\s)]+\.(?:psm1|psd1|ps1))\b/i)?.[1]; +} + +/** Static local PowerShell dependencies that establish a callable scope. */ +export function extractPowershellDependencies(source: string): PowershellDependencySpec[] { + const result: PowershellDependencySpec[] = []; + for (const match of source.matchAll(/^[ \t]*Import-Module\s+([^\r\n]+)/gim)) { + const relativePath = relativeScriptPath(match[1]!); + if (!relativePath) continue; + result.push({ kind: 'module', relativePath, index: match.index ?? 0, signature: match[0].trim() }); + } + for (const match of source.matchAll(/^[ \t]*\.[ \t]+([^\r\n]+)/gim)) { + const relativePath = relativeScriptPath(match[1]!); + if (!relativePath) continue; + result.push({ kind: 'dot-source', relativePath, index: match.index ?? 0, signature: match[0].trim() }); + } + return result.sort((a, b) => a.index - b.index); +} diff --git a/src/project-config.ts b/src/project-config.ts index ec388daf7..003ce61e3 100644 --- a/src/project-config.ts +++ b/src/project-config.ts @@ -67,6 +67,10 @@ export interface ProjectConfig { * wins. Absent/empty (the default) forces nothing in. */ include?: string[]; + /** Project-relative include roots used by GLSL/HLSL preprocessors. */ + shaderIncludePaths?: string[]; + /** Shader include prefix aliases, e.g. "$TOOLS": "pxr/imaging". */ + shaderIncludeAliases?: Record; } /** Parsed, validated view of a project's `codegraph.json`. */ @@ -75,6 +79,8 @@ interface ParsedConfig { includeIgnored: string[]; exclude: string[]; include: string[]; + shaderIncludePaths: string[]; + shaderIncludeAliases: Record; } interface CacheEntry { @@ -97,6 +103,8 @@ const EMPTY_CONFIG: ParsedConfig = Object.freeze({ includeIgnored: Object.freeze([]) as unknown as string[], exclude: Object.freeze([]) as unknown as string[], include: Object.freeze([]) as unknown as string[], + shaderIncludePaths: Object.freeze([]) as unknown as string[], + shaderIncludeAliases: Object.freeze({}) as Record, }); /** @@ -149,15 +157,19 @@ function parseConfig(file: string): ParsedConfig { const includeIgnored = extractIncludeIgnored(parsed, file); const exclude = extractExclude(parsed, file); const include = extractInclude(parsed, file); + const shaderIncludePaths = extractShaderIncludePaths(parsed, file); + const shaderIncludeAliases = extractShaderIncludeAliases(parsed, file); if ( extensions === EMPTY_EXTENSIONS && includeIgnored.length === 0 && exclude.length === 0 && - include.length === 0 + include.length === 0 && + shaderIncludePaths.length === 0 && + Object.keys(shaderIncludeAliases).length === 0 ) { return EMPTY_CONFIG; } - return { extensions, includeIgnored, exclude, include }; + return { extensions, includeIgnored, exclude, include, shaderIncludePaths, shaderIncludeAliases }; } /** @@ -264,6 +276,42 @@ function extractInclude(parsed: object, file: string): string[] { return out; } +function extractShaderIncludePaths(parsed: object, file: string): string[] { + const raw = (parsed as ProjectConfig).shaderIncludePaths; + if (raw === undefined) return []; + if (!Array.isArray(raw)) { + logWarn(`Ignoring "shaderIncludePaths" in ${PROJECT_CONFIG_FILENAME}: must be an array of project-relative paths`, { file }); + return []; + } + const out: string[] = []; + for (const entry of raw) { + if (typeof entry !== 'string' || !entry.trim()) { + logWarn(`Ignoring a "shaderIncludePaths" entry in ${PROJECT_CONFIG_FILENAME}: every path must be a non-empty string`, { file }); + continue; + } + out.push(entry.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '')); + } + return [...new Set(out)]; +} + +function extractShaderIncludeAliases(parsed: object, file: string): Record { + const raw = (parsed as ProjectConfig).shaderIncludeAliases; + if (raw === undefined) return {}; + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { + logWarn(`Ignoring "shaderIncludeAliases" in ${PROJECT_CONFIG_FILENAME}: must be an object of prefix-to-path mappings`, { file }); + return {}; + } + const out: Record = {}; + for (const [prefix, target] of Object.entries(raw)) { + if (!prefix.trim() || typeof target !== 'string' || !target.trim()) { + logWarn(`Ignoring shader include alias "${prefix}" in ${PROJECT_CONFIG_FILENAME}: aliases require non-empty string targets`, { file }); + continue; + } + out[prefix.trim()] = target.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, ''); + } + return out; +} + /** * Load the parsed `codegraph.json` for a project, mtime-cached. A missing or * malformed file yields the zero-config default. One `stat` (and at most one @@ -338,6 +386,14 @@ export function loadIncludePatterns(rootDir: string): string[] { return loadParsedConfig(rootDir).include; } +export function loadShaderIncludePaths(rootDir: string): string[] { + return loadParsedConfig(rootDir).shaderIncludePaths; +} + +export function loadShaderIncludeAliases(rootDir: string): Record { + return loadParsedConfig(rootDir).shaderIncludeAliases; +} + /** Test/maintenance hook: forget cached config (e.g. after rewriting it in a test). */ export function clearProjectConfigCache(): void { cache.clear(); diff --git a/src/resolution/callback-synthesizer.ts b/src/resolution/callback-synthesizer.ts index d3beed745..3f4100cf6 100644 --- a/src/resolution/callback-synthesizer.ts +++ b/src/resolution/callback-synthesizer.ts @@ -28,6 +28,7 @@ import { isGeneratedFile } from '../extraction/generated-detection'; import { stripCommentsForRegex } from './strip-comments'; import { cFnPointerDispatchEdges } from './c-fnptr-synthesizer'; import { goframeRouteEdges } from './goframe-synthesizer'; +import { shaderIntegrationEdges } from './shader-synthesizer'; import { createYielder, type MaybeYield } from './cooperative-yield'; const REGISTRAR_NAME = /^(on[A-Z]\w*|subscribe|addListener|addEventListener|register|watch|listen|addCallback)$/; @@ -3540,6 +3541,7 @@ export async function synthesizeCallbackEdges(queries: QueryBuilder, ctx: Resolu const cFnPtrEdges = has('c', 'cpp') ? await cFnPointerDispatchEdges(queries, ctx, yieldToLoop) : NONE; await yieldToLoop(); __mark('cFnPtrEdges'); const goframeEdges = has('go') ? await goframeRouteEdges(ctx, yieldToLoop) : NONE; await yieldToLoop(); __mark('goframeEdges'); const nixOptionEdges = has('nix') ? await nixOptionPathEdges(queries, yieldToLoop) : NONE; await yieldToLoop(); __mark('nixOptionEdges'); + const shaderEdges = has('glsl', 'hlsl') ? await shaderIntegrationEdges(queries, ctx, yieldToLoop) : NONE; await yieldToLoop(); __mark('shaderEdges'); const merged: Edge[] = []; const seen = new Set(); @@ -3580,6 +3582,7 @@ export async function synthesizeCallbackEdges(queries: QueryBuilder, ctx: Resolu ...cFnPtrEdges, ...goframeEdges, ...nixOptionEdges, + ...shaderEdges, ]) { const key = `${e.source}>${e.target}`; if (seen.has(key)) continue; diff --git a/src/resolution/import-resolver.ts b/src/resolution/import-resolver.ts index bbb1303b3..306fb27ce 100644 --- a/src/resolution/import-resolver.ts +++ b/src/resolution/import-resolver.ts @@ -10,6 +10,7 @@ import { Language, Node } from '../types'; import { UnresolvedRef, ResolvedRef, ResolutionContext, ImportMapping, ReExport } from './types'; import { applyAliases } from './path-aliases'; import { resolveWorkspaceImport } from './workspace-packages'; +import { extractPowershellDependencies } from '../powershell-dependencies'; /** * Extension resolution order by language @@ -1204,10 +1205,54 @@ function pickClosestJvmCandidate(candidates: Node[], fromPath: string): Node { return best; } +function powershellDependencyPaths(filePath: string, context: ResolutionContext): Array<{ filePath: string; exportedOnly: boolean }> { + const source = context.readFile(filePath); + if (!source) return []; + const files = new Set(context.getAllFiles()); + const result = new Map(); + for (const dependency of extractPowershellDependencies(source)) { + const candidate = path.posix.normalize(path.posix.join( + path.posix.dirname(filePath.replace(/\\/g, '/')), + dependency.relativePath.replace(/\\/g, '/'), + )); + if (candidate.startsWith('../') || !files.has(candidate)) continue; + const exportedOnly = dependency.kind === 'module'; + result.set(candidate, (result.get(candidate) ?? true) && exportedOnly); + } + return [...result].map(([dependencyPath, exportedOnly]) => ({ filePath: dependencyPath, exportedOnly })); +} + +function powershellExports(modulePath: string, context: ResolutionContext): Set | null { + const source = context.readFile(modulePath); + const match = source?.match(/Export-ModuleMember\b[^\r\n]*?-Function\s+([^\r\n]+)/i); + if (!match) return null; + return new Set(match[1]!.split(',').map((name) => name.trim().replace(/^['"]|['"]$/g, '').toLowerCase()).filter(Boolean)); +} + export function resolveViaImport( ref: UnresolvedRef, context: ResolutionContext ): ResolvedRef | null { + if (ref.language === 'powershell') { + if (ref.referenceKind === 'imports') { + const target = context.getNodesInFile(ref.referenceName).find((node) => node.kind === 'file'); + return target ? { original: ref, targetNodeId: target.id, confidence: 0.98, resolvedBy: 'file-path' } : null; + } + if (ref.referenceKind === 'calls') { + const matches: Node[] = []; + for (const dependency of powershellDependencyPaths(ref.filePath, context)) { + const exports = dependency.exportedOnly ? powershellExports(dependency.filePath, context) : null; + if (exports && !exports.has(ref.referenceName.toLowerCase())) continue; + for (const node of context.getNodesInFile(dependency.filePath)) { + if (node.kind === 'function' && node.name.toLowerCase() === ref.referenceName.toLowerCase()) matches.push(node); + } + } + return matches.length === 1 + ? { original: ref, targetNodeId: matches[0]!.id, confidence: 0.97, resolvedBy: 'import' } + : null; + } + } + // C/C++ #include references — resolve directly to the included file // (file→file edge), bypassing symbol lookup. The extractor emits these // with `referenceKind: 'imports'` and `referenceName: ` diff --git a/src/resolution/index.ts b/src/resolution/index.ts index e956794e7..ea7b2c4bb 100644 --- a/src/resolution/index.ts +++ b/src/resolution/index.ts @@ -27,6 +27,7 @@ import { loadWorkspacePackages, type WorkspacePackages } from './workspace-packa import { logDebug } from '../errors'; import type { ReExport } from './types'; import { LRUCache } from './lru-cache'; +import { isShaderLanguage, ShaderResolver } from './shader-resolver'; /** Node kinds that can declare supertypes (extends/implements). */ const SUPERTYPE_BEARING_KINDS = new Set([ @@ -252,6 +253,7 @@ export class ReferenceResolver { private goModule: GoModule | null | undefined = undefined; // Monorepo workspace member packages. Same lazy/immutable convention. private workspacePackages: WorkspacePackages | null | undefined = undefined; + private shaderResolver: ShaderResolver; constructor(projectRoot: string, queries: QueryBuilder) { this.projectRoot = projectRoot; @@ -274,6 +276,7 @@ export class ReferenceResolver { this.methodMatchCache = new LRUCache(limit); this.context = this.createContext(); + this.shaderResolver = new ShaderResolver(this.context); } /** @@ -372,6 +375,7 @@ export class ReferenceResolver { this.knownNames = null; this.knownFiles = null; this.cachesWarmed = false; + this.shaderResolver.clear(); } /** `readFile` through the LRU content cache (null = read failed, also cached). */ @@ -615,6 +619,7 @@ export class ReferenceResolver { const resolved: ResolvedRef[] = []; const unresolved: UnresolvedRef[] = []; const byMethod: Record = {}; + let resolvedRefCount = 0; // Convert to our internal format, using denormalized fields when available const refs: UnresolvedRef[] = unresolvedRefs.map((ref) => ({ @@ -623,6 +628,7 @@ export class ReferenceResolver { referenceKind: ref.referenceKind, line: ref.line, column: ref.column, + candidates: ref.candidates, filePath: ref.filePath || this.getFilePathFromNodeId(ref.fromNodeId), language: ref.language || this.getLanguageFromNodeId(ref.fromNodeId), rowId: ref.rowId, @@ -633,11 +639,12 @@ export class ReferenceResolver { for (let i = 0; i < refs.length; i++) { const ref = refs[i]!; // Array index is guaranteed to be in bounds - const result = this.resolveOne(ref); + const results = this.resolveTargets(ref); - if (result) { - resolved.push(result); - byMethod[result.resolvedBy] = (byMethod[result.resolvedBy] || 0) + 1; + if (results.length > 0) { + resolved.push(...results); + resolvedRefCount++; + byMethod[results[0]!.resolvedBy] = (byMethod[results[0]!.resolvedBy] || 0) + 1; } else { unresolved.push(ref); } @@ -662,7 +669,7 @@ export class ReferenceResolver { unresolved, stats: { total: refs.length, - resolved: resolved.length, + resolved: resolvedRefCount, unresolved: unresolved.length, byMethod, }, @@ -769,6 +776,11 @@ export class ReferenceResolver { return null; } + // Shader symbols live in a translation unit assembled from explicit includes. + // Never fall through to global path-proximity matching: duplicate helpers and + // resources are common across shader variants, so ambiguity must stay silent. + if (isShaderLanguage(ref.language)) return this.shaderResolver.resolve(ref); + // CFML component paths in inheritance (#1152): `extends="coldbox.system.web. // Controller"` names the supertype by its dot-separated path (or `extends= // "../base"` by relative file path) — the graph indexes the class under its @@ -847,6 +859,28 @@ export class ReferenceResolver { if (razorResult) return razorResult; } + // A named local C++ lambda is lexically closer than anything introduced by + // an included header. Resolve that scoped child before import resolution can + // capture the bare call with an unrelated same-named method declaration. + if (ref.language === 'cpp' && ref.referenceKind === 'calls') { + const lexical = this.gateLanguage(matchReference(ref, this.context), ref); + if (lexical) { + const caller = this.queries.getNodeById(ref.fromNodeId); + const target = this.queries.getNodeById(lexical.targetNodeId); + if ( + caller && target && + (caller.kind === 'function' || caller.kind === 'method') && + target.kind === 'function' && + target.filePath === ref.filePath && + (target.qualifiedName.startsWith(`${caller.qualifiedName}::`) || + target.qualifiedName.startsWith(`${caller.name}::`) || + target.qualifiedName.includes(`::${caller.name}::`)) + ) { + return lexical; + } + } + } + const candidates: ResolvedRef[] = []; // Strategy 1: Try framework-specific resolution. Cross-language bridges @@ -887,6 +921,21 @@ export class ReferenceResolver { : null; } + // PowerShell has no ambient project-wide function namespace. A command is + // callable from the same script or from an explicitly imported module; + // never let the generic matcher jump to a same-named function in an + // unrelated audit script. + if (ref.language === 'powershell' && ref.referenceKind === 'calls') { + const local = this.gateLanguage(matchReference(ref, this.context), ref); + if (local) { + const target = this.queries.getNodeById(local.targetNodeId); + if (target?.filePath === ref.filePath) return local; + } + return candidates.length > 0 + ? candidates.reduce((best, curr) => curr.confidence > best.confidence ? curr : best) + : null; + } + // Strategy 3: Try name matching let nameResult = this.gateLanguage(matchReference(ref, this.context), ref); // Nix has no ambient cross-file namespace — a callee binds lexically @@ -942,6 +991,18 @@ export class ReferenceResolver { ); } + private resolveTargets(ref: UnresolvedRef): ResolvedRef[] { + const single = this.resolveOne(ref); + if (single) return [single]; + if (!isShaderLanguage(ref.language)) return []; + return this.shaderResolver.getConditionalCallTargets(ref).map((target) => ({ + original: ref, + targetNodeId: target.id, + confidence: 0.95, + resolvedBy: 'import' as const, + })); + } + /** * Create edges from resolved references */ @@ -1217,6 +1278,7 @@ export class ReferenceResolver { const resolved: ResolvedRef[] = []; const unresolved: UnresolvedRef[] = []; const byMethod: Record = {}; + let resolvedRefCount = 0; for (const raw of batch) { const ref: UnresolvedRef = { @@ -1225,14 +1287,16 @@ export class ReferenceResolver { referenceKind: raw.referenceKind, line: raw.line, column: raw.column, + candidates: raw.candidates, filePath: raw.filePath || this.getFilePathFromNodeId(raw.fromNodeId), language: raw.language || this.getLanguageFromNodeId(raw.fromNodeId), rowId: raw.rowId, }; - const result = this.resolveOne(ref); - if (result) { - resolved.push(result); - byMethod[result.resolvedBy] = (byMethod[result.resolvedBy] || 0) + 1; + const results = this.resolveTargets(ref); + if (results.length > 0) { + resolved.push(...results); + resolvedRefCount++; + byMethod[results[0]!.resolvedBy] = (byMethod[results[0]!.resolvedBy] || 0) + 1; } else { unresolved.push(ref); } @@ -1244,7 +1308,7 @@ export class ReferenceResolver { unresolved, stats: { total: batch.length, - resolved: resolved.length, + resolved: resolvedRefCount, unresolved: unresolved.length, byMethod, }, @@ -1839,6 +1903,11 @@ export class ReferenceResolver { private gateLanguage(result: ResolvedRef | null, ref: UnresolvedRef): ResolvedRef | null { if (!result) return result; + if ( + (ref.language === 'cmake' || ref.language === 'powershell') && + result.resolvedBy === 'file-path' && + (ref.referenceKind === 'references' || ref.referenceKind === 'imports') + ) return result; const tgt = this.getLanguageFromNodeId(result.targetNodeId); if (!tgt || !ref.language) return result; if ((ref.referenceKind === 'references' || ref.referenceKind === 'function_ref') && !sameLanguageFamily(tgt, ref.language)) return null; diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 9e77d630f..c1ef7cd71 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -374,6 +374,47 @@ export function matchByExactName( }; } + // A named local C++ lambda is indexed as a function nested under its + // enclosing function/method (`recordGraph::bufferResource`). Prefer that + // lexical child over same-named project methods before path proximity can + // select an unrelated API symbol. Multiple children remain unresolved. + if (ref.language === 'cpp' && ref.referenceKind === 'calls' && context.getNodeById) { + const caller = context.getNodeById(ref.fromNodeId); + if (caller && (caller.kind === 'function' || caller.kind === 'method')) { + const prefix = `${caller.qualifiedName}::`; + const nameScope = `::${caller.name}::`; + const local = candidates.filter((candidate) => + candidate.kind === 'function' && + candidate.filePath === ref.filePath && + (candidate.qualifiedName.startsWith(prefix) || + candidate.qualifiedName.startsWith(`${caller.name}::`) || + candidate.qualifiedName.includes(nameScope)) + ); + if (local.length === 1) { + return { + original: ref, + targetNodeId: local[0]!.id, + confidence: 0.98, + resolvedBy: 'exact-match', + }; + } + } + } + + // A bare C/C++ method call such as `std::string::empty()` may arrive as + // just `empty` when the extractor cannot retain the receiver chain. Never + // guess among unrelated method declarations in that case; a false edge is + // worse than an unresolved standard-library call. Same-file methods remain + // eligible because the enclosing type is a strong local signal. + if ( + (ref.language === 'cpp' || ref.language === 'c') && + ref.referenceKind === 'calls' && + candidates.every((candidate) => candidate.kind === 'method') && + !candidates.some((candidate) => candidate.filePath === ref.filePath) + ) { + return null; + } + // Ubiquitous-name ceiling (#999): above it, picking one target among K // same-named defs by directory proximity is unreliable AND O(K) per ref — the // quadratic behind the "Resolving refs" wedge on theme/SDK-vendoring repos. @@ -1551,6 +1592,11 @@ export function matchMethodCall( if (typedMatch) { return typedMatch; } + // C++ receiver typing is strong evidence. If the inferred type's method + // is not indexed (most commonly `std::string::empty()`), do not discard + // that evidence and fall through to a unique same-named project method. + // Missing is safer than manufacturing a cross-type call edge. + if (ref.language === 'cpp') return null; } } @@ -1649,6 +1695,12 @@ export function matchMethodCall( } } + // `empty()` is ubiquitous on standard-library containers and strings, whose + // declarations are intentionally outside the project index. An untyped C++ + // receiver must not capture the only project method named `empty`; precise + // class-name and inferred-type strategies already had their chance above. + if (ref.language === 'cpp' && dotMatch && methodName === 'empty') return null; + // Strategy 3: Find methods by name across the codebase, match by receiver // name similarity with the containing class. Handles abbreviated variable // names like permissionEngine → PermissionRuleEngine. diff --git a/src/resolution/shader-resolver.ts b/src/resolution/shader-resolver.ts new file mode 100644 index 000000000..49ef5c149 --- /dev/null +++ b/src/resolution/shader-resolver.ts @@ -0,0 +1,496 @@ +import * as path from 'path'; +import type { Language, Node, ReferenceKind } from '../types'; +import { loadShaderIncludeAliases, loadShaderIncludePaths } from '../project-config'; +import type { ResolutionContext, ResolvedRef, UnresolvedRef } from './types'; + +export const SHADER_EXTENSIONS = [ + '.glsl', '.vert', '.frag', '.comp', '.geom', '.tesc', '.tese', + '.rgen', '.rmiss', '.rchit', '.rahit', '.rint', '.rcall', '.mesh', '.task', + '.glslfx', '.hlsl', '.hlsli', '.fx', '.fxh', +] as const; + +export function isShaderLanguage(language: Language): language is 'glsl' | 'hlsl' { + return language === 'glsl' || language === 'hlsl'; +} + +function normalizeRelative(root: string, candidate: string): string | null { + const absolute = path.resolve(root, candidate); + const relative = path.relative(root, absolute); + if (!relative || relative === '.') return null; + if (relative.startsWith('..') || path.isAbsolute(relative)) return null; + return relative.replace(/\\/g, '/'); +} + +function withExtensionFallback(value: string): string[] { + if (path.posix.extname(value)) return [value]; + return [value, ...SHADER_EXTENSIONS.map((ext) => value + ext)]; +} + +export function resolveShaderPath( + includePath: string, + fromFile: string, + context: ResolutionContext, + line?: number, +): string | null { + const projectRoot = context.getProjectRoot(); + const aliases = loadShaderIncludeAliases(projectRoot); + const roots = loadShaderIncludePaths(projectRoot); + let requested = includePath.replace(/\\/g, '/').trim(); + if (!requested) return null; + + for (const [prefix, target] of Object.entries(aliases)) { + if (requested === prefix || requested.startsWith(prefix + '/')) { + requested = path.posix.join(target, requested.slice(prefix.length).replace(/^\//, '')); + const aliased = withExtensionFallback(requested) + .map((p) => normalizeRelative(projectRoot, p)) + .filter((p): p is string => !!p && context.fileExists(p)); + return aliased.length === 1 ? aliased[0]! : null; + } + } + + const sourceLine = line ? context.getFileLines?.(fromFile)?.[line - 1] : undefined; + const angleInclude = !!sourceLine && /#\s*(?:include|import)\s* path.posix.join(root, requested))); + tiers.push([requested]); + + for (const tier of tiers) { + const matches = new Set(); + for (const base of tier) { + for (const candidate of withExtensionFallback(base)) { + const relative = normalizeRelative(projectRoot, candidate); + if (relative && context.fileExists(relative)) matches.add(relative); + } + } + if (matches.size === 1) return [...matches][0]!; + if (matches.size > 1) return null; + } + + // Compiler include roots are often declared only in CMake and are not always + // mirrored into codegraph.json. A project-wide suffix is still precise when + // it identifies exactly one indexed file (`Rtxdi/DI/Reservoir.hlsli` -> + // `Libraries/Rtxdi/Include/Rtxdi/DI/Reservoir.hlsli`). Ambiguous application + // bridges deliberately remain unresolved. + const suffixMatches = new Set(); + for (const requestedPath of withExtensionFallback(requested)) { + const normalized = requestedPath.replace(/^\.\//, '').replace(/^(?:\.\.\/)+/, ''); + const comparableRequest = process.platform === 'win32' ? normalized.toLowerCase() : normalized; + for (const file of context.getAllFiles()) { + const candidate = file.replace(/\\/g, '/'); + if (!normalizeRelative(projectRoot, candidate)) continue; + const comparableCandidate = process.platform === 'win32' ? candidate.toLowerCase() : candidate; + if (comparableCandidate === comparableRequest || comparableCandidate.endsWith('/' + comparableRequest)) { + suffixMatches.add(candidate); + } + } + } + if (suffixMatches.size === 1) return [...suffixMatches][0]!; + return null; +} + +function targetKinds(kind: ReferenceKind): Set | null { + if (kind === 'calls' || kind === 'function_ref') return new Set(['function', 'method']); + if (kind === 'extends' || kind === 'implements') return new Set(['struct', 'class', 'interface', 'type_alias']); + return null; +} + +function normalizedSignature(node: Node): string | null { + let signature = node.signature?.replace(/\s+/g, ' ').trim(); + if (!signature) return null; + // Parameter names differ across equivalent shader variants (`seed` vs + // `state`) but their type/qualifier contract is what makes a call safe. + signature = signature.replace( + /\b(inout|out|in|const)\s+([A-Za-z_]\w*(?:\s*\[\s*\])?)\s+[A-Za-z_]\w*/g, + '$1 $2 _', + ); + signature = signature.replace(/\b([A-Za-z_]\w*(?:\s*\[\s*\])?)\s+[A-Za-z_]\w*(?=\s*(?:,|\)))/g, '$1 _'); + // Some bridge declarations omit parameter names entirely. Normalize those + // to the same contract as a named parameter without erasing real type data. + signature = signature.replace( + /([,(]\s*)((?:inout|out|in|const)\s+)?([A-Za-z_]\w*(?:\s*\[\s*\])?)(?=\s*(?:,|\)))/g, + '$1$2$3 _', + ); + return signature ? signature : null; +} + +function callArityHint(ref: UnresolvedRef): number | null { + for (const candidate of ref.candidates ?? []) { + const match = candidate.match(/^arity:(\d+)$/); + if (match) return Number(match[1]); + } + return null; +} + +function signatureParameters(node: Node): { params: string[]; variadic: boolean } | null { + const signature = node.signature; + if (!signature) return null; + const open = signature.indexOf('('); + if (open < 0) return null; + let paren = 0; + let square = 0; + let angle = 0; + let current = ''; + const params: string[] = []; + for (let i = open + 1; i < signature.length; i++) { + const char = signature[i]!; + if (char === '(') paren++; + else if (char === ')') { + if (paren === 0) { + if (current.trim()) params.push(current.trim()); + const meaningful = params.filter((param) => param !== 'void'); + const variadic = meaningful.some((param) => param.includes('...')); + return { params: meaningful, variadic }; + } + paren--; + } else if (char === '[') square++; + else if (char === ']') square = Math.max(0, square - 1); + else if (char === '<') angle++; + else if (char === '>') angle = Math.max(0, angle - 1); + if (char === ',' && paren === 0 && square === 0 && angle === 0) { + params.push(current.trim()); + current = ''; + } else { + current += char; + } + } + return null; +} + +function signatureArity(node: Node): { minimum: number; variadic: boolean } | null { + const parsed = signatureParameters(node); + if (!parsed) return null; + return { + minimum: parsed.params.filter((param) => !param.includes('...')).length, + variadic: parsed.variadic, + }; +} + +function callTypeHints(ref: UnresolvedRef): Map { + const hints = new Map(); + for (const candidate of ref.candidates ?? []) { + const match = candidate.match(/^argtype:(\d+):([A-Za-z_]\w*)$/); + if (match) hints.set(Number(match[1]), match[2]!.toLowerCase()); + } + return hints; +} + +function parameterType(param: string): string | null { + const cleaned = param + .replace(/\s*:\s*[A-Za-z_]\w*\s*$/, '') + .replace(/\s*=.*$/, '') + .replace(/\b(?:inout|out|in|const|static|uniform|groupshared)\b/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + if (!cleaned || cleaned.includes('...')) return null; + const tokens = cleaned.split(' '); + const type = tokens.length > 1 ? tokens.slice(0, -1).join(' ') : tokens[0]!; + return type.replace(/\s+/g, '').toLowerCase(); +} + +function applyCallEvidence(candidates: Node[], ref: UnresolvedRef): Node[] { + const arity = callArityHint(ref); + let narrowed = candidates; + if (arity !== null) { + const matching = narrowed.filter((node) => { + const parsed = signatureArity(node); + return parsed && (parsed.variadic ? arity >= parsed.minimum : arity === parsed.minimum); + }); + if (matching.length > 0) narrowed = matching; + } + const typeHints = callTypeHints(ref); + if (typeHints.size > 0) { + const matching = narrowed.filter((node) => { + const parsed = signatureParameters(node); + if (!parsed) return false; + for (const [index, expected] of typeHints) { + const actual = parsed.params[index] ? parameterType(parsed.params[index]!) : null; + if (!actual || actual !== expected) return false; + } + return true; + }); + if (matching.length > 0) narrowed = matching; + } + return narrowed; +} + +export interface ContextualShaderTarget { + node: Node; + contextRoots: string[]; +} + +interface ConditionalBranchFrame { + groupLine: number; + branch: number; +} + +function conditionalBranchesByLine(source: string): ConditionalBranchFrame[][] { + const lines = source.split(/\r?\n/); + const result: ConditionalBranchFrame[][] = Array.from({ length: lines.length + 1 }, () => []); + const stack: ConditionalBranchFrame[] = []; + for (let index = 0; index < lines.length; index++) { + result[index + 1] = stack.map((frame) => ({ ...frame })); + const line = lines[index]!; + if (/^\s*#\s*(?:if|ifdef|ifndef)\b/.test(line)) { + stack.push({ groupLine: index + 1, branch: 0 }); + } else if (/^\s*#\s*(?:elif|else)\b/.test(line) && stack.length > 0) { + stack[stack.length - 1]!.branch++; + } else if (/^\s*#\s*endif\b/.test(line)) { + stack.pop(); + } + } + return result; +} + +export class ShaderResolver { + private closureCache = new Map>(); + private directIncludeCache = new Map(); + private reverseIncludeCache = new Map<'glsl' | 'hlsl', Map>>(); + private contextRootCache = new Map>(); + private conditionalBranchCache = new Map(); + + constructor(private readonly context: ResolutionContext) {} + + clear(): void { + this.closureCache.clear(); + this.directIncludeCache.clear(); + this.reverseIncludeCache.clear(); + this.contextRootCache.clear(); + this.conditionalBranchCache.clear(); + } + + resolve(ref: UnresolvedRef): ResolvedRef | null { + if (!isShaderLanguage(ref.language)) return null; + if (ref.referenceKind === 'imports') { + const file = resolveShaderPath(ref.referenceName, ref.filePath, this.context, ref.line); + if (!file) return null; + const target = this.context.getNodesInFile(file).find((n) => n.kind === 'file'); + return target ? { original: ref, targetNodeId: target.id, confidence: 1, resolvedBy: 'import' } : null; + } + + const kinds = targetKinds(ref.referenceKind); + const roots = this.getContextRoots(ref.filePath, ref.language); + const candidates = applyCallEvidence(this.context.getNodesByName(ref.referenceName).filter((node) => { + if (node.language !== ref.language || node.id === ref.fromNodeId) return false; + if (node.kind === 'file' || node.kind === 'import') return false; + return !kinds || kinds.has(node.kind); + }), ref); + if (candidates.length === 0) return null; + const scored = candidates.map((node) => ({ node, distance: this.contextDistance(node.filePath, ref.language as 'glsl' | 'hlsl', roots) })) + .filter((item) => Number.isFinite(item.distance)); + if (scored.length === 0) { + // Some shared shader headers are injected by the build system rather than + // included textually. If all matching declarations have the same callable + // contract, resolve the equivalent variant deterministically instead of + // leaving every call in that header unresolved. + const signatures = new Set(candidates.map(normalizedSignature)); + if (signatures.size !== 1 || signatures.has(null)) return null; + const target = [...candidates].sort((a, b) => a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine)[0]!; + return { original: ref, targetNodeId: target.id, confidence: 0.72, resolvedBy: 'import' }; + } + const minDepth = Math.min(...scored.map((item) => item.distance)); + const nearest = scored.filter((item) => item.distance === minDepth).map((item) => item.node); + const files = new Set(nearest.map((node) => node.filePath)); + if (files.size !== 1) { + // A shared HLSL library can be compiled under several application bridge + // roots. One reference node therefore legitimately targets one callback + // per translation-unit context; choosing a single file conflates those + // implementations. Leave the ref for contextual edge synthesis. + return null; + } + if (nearest.length > 1) { + // Distinct overload contracts in one closure require type inference we do + // not have. Equivalent conditional declarations can still collapse. + const signatures = new Set(nearest.map(normalizedSignature)); + if (signatures.size !== 1 || signatures.has(null)) return null; + } + const target = [...nearest].sort((a, b) => a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine)[0]!; + return { original: ref, targetNodeId: target.id, confidence: minDepth === 0 ? 0.98 : 0.95, resolvedBy: 'import' }; + } + + /** + * Return one equivalent callable per nearest shader translation-unit context. + * Used after ordinary resolution deliberately leaves a multi-root bridge call + * unresolved; differing signatures stay ambiguous. + */ + getContextualCallTargets(ref: UnresolvedRef): Node[] { + return this.getContextualCallTargetContexts(ref).map((target) => target.node); + } + + /** + * Return one target from each mutually exclusive branch of the same shader + * preprocessor conditional. This is a deliberate variant union: the active + * compiler defines choose one branch, while source-only analysis must retain + * every possible call edge. Independent guarded overloads remain ambiguous. + */ + getConditionalCallTargets(ref: UnresolvedRef): Node[] { + if (!isShaderLanguage(ref.language) || ref.referenceKind !== 'calls') return []; + const language = ref.language as 'glsl' | 'hlsl'; + const roots = this.getContextRoots(ref.filePath, language); + const candidates = applyCallEvidence(this.context.getNodesByName(ref.referenceName).filter((node) => + node.language === ref.language && + node.id !== ref.fromNodeId && + (node.kind === 'function' || node.kind === 'method') + ), ref); + const scored = candidates.map((node) => ({ + node, + distance: this.contextDistance(node.filePath, language, roots), + })).filter((item) => Number.isFinite(item.distance)); + if (scored.length < 2) return []; + const minDepth = Math.min(...scored.map((item) => item.distance)); + const nearest = scored.filter((item) => item.distance === minDepth).map((item) => item.node); + if (nearest.length < 2 || new Set(nearest.map((node) => node.filePath)).size !== 1) return []; + + const filePath = nearest[0]!.filePath; + let branches = this.conditionalBranchCache.get(filePath); + if (!branches) { + const source = this.context.readFile(filePath); + if (!source) return []; + branches = conditionalBranchesByLine(source); + this.conditionalBranchCache.set(filePath, branches); + } + const stacks = nearest.map((node) => branches![node.startLine] ?? []); + if (stacks.some((stack) => stack.length === 0)) return []; + + for (const frame of stacks[0]!) { + const selected = stacks.map((stack) => stack.find((candidate) => candidate.groupLine === frame.groupLine)); + if (selected.some((candidate) => !candidate)) continue; + const branchIds = selected.map((candidate) => candidate!.branch); + // One candidate per branch proves this is a conditional implementation + // family, not an unresolved overload set inside one branch. + if (new Set(branchIds).size !== nearest.length) continue; + return [...nearest].sort((a, b) => a.startLine - b.startLine); + } + return []; + } + + getContextualCallTargetContexts(ref: UnresolvedRef): ContextualShaderTarget[] { + if (!isShaderLanguage(ref.language) || ref.referenceKind !== 'calls') return []; + const language = ref.language as 'glsl' | 'hlsl'; + const roots = this.getContextRoots(ref.filePath, language); + const candidates = applyCallEvidence(this.context.getNodesByName(ref.referenceName).filter((node) => + node.language === ref.language && + node.id !== ref.fromNodeId && + (node.kind === 'function' || node.kind === 'method') + ), ref); + const scored = candidates.map((node) => ({ + node, + distance: this.contextDistance(node.filePath, language, roots), + })).filter((item) => Number.isFinite(item.distance)); + if (scored.length < 2) return []; + const minDepth = Math.min(...scored.map((item) => item.distance)); + const nearest = scored.filter((item) => item.distance === minDepth).map((item) => item.node); + if (new Set(nearest.map((node) => node.filePath)).size < 2) return []; + const signatures = new Set(nearest.map(normalizedSignature)); + if (signatures.size !== 1 || signatures.has(null)) return []; + + const byFile = new Map(); + for (const node of [...nearest].sort((a, b) => a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine)) { + if (!byFile.has(node.filePath)) byFile.set(node.filePath, node); + } + const reverse = this.getReverseIncludes(language); + return [...byFile.values()].map((node) => { + const matching = new Set([...roots.keys()].filter((root) => this.getClosure(root, language).has(node.filePath))); + const topLevel = [...matching].filter((root) => + ![...(reverse.get(root) ?? [])].some((parent) => matching.has(parent)) + ); + return { node, contextRoots: (topLevel.length > 0 ? topLevel : [...matching]).sort() }; + }); + } + + getClosure(filePath: string, language: 'glsl' | 'hlsl'): Map { + const key = `${language}\0${filePath}`; + const cached = this.closureCache.get(key); + if (cached) return cached; + const depth = new Map([[filePath, 0]]); + const queue = [filePath]; + for (let index = 0; index < queue.length && index < 4096; index++) { + const file = queue[index]!; + const currentDepth = depth.get(file)!; + if (currentDepth >= 32) continue; + for (const target of this.getDirectIncludes(file, language)) { + if (!target || depth.has(target)) continue; + depth.set(target, currentDepth + 1); + queue.push(target); + } + } + this.closureCache.set(key, depth); + return depth; + } + + private getDirectIncludes(filePath: string, language: 'glsl' | 'hlsl'): string[] { + const key = `${language}\0${filePath}`; + const cached = this.directIncludeCache.get(key); + if (cached) return cached; + const targets = new Set(); + const content = this.context.readFile(filePath); + if (content) { + const lines = content.split(/\r?\n/); + for (let row = 0; row < lines.length; row++) { + const match = lines[row]!.match(/^\s*#\s*(?:include|import)\s*[<"]([^>"]+)[>"]/); + if (!match) continue; + const target = resolveShaderPath(match[1]!, filePath, this.context, row + 1); + if (!target) continue; + const targetLanguage = this.context.getNodesInFile(target).find((node) => node.kind === 'file')?.language; + if (targetLanguage === language) targets.add(target); + } + } + const result = [...targets]; + this.directIncludeCache.set(key, result); + return result; + } + + private getReverseIncludes(language: 'glsl' | 'hlsl'): Map> { + const cached = this.reverseIncludeCache.get(language); + if (cached) return cached; + const reverse = new Map>(); + for (const file of this.context.getAllFiles()) { + const fileLanguage = this.context.getNodesInFile(file).find((node) => node.kind === 'file')?.language; + if (fileLanguage !== language) continue; + for (const target of this.getDirectIncludes(file, language)) { + let parents = reverse.get(target); + if (!parents) { + parents = new Set(); + reverse.set(target, parents); + } + parents.add(file); + } + } + this.reverseIncludeCache.set(language, reverse); + return reverse; + } + + private getContextRoots(filePath: string, language: 'glsl' | 'hlsl'): Map { + const key = `${language}\0${filePath}`; + const cached = this.contextRootCache.get(key); + if (cached) return cached; + const reverse = this.getReverseIncludes(language); + const roots = new Map([[filePath, 0]]); + const queue = [filePath]; + for (let index = 0; index < queue.length && index < 4096; index++) { + const file = queue[index]!; + const currentDepth = roots.get(file)!; + if (currentDepth >= 32) continue; + for (const parent of reverse.get(file) ?? []) { + if (roots.has(parent)) continue; + roots.set(parent, currentDepth + 1); + queue.push(parent); + } + } + this.contextRootCache.set(key, roots); + return roots; + } + + private contextDistance( + targetFile: string, + language: 'glsl' | 'hlsl', + roots: Map, + ): number { + let best = Number.POSITIVE_INFINITY; + for (const [root, reverseDepth] of roots) { + const forwardDepth = this.getClosure(root, language).get(targetFile); + if (forwardDepth !== undefined) best = Math.min(best, reverseDepth + forwardDepth); + } + return best; + } +} diff --git a/src/resolution/shader-synthesizer.ts b/src/resolution/shader-synthesizer.ts new file mode 100644 index 000000000..9518c75c4 --- /dev/null +++ b/src/resolution/shader-synthesizer.ts @@ -0,0 +1,276 @@ +import * as path from 'path'; +import type { Edge, Node } from '../types'; +import type { QueryBuilder } from '../db/queries'; +import type { MaybeYield } from './cooperative-yield'; +import type { ResolutionContext } from './types'; +import { isShaderLanguage, ShaderResolver, SHADER_EXTENSIONS } from './shader-resolver'; + +const SHADER_LITERAL_RE = new RegExp( + `["']([^"']+(?:${SHADER_EXTENSIONS.map((ext) => ext.replace('.', '\\.')).join('|')})(?:\\.[A-Za-z0-9_-]+)*(?:\\.spv)?)["']`, + 'gi', +); + +function enclosingCallNames(source: string, literalOffset: number): string[] { + const names: string[] = []; + let closedParens = 0; + const start = Math.max(0, literalOffset - 768); + for (let index = literalOffset - 1; index >= start; index--) { + const char = source[index]!; + if (char === ')') { + closedParens++; + continue; + } + if (char === '(') { + if (closedParens > 0) { + closedParens--; + continue; + } + const prefix = source.slice(start, index); + const match = /([A-Za-z_]\w*(?:(?:::|->|\.)[A-Za-z_]\w*)*)\s*$/.exec(prefix); + if (match) names.push(match[1]!.split(/::|->|\./).pop()!); + continue; + } + if (closedParens === 0 && (char === ';' || char === '{' || char === '}')) break; + } + return names; +} + +function createsShaderEntryAssociation(source: string, literalOffset: number): boolean { + return enclosingCallNames(source, literalOffset).some((name) => { + const lower = name.toLowerCase(); + const hasShaderObject = /shader|spirv|spv|pipeline|program/.test(lower); + const hasLoaderVerb = /compile|load|create|build|make|open|read|register|attach|add|set/.test(lower); + return hasShaderObject && hasLoaderVerb; + }); +} + +function sourceFor(node: Node, ctx: ResolutionContext): string { + const lines = ctx.getFileLines?.(node.filePath) ?? ctx.readFile(node.filePath)?.split(/\r?\n/) ?? []; + return lines.slice(Math.max(0, node.startLine - 1), node.endLine).join('\n'); +} + +function resolveShaderAsset(literal: string, fromFile: string, shaderFiles: string[]): string | null { + let requested = literal.replace(/\\/g, '/').replace(/^\.\//, ''); + const derivedSpv = requested.toLowerCase().endsWith('.spv'); + if (derivedSpv) requested = requested.slice(0, -4); + const fromDir = path.posix.dirname(fromFile); + const tiers = [ + shaderFiles.filter((file) => file === path.posix.normalize(path.posix.join(fromDir, requested))), + shaderFiles.filter((file) => file === requested), + shaderFiles.filter((file) => file.endsWith('/' + requested)), + ...(derivedSpv ? [shaderFiles.filter((file) => { + const base = path.posix.basename(file); + const requestedBase = path.posix.basename(requested); + return requested.startsWith(file + '.') || requestedBase.startsWith(base + '.'); + })] : []), + shaderFiles.filter((file) => path.posix.basename(file) === path.posix.basename(requested)), + ]; + for (const tier of tiers) { + const unique = [...new Set(tier)]; + if (unique.length === 1) return unique[0]!; + if (unique.length > 1) return null; + } + return null; +} + +function decoratorValue(node: Node, prefix: string): string | undefined { + return node.decorators?.find((value) => value.startsWith(prefix + ':'))?.slice(prefix.length + 1); +} + +function resourceClass(node: Node): string | undefined { + const explicit = node.decorators?.find((value) => value.startsWith('resource:'))?.slice('resource:'.length); + if (explicit === 'buffer') return 'storage'; + if (explicit) return explicit; + if (node.decorators?.includes('storage:buffer')) return 'storage'; + if (node.decorators?.includes('storage:uniform')) return 'constant-buffer'; + return undefined; +} + +function descriptorClass(text: string): string | undefined { + if (/STORAGE_(?:IMAGE|BUFFER)|STORAGE_BUFFER/i.test(text)) return 'storage'; + if (/COMBINED_IMAGE_SAMPLER|SAMPLED_IMAGE/i.test(text)) return 'texture'; + if (/\bSAMPLER\b/i.test(text)) return 'sampler'; + if (/UNIFORM_BUFFER/i.test(text)) return 'constant-buffer'; + if (/ACCELERATION_STRUCTURE/i.test(text)) return 'acceleration-structure'; + return undefined; +} + +function resourcesFor(shaderFile: string, resolver: ShaderResolver, ctx: ResolutionContext): Node[] { + const language = ctx.getNodesInFile(shaderFile).find((node) => node.kind === 'file')?.language; + if (!language || !isShaderLanguage(language)) return []; + const closure = resolver.getClosure(shaderFile, language); + const resources: Node[] = []; + for (const file of closure.keys()) { + resources.push(...ctx.getNodesInFile(file).filter((node) => + node.kind === 'variable' || node.kind === 'constant' || node.kind === 'struct' + )); + } + return resources; +} + +function uniqueResource(candidates: Node[]): Node | null { + const unique = [...new Map(candidates.map((node) => [node.id, node])).values()]; + return unique.length === 1 ? unique[0]! : null; +} + +function interfaceEdges(owner: Node, shaderFile: string, entry: Node, body: string, resolver: ShaderResolver, ctx: ResolutionContext): Edge[] { + const out: Edge[] = []; + const resources = resourcesFor(shaderFile, resolver, ctx); + const registeredAt = `${owner.filePath}:${owner.startLine}`; + const add = (target: Node | null, metadata: Record): void => { + if (!target) return; + out.push({ source: owner.id, target: target.id, kind: 'references', provenance: 'heuristic', line: owner.startLine, + metadata: { synthesizedBy: 'shader-interface', shaderEntry: entry.qualifiedName, registeredAt, ...metadata } }); + }; + + const bindingRe = /\b(?:dstBinding|binding)\b\s*(?:=\s*)?(?:\{\s*)?(\d+)/g; + let match: RegExpExecArray | null; + while ((match = bindingRe.exec(body))) { + const binding = match[1]!; + const nearby = body.slice(Math.max(0, match.index - 140), Math.min(body.length, match.index + 280)); + const expectedClass = descriptorClass(nearby); + const expectedSet = nearby.match(/\b(?:set|space)\b\s*(?:=\s*)?(\d+)/i)?.[1]; + const candidates = resources.filter((node) => { + const explicit = decoratorValue(node, 'binding'); + const register = decoratorValue(node, 'register')?.match(/^[tsub](\d+)$/)?.[1]; + if (explicit !== binding && register !== binding) return false; + const actualSet = decoratorValue(node, 'set') ?? decoratorValue(node, 'space'); + if (expectedSet !== undefined && actualSet !== expectedSet) return false; + const actualClass = resourceClass(node); + return !expectedClass || !actualClass || actualClass === expectedClass || + (expectedClass === 'constant-buffer' && actualClass === 'buffer'); + }); + add(uniqueResource(candidates), { binding: Number(binding), set: expectedSet === undefined ? undefined : Number(expectedSet), descriptorType: expectedClass }); + } + + const constantIds = new Set(); + for (const id of body.matchAll(/\bconstantID\b\s*=\s*(\d+)/g)) constantIds.add(id[1]!); + for (const id of body.matchAll(/VkSpecializationMapEntry(?:\s+[A-Za-z_]\w*)?\s*\{\s*(\d+)/g)) constantIds.add(id[1]!); + for (const id of constantIds) add(uniqueResource(resources.filter((node) => decoratorValue(node, 'constant_id') === id)), { constantId: Number(id) }); + + const locations = new Set(); + for (const location of body.matchAll(/\blocation\b\s*=\s*(\d+)/g)) locations.add(location[1]!); + for (const location of body.matchAll(/VkVertexInputAttributeDescription(?:\s+[A-Za-z_]\w*)?\s*(?:=\s*)?\{\s*(\d+)/g)) locations.add(location[1]!); + for (const location of locations) add(uniqueResource(resources.filter((node) => decoratorValue(node, 'location') === location)), { location: Number(location) }); + + if (/\bvkCmdPushConstants\b|\bVkPushConstantRange\b/.test(body)) { + add(uniqueResource(resources.filter((node) => node.decorators?.includes('push_constant'))), { pushConstant: true }); + } + return out; +} + +export async function shaderIntegrationEdges( + queries: QueryBuilder, + ctx: ResolutionContext, + onYield: MaybeYield, +): Promise { + const shaderFiles = ctx.getAllFiles().filter((file) => { + const language = ctx.getNodesInFile(file).find((node) => node.kind === 'file')?.language; + return !!language && isShaderLanguage(language); + }); + if (shaderFiles.length === 0) return []; + + const resolver = new ShaderResolver(ctx); + const edges: Edge[] = []; + const seen = new Set(); + + // A shared shader library is compiled once per application bridge. The same + // indexed call site can therefore target several equivalent implementations, + // one in each include-root context. Ordinary resolution intentionally leaves + // that multi-target ref unresolved; represent every proven context here. + let contextualScanned = 0; + for (const ref of queries.getUnresolvedReferences?.() ?? []) { + if (!ref.language || !ref.filePath || !isShaderLanguage(ref.language) || ref.referenceKind !== 'calls') continue; + if ((++contextualScanned & 127) === 0) await onYield(); + for (const target of resolver.getContextualCallTargetContexts({ + ...ref, + filePath: ref.filePath, + language: ref.language, + })) { + edges.push({ + source: ref.fromNodeId, + target: target.node.id, + kind: 'calls', + line: ref.line, + provenance: 'heuristic', + metadata: { + synthesizedBy: 'shader-context-variant', + reference: ref.referenceName, + targetFile: target.node.filePath, + contextRoots: target.contextRoots, + registeredAt: `${ref.filePath}:${ref.line}`, + }, + }); + } + } + + const interfaceOwners = new Map }>(); + const callable = [ + ...(ctx.iterateNodesByKind?.('function') ?? ctx.getNodesByKind('function')), + ...(ctx.iterateNodesByKind?.('method') ?? ctx.getNodesByKind('method')), + ]; + let scanned = 0; + for (const node of callable) { + if (node.language !== 'c' && node.language !== 'cpp') continue; + if ((++scanned & 127) === 0) await onYield(); + const body = sourceFor(node, ctx); + SHADER_LITERAL_RE.lastIndex = 0; + const associated = new Map(); + let match: RegExpExecArray | null; + while ((match = SHADER_LITERAL_RE.exec(body))) { + const shaderFile = resolveShaderAsset(match[1]!, node.filePath, shaderFiles); + if (!shaderFile) continue; + const fileNode = ctx.getNodesInFile(shaderFile).find((candidate) => candidate.kind === 'file'); + if (!fileNode) continue; + const literalLine = node.startLine + body.slice(0, match.index).split('\n').length - 1; + edges.push({ source: node.id, target: fileNode.id, kind: 'references', provenance: 'heuristic', line: literalLine, + metadata: { synthesizedBy: 'shader-file', literal: match[1], registeredAt: `${node.filePath}:${literalLine}` } }); + + if (!createsShaderEntryAssociation(body, match.index)) continue; + const entry = ctx.getNodesInFile(shaderFile).find((candidate) => candidate.kind === 'function' && candidate.decorators?.includes('entrypoint')) + ?? ctx.getNodesInFile(shaderFile).find((candidate) => candidate.kind === 'function' && candidate.name === 'main'); + if (!entry) continue; + associated.set(shaderFile, entry); + edges.push({ source: node.id, target: entry.id, kind: 'calls', provenance: 'heuristic', line: literalLine, + metadata: { synthesizedBy: 'shader-entry', shaderFile, registeredAt: `${node.filePath}:${literalLine}` } }); + } + if (associated.size === 0) continue; + + const owners = new Map([[node.id, node]]); + let frontier = [node]; + for (let depth = 0; depth < 2; depth++) { + const next: Node[] = []; + for (const owner of frontier) { + for (const edge of queries.getOutgoingEdges(owner.id, ['calls'])) { + const target = queries.getNodeById(edge.target); + if (!target || (target.language !== 'c' && target.language !== 'cpp') || owners.has(target.id)) continue; + owners.set(target.id, target); + next.push(target); + } + } + frontier = next; + } + for (const owner of owners.values()) { + const aggregate = interfaceOwners.get(owner.id) ?? { owner, shaders: new Map() }; + for (const [shaderFile, entry] of associated) aggregate.shaders.set(shaderFile, entry); + interfaceOwners.set(owner.id, aggregate); + } + } + + // Descriptor coordinates without a uniquely associated shader are unsafe: + // binding 0 is common across unrelated pipelines and generic helper calls. + for (const { owner, shaders } of interfaceOwners.values()) { + if (shaders.size !== 1) continue; + const association = shaders.entries().next().value; + if (!association) continue; + const [shaderFile, entry] = association; + edges.push(...interfaceEdges(owner, shaderFile, entry, sourceFor(owner, ctx), resolver, ctx)); + } + + return edges.filter((edge) => { + const key = `${edge.source}>${edge.target}>${edge.kind}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); +} diff --git a/src/types.ts b/src/types.ts index 6f3de63db..332306777 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,6 +105,10 @@ export const LANGUAGES = [ 'vbnet', 'erlang', 'terraform', + 'glsl', + 'hlsl', + 'cmake', + 'powershell', 'unknown', ] as const; From a892400288aadcdf8c0d086f354debdd089efc93 Mon Sep 17 00:00:00 2001 From: Manh Hung Date: Wed, 15 Jul 2026 16:27:04 +0700 Subject: [PATCH 2/3] docs: document shader indexing and validation --- .claude/skills/agent-eval/corpus.json | 46 +++++++++++++ CHANGELOG.md | 23 +++++++ README.md | 36 ++++++++-- assets/generate-language-tiles.py | 8 +++ assets/languages/glsl.svg | 7 ++ assets/languages/hlsl.svg | 7 ++ .../dynamic-dispatch-coverage-playbook.md | 15 +++++ docs/grammars/tree-sitter-shaders.md | 20 ++++++ scripts/add-lang/verify-shader-corpus.mjs | 66 +++++++++++++++++++ .../src/content/docs/guides/affected-tests.md | 6 ++ site/src/content/docs/reference/cli.md | 13 +++- site/src/content/docs/reference/languages.md | 4 ++ 12 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 assets/languages/glsl.svg create mode 100644 assets/languages/hlsl.svg create mode 100644 docs/grammars/tree-sitter-shaders.md create mode 100644 scripts/add-lang/verify-shader-corpus.mjs diff --git a/.claude/skills/agent-eval/corpus.json b/.claude/skills/agent-eval/corpus.json index 150b4a601..5ffb57ecd 100644 --- a/.claude/skills/agent-eval/corpus.json +++ b/.claude/skills/agent-eval/corpus.json @@ -607,5 +607,51 @@ "files": "~3390", "question": "How does programs.git.enable produce the final git config file in the user's home directory? Trace the flow from the git program module to the home-files machinery that links generated files into place." } + ], + "GLSL": [ + { + "name": "glsl-blend", + "repo": "https://github.com/jamieowen/glsl-blend", + "size": "Small", + "files": "~43", + "question": "How does the overlay blend entry function reach the shared channel-level blend helpers across the shader includes?" + }, + { + "name": "gl-transitions", + "repo": "https://github.com/gl-transitions/gl-transitions", + "size": "Medium", + "files": "~145", + "question": "How does a transition shader's main entry point reach its transition function and shared sampling helpers?" + }, + { + "name": "lygia", + "repo": "https://github.com/patriciogonzalezvivo/lygia", + "size": "Large", + "files": "~2860", + "question": "How does a lighting shader reach the material, BRDF, and sampling helpers brought in through Lygia's include hierarchy?" + } + ], + "HLSL": [ + { + "name": "NoiseShader", + "repo": "https://github.com/keijiro/NoiseShader", + "size": "Small", + "files": "~112", + "question": "How do the public noise shader functions reach the shared hash and interpolation helpers?" + }, + { + "name": "NRD", + "repo": "https://github.com/NVIDIA-RTX/NRD", + "size": "Medium", + "files": "~132", + "question": "How does an NRD compute shader entry point reach its macro-declared resources and shared denoising helpers?" + }, + { + "name": "Magpie", + "repo": "https://github.com/Blinue/Magpie", + "size": "Large", + "files": "~631", + "question": "How does a scaling effect entry point reach the common sampling and color-processing helpers across included HLSL files?" + } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 22fb4c5fc..b76f34990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,31 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### New Features + +- CodeGraph now indexes GLSL, Vulkan ray-tracing and mesh shaders, HLSL, and OpenUSD GLSLFX files, including shader includes, macros, entry points, descriptor/register metadata, uniform and storage resources, push and specialization constants, and conservative C/C++ links to the shaders and bindings they configure. +- CodeGraph now indexes CMake and PowerShell build scripts, so shader targets, generated SPIR-V names, and build-time variables appear in project search and dependency analysis. + ### Fixes +- GLSL extraction now recovers preprocessor-heavy and malformed-but-valid function declarations, while shader call resolution follows equivalent definitions through include closures instead of silently dropping real callers. +- Shader impact queries can be scoped to a file, common variant definitions are presented without duplicate blast-radius entries, unrelated C++ methods are no longer guessed as matches, and deletion-only syncs preserve a fresh index timestamp. +- Common shader names such as `main` now require an explicit file when a project contains many definitions, explicit shader filenames anchor `codegraph_explore` to that file, and conditional shader variants remain visible as separate impact roots. +- Shared shader helpers and constants injected by the build system now resolve when their equivalent declarations have matching contracts, and recovered GLSL signatures retain their return type and parameters. +- PowerShell indexing now records calls between script functions and from script scope, reports exact declaration and call-site lines, and avoids flooding search with repeated or function-local assignment nodes. +- Ambiguous CLI queries now report the complete definition count instead of the first 50 matches, and shader impact output identifies the preprocessor conditions attached to each indexed variant. +- PowerShell `Import-Module` statements now link scripts to local `.psm1` modules, and calls to exported module functions participate in callers, callees, impact, and file dependency analysis. +- Shader exploration now expands an explicitly requested file through its transitive include closure, while PowerShell exports no longer appear as callers and executable launches are no longer misclassified as module imports. +- PowerShell dot-sourced scripts now create file dependencies and expose their shared functions to callers, callees, impact, and exploration without enabling unrelated project-wide name matches. +- Named local C++ lambdas now appear as scoped functions with accurate callers and callees, preventing their invocations from being linked to unrelated same-named methods elsewhere in the project. +- Malformed C++ preprocessor parse ranges no longer let one method consume the rest of a file, while local lambdas retain their namespace and class ownership and can be isolated by source line in callers, callees, and impact queries. +- HLSL compiler-root includes now resolve through unique project suffixes, overloads use include context and call arity, and shared RTXDI-style library calls connect to each application bridge implementation instead of collapsing onto one sample. +- `codegraph status` now distinguishes metadata-only files and reports unresolved attempted references by language and kind, so a current index no longer hides semantic coverage gaps. +- `codegraph affected` now excludes vendored dependency tests for project-owned changes while retaining them for dependency changes or explicit filters. +- Shader exploration and impact now stay inside the explicitly selected compilation target, HLSL overloads use proven argument types when arity alone is ambiguous, and shader compile-test entry files participate in affected-test results. +- Shared shader-header impact now surfaces every proven compilation entry even at shallow depth, while `codegraph affected` follows shader include roots without crossing between application bridges or treating helper headers as tests. +- GLSL overload callers now resolve by their real parameter counts, and C/C++ shader filename checks or documentation strings remain file dependencies instead of being reported as calls to a shader entry point. +- Calls to shader helpers that switch between a function and a function-like macro now remain connected across every preprocessor variant, and `codegraph_explore` keeps both endpoint sources while showing proven direct calls in Flow and Relationships when a query uses `file:line`, `file line N`, or directly names both endpoints. - Callers and impact analysis no longer silently under-count a function that calls the same callee many times. When one caller contained several call sites to the same callee and an internal resolution batch boundary happened to split them, cleanup after the first batch removed the later sites' pending rows before they were ever attempted — their edges were never created, deterministically, and which edges went missing shifted with unrelated changes to the project's total reference count. Post-pass cleanup now targets the exact database row each processed reference came from. Found while validating the operator-call fix on nlohmann/json, where `write_cbor`'s 11 calls to `to_char_type` indexed as 10. (#1269) - C++ explicit operator calls — `a.operator+(b)`, `p->operator+(b)`, `a.operator[](3)`, and the other symbolic forms — now produce a `calls` edge to the operator method, so an operator invoked only through the explicit syntax no longer looks uncalled in callers and impact analysis. tree-sitter parses these call sites with the operator name stranded in an error node (never as a normal member access), so the call's target was silently read as just the receiver variable; the operator name is now recovered from the error node and resolved through receiver-type inference like any other member call — a same-named operator on an unrelated class can never capture the edge. Infix uses (`a + b`, `a[i]`) need real type inference and are tracked separately. (#1247) diff --git a/README.md b/README.md index 73e00aa55..0375faeac 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,8 @@ Every language below gets the same treatment — full structural extraction and Objective-C Metal CUDA + GLSL + HLSL Swift Kotlin Scala @@ -314,7 +316,7 @@ The reliable, universal payoff is **surgical context and speed**: CodeGraph coll | **Full-Text Search** | Find code by name instantly across your entire codebase, powered by FTS5 | | **Impact Analysis** | Trace callers, callees, and the full impact radius of any symbol before making changes | | **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config | -| **20+ Languages** | TypeScript, JavaScript, ArkTS, Python, Go, Rust, Java, C#, VB.NET, PHP, Ruby, C, C++, CUDA, Objective-C, Metal, Swift, Kotlin, Scala, Dart, Lua, Luau, R, Nix, Erlang, CFML, COBOL, Solidity, Terraform/OpenTofu, Svelte, Vue, Astro, Liquid, Pascal/Delphi | +| **30+ Languages** | TypeScript, JavaScript, ArkTS, Python, Go, Rust, Java, C#, VB.NET, PHP, Ruby, C, C++, CUDA, GLSL, HLSL, CMake, PowerShell, Objective-C, Metal, Swift, Kotlin, Scala, Dart, Lua, Luau, R, Nix, Erlang, CFML, COBOL, Solidity, Terraform/OpenTofu, Svelte, Vue, Astro, Liquid, Pascal/Delphi | | **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 17 frameworks | | **Mixed iOS / React Native / Expo** | Closes cross-language flows that static parsing misses: Swift ↔ ObjC bridging, React Native legacy bridge + TurboModules + Fabric view components, native → JS event emitters, Expo Modules | | **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only | @@ -553,9 +555,9 @@ codegraph query # Search symbols (--kind, --limit, --json) codegraph explore # Relevant symbols' source + call paths in one shot (same output as the codegraph_explore MCP tool) codegraph node # One symbol's source + callers, or read a file with line numbers (same output as codegraph_node) codegraph files [path] # Show file structure (--format, --filter, --max-depth, --json) -codegraph callers # Find what calls a function/method (--limit, --json) -codegraph callees # Find what a function/method calls (--limit, --json) -codegraph impact # Analyze what code is affected by changing a symbol (--depth, --json) +codegraph callers # Find what calls a function/method (--file, --line, --limit, --json) +codegraph callees # Find what a function/method calls (--file, --line, --limit, --json) +codegraph impact # Analyze what code is affected by changing a symbol (--file, --line, --depth, --json) codegraph affected [files...] # Find test files affected by changes (see below) codegraph daemon # Manage background daemons — pick one to stop (alias: daemons) codegraph telemetry [on|off] # Show or change anonymous usage telemetry @@ -564,6 +566,8 @@ codegraph version # Print the installed version (also -v, --vers codegraph help [command] # Show help, optionally for one command ``` +`codegraph status --json` separates tracked files from graph-producing files and reports unresolved attempted references by language and kind, making semantic gaps visible even when the index is current. + ### `codegraph affected` Traces import dependencies transitively to find which test files are affected by changed source files. @@ -582,6 +586,8 @@ codegraph affected src/auth.ts --filter "e2e/*" # Custom test file pattern | `-j, --json` | Output as JSON | `false` | | `-q, --quiet` | Output file paths only | `false` | +Automatic test discovery excludes common vendored roots for project-owned changes; pass `--filter` to include matching dependency tests explicitly. + **CI/hook example:** ```bash @@ -726,6 +732,24 @@ language or a malformed file is warned about and skipped — it never breaks indexing — and a project with no `codegraph.json` behaves exactly as before. Re-index (`codegraph index`) after adding or changing mappings. +### Shader include roots + +GLSL and HLSL quoted includes resolve relative to the including file, then +through project include roots, then from the project root. Configure compiler +include roots and aliases in the same `codegraph.json` when your build uses them: + +```json +{ + "shaderIncludePaths": ["shaders/include", "third_party/NRD/Shaders/Include"], + "shaderIncludeAliases": { + "$TOOLS": "pxr/imaging" + } +} +``` + +Paths must remain inside the project. Ambiguous include or symbol matches stay +unlinked rather than guessing. + ## Telemetry CodeGraph collects **anonymous usage statistics** — which tools and commands get @@ -790,6 +814,10 @@ is written): | Objective-C | `.m`, `.mm`, `.h` | Partial support (classes, protocols, methods, `@property`, `#import`, message sends; `.mm` ObjC++ may parse incompletely) | | Metal | `.metal` | Full support (vertex/fragment/kernel functions, structs, type aliases, call edges — MSL parses as C++, with `[[attribute]]` annotations handled) | | CUDA | `.cu`, `.cuh` | Full support (kernels and device/host functions, structs, classes, host→kernel call edges through `<<>>` launch syntax — templated launches, function-pointer launches (`auto kernel = &fn<...>`), `dim3{...}` configs, and macro-defined kernels included; `__global__`/`__device__`/`__launch_bounds__` specifiers handled; CUDA in plain `.h`/`.hpp` headers recognized by content) | +| GLSL / Vulkan GLSL | `.glsl`, `.vert`, `.frag`, `.comp`, `.geom`, `.tesc`, `.tese`, `.rgen`, `.rmiss`, `.rchit`, `.rahit`, `.rint`, `.rcall`, `.mesh`, `.task`, `.glslfx` | Full support (functions, structs, macros, transitive includes, call edges, descriptor layouts, uniform/storage/push-constant blocks, specialization constants, ray payloads, mesh/ray stages, and OpenUSD GLSLFX sections/techniques) | +| HLSL | `.hlsl`, `.hlsli`, `.fx`, `.fxh` | Full support (functions and shader entry points, structs/classes, macros/includes, `cbuffer`/`tbuffer`, semantics and thread-group attributes, register/space and Vulkan attributes, texture/buffer/sampler/ray-tracing resources, including NVIDIA NRD-style declaration macros) | +| CMake | `CMakeLists.txt`, `.cmake` | Build-script support (functions/macros, variables, include and subdirectory wiring, shader and SPIR-V references) | +| PowerShell | `.ps1`, `.psm1`, `.psd1` | Build-script support (functions, `Import-Module` dependencies, cross-module and script/function call edges, script-scope variables, tool invocations, shader and SPIR-V references) | | Swift | `.swift` | Full support | | Kotlin | `.kt`, `.kts` | Full support | | Scala | `.scala`, `.sc` | Full support (classes, traits, methods, type aliases, Scala 3 enums) | diff --git a/assets/generate-language-tiles.py b/assets/generate-language-tiles.py index 0e78fb485..4722c3b1d 100644 --- a/assets/generate-language-tiles.py +++ b/assets/generate-language-tiles.py @@ -76,6 +76,8 @@ ("objective-c", "Objective-C", ("devicon", "objectivec", "plain")), ("metal", "Metal", ("si", "apple")), ("cuda", "CUDA", ("si", "nvidia")), + ("glsl", "GLSL", ("custom", "glsl")), + ("hlsl", "HLSL", ("custom", "hlsl")), ("swift", "Swift", ("si", "swift")), ("kotlin", "Kotlin", ("si", "kotlin")), ("scala", "Scala", ("si", "scala")), @@ -174,6 +176,12 @@ def custom_glyph(key, out): baseline = round(cy + out.cap_height(size, wt) / 2, 2) d = out.centered("", size, wt, cx, baseline, track=0.4) return f'' + if key in ("glsl", "hlsl"): + size, wt = 18.0, 750 + baseline = round(cy + out.cap_height(size, wt) / 2, 2) + d = out.centered("GL" if key == "glsl" else "HL", size, wt, cx, baseline, track=0.5) + color = "#5586A4" if key == "glsl" else "#2B579A" + return f'' if key == "cobol": # A punched card: manila stock, clipped corner, punched rows. w, h = GLYPH_BOX, 28 diff --git a/assets/languages/glsl.svg b/assets/languages/glsl.svg new file mode 100644 index 000000000..92b1d4dee --- /dev/null +++ b/assets/languages/glsl.svg @@ -0,0 +1,7 @@ + + GLSL + + + GL + GLSL + diff --git a/assets/languages/hlsl.svg b/assets/languages/hlsl.svg new file mode 100644 index 000000000..194b3c06e --- /dev/null +++ b/assets/languages/hlsl.svg @@ -0,0 +1,7 @@ + + HLSL + + + HL + HLSL + diff --git a/docs/design/dynamic-dispatch-coverage-playbook.md b/docs/design/dynamic-dispatch-coverage-playbook.md index 63603a920..d9e9ec3ee 100644 --- a/docs/design/dynamic-dispatch-coverage-playbook.md +++ b/docs/design/dynamic-dispatch-coverage-playbook.md @@ -630,6 +630,21 @@ Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started. frontier — no named target); React lazy data-router (variable paths + lazy imports); C++ pure-virtual base methods (extracting bodyless decls risks duplicate decl/def nodes for modest gain). Forcing these would add noise, violating "partial coverage worse than none." +- **GLSL / HLSL / GLSLFX shaders (validated 2026-07-13, engine corpus + six public corpora + OpenUSD).** + The local v1.4.1 build indexed the Vulkan engine's 99 first-party GLSL files with zero extraction failures + and stable reindex counts (416 total indexed files and 15,139 nodes). The explicit corpus harness also + parsed ignored/vendor inputs: 191 shader files across GLSL, Vulkan ray-tracing stages, HLSL/HLSLI, and + GLSLFX, with zero failures and stable symbol IDs. Public corpus coverage: glsl-blend 31 shader files / 267 + nodes / 880 edges; gl-transitions 125 / 847 / 1,178; lygia 1,235 / 17,486 / 27,369; NoiseShader 7 / 125 / + 188; NRD 72 / 2,128 / 6,868; Magpie 146 / 7,821 / 52,207. Representative include-to-call probes resolve + across files (`backInOut -> backIn`, `bounceIn -> bounceOut`, `blendColor -> rgb2hsv`, + `ClassicNoise_impl -> wglnoise_fade`, `NRD_CS_MAIN -> WithRectOrigin`, and `Pass2 -> MulAdd`). The canonical + OpenUSD sparse corpus parsed 55 GLSLFX files twice with zero failures (1,079 nodes / 1,050 extraction edges), + preserving embedded GLSL line mappings. C++ integration produced 93 exact shader-file references plus 93 + entry-point call edges in the engine. A real-corpus precision audit caught broad binding-0 links through a + generic helper; interface synthesis now requires one unique shader association, leaving zero ambiguous + bindless links. Agent A/B was not run in this Windows implementation session; these are deterministic + extraction, graph, reindex, retrieval, and precision probes only. - **Difficulty gradient is real:** named-ref dispatch (resolver) is cheap; anonymous callback dispatch (synthesizer) is medium; **anonymous-arrow handlers are the hard remaining gap** (no identity → need synthesizer link-through-body, not yet built). diff --git a/docs/grammars/tree-sitter-shaders.md b/docs/grammars/tree-sitter-shaders.md new file mode 100644 index 000000000..ea3adaf44 --- /dev/null +++ b/docs/grammars/tree-sitter-shaders.md @@ -0,0 +1,20 @@ +# Shader grammar provenance + +CodeGraph vendors two MIT-licensed Tree-sitter WASM grammars for shader parsing. +Their complete license texts ship in `THIRD_PARTY_NOTICES.md`. + +| Language | Upstream revision | Build | +|---|---|---| +| GLSL | `Caellian/tree-sitter-glsl@09b8cbc3a36bce116d641f1bc176268c0899e6e0` | Tree-sitter CLI 0.26.8, ABI 15, SHA-256 `EBFB8027DF9DC4B31DD967DB4B119D224ECC798740E76790EE186A78F198F117` | +| HLSL | `tree-sitter-grammars/tree-sitter-hlsl@bab9111922d53d43668fabb61869bec51bbcb915` | Parser regenerated with Tree-sitter CLI 0.26.8, ABI 15, SHA-256 `DEFDC4C7ED6971A9C6A7B8411237DD001CA94F79FABD7383866DE5F6433D7F12` | + +Rebuild from a clean checkout of each revision with: + +```sh +tree-sitter generate # required for HLSL to replace the upstream ABI-14 parser +tree-sitter build --wasm +``` + +The resulting files are stored as `tree-sitter-glsl.wasm` and +`tree-sitter-hlsl.wasm` under the extraction WASM assets and are copied into the +published build by the normal `copy-assets` step. diff --git a/scripts/add-lang/verify-shader-corpus.mjs b/scripts/add-lang/verify-shader-corpus.mjs new file mode 100644 index 000000000..73eb020b2 --- /dev/null +++ b/scripts/add-lang/verify-shader-corpus.mjs @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const root = path.resolve(process.argv[2] || '.'); +const expectedIndex = process.argv.indexOf('--expect-glsl'); +const expectedGlsl = expectedIndex >= 0 ? Number(process.argv[expectedIndex + 1]) : null; +const shaderExts = new Set([ + '.glsl', '.vert', '.frag', '.comp', '.geom', '.tesc', '.tese', '.rgen', '.rmiss', + '.rchit', '.rahit', '.rint', '.rcall', '.mesh', '.task', '.glslfx', + '.hlsl', '.hlsli', '.fx', '.fxh', +]); +const glslExts = new Set([...shaderExts].filter((ext) => !['.hlsl', '.hlsli', '.fx', '.fxh'].includes(ext))); + +const files = []; +function walk(dir) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.name === '.git' || entry.name === '.codegraph' || entry.name === 'node_modules') continue; + const absolute = path.join(dir, entry.name); + if (entry.isDirectory()) walk(absolute); + else if (shaderExts.has(path.extname(entry.name).toLowerCase())) files.push(absolute); + } +} +walk(root); + +const grammars = await import(pathToFileURL(path.resolve('dist/extraction/grammars.js')).href); +const extraction = await import(pathToFileURL(path.resolve('dist/extraction/tree-sitter.js')).href); +await grammars.loadGrammarsForLanguages(['glsl', 'hlsl']); + +const byExtension = {}; +let filesErrored = 0; +let nodes = 0; +let edges = 0; +let refs = 0; +const failures = []; +for (const absolute of files) { + const relative = path.relative(root, absolute).replace(/\\/g, '/'); + const extension = path.extname(relative).toLowerCase(); + byExtension[extension] = (byExtension[extension] || 0) + 1; + const source = fs.readFileSync(absolute, 'utf8'); + const first = extraction.extractFromSource(relative, source); + const second = extraction.extractFromSource(relative, source); + const fatal = first.errors.filter((error) => error.severity === 'error'); + const stable = + first.nodes.length === second.nodes.length && + first.edges.length === second.edges.length && + first.unresolvedReferences.length === second.unresolvedReferences.length && + first.nodes.map((node) => node.id).join('\0') === second.nodes.map((node) => node.id).join('\0'); + if (fatal.length > 0 || !stable) { + filesErrored++; + failures.push({ file: relative, errors: fatal.map((error) => error.message), stable }); + } + nodes += first.nodes.length; + edges += first.edges.length; + refs += first.unresolvedReferences.length; +} + +const firstPartyGlsl = files.filter((file) => { + const relative = path.relative(root, file).replace(/\\/g, '/'); + return relative.startsWith('shaders/') && glslExts.has(path.extname(relative).toLowerCase()); +}).length; +const summary = { root, files: files.length, firstPartyGlsl, filesErrored, nodes, edges, unresolvedReferences: refs, byExtension, failures }; +console.log(JSON.stringify(summary, null, 2)); +if (filesErrored > 0 || (expectedGlsl !== null && firstPartyGlsl !== expectedGlsl)) process.exit(1); diff --git a/site/src/content/docs/guides/affected-tests.md b/site/src/content/docs/guides/affected-tests.md index b51910561..83730f26c 100644 --- a/site/src/content/docs/guides/affected-tests.md +++ b/site/src/content/docs/guides/affected-tests.md @@ -21,6 +21,12 @@ codegraph affected src/auth.ts --filter "e2e/*" # custom test-file pattern | `-j, --json` | Output as JSON | `false` | | `-q, --quiet` | Output file paths only | `false` | +For project-owned changes, automatic test discovery excludes common vendored roots such as `External`, `vendor`, `third_party`, and `deps`. Changes inside those roots still return their own tests, and an explicit `--filter` includes any matching path. + +Test-directory matching is case-insensitive and includes shader entry files, so paths such as `Support/Tests/.../CompileTest.hlsl` can be returned when an included shader library changes. + +For shader changes, CodeGraph follows reverse include relationships to concrete compilation roots before looking for tests. This keeps application bridges isolated and classifies only entry shaders as tests; supporting `.hlsli` and `.fxh` headers are not returned merely because they live under a test directory. + ## CI / hook example ```bash diff --git a/site/src/content/docs/reference/cli.md b/site/src/content/docs/reference/cli.md index 0bf4c1ffe..1f21e9a53 100644 --- a/site/src/content/docs/reference/cli.md +++ b/site/src/content/docs/reference/cli.md @@ -17,9 +17,9 @@ codegraph query # Search symbols (--kind, --limit, --json) codegraph explore # Relevant symbols' source + call paths in one shot (same output as the codegraph_explore MCP tool) codegraph node # One symbol's source + callers, or read a file with line numbers (same output as codegraph_node) codegraph files [path] # Show file structure (--format, --filter, --pattern, --max-depth, --json) -codegraph callers # Find what calls a function/method (--limit, --json) -codegraph callees # Find what a function/method calls (--limit, --json) -codegraph impact # Analyze what code is affected by changing a symbol (--depth, --json) +codegraph callers # Find what calls a function/method (--file, --line, --limit, --json) +codegraph callees # Find what a function/method calls (--file, --line, --limit, --json) +codegraph impact # Analyze what code is affected by changing a symbol (--file, --line, --depth, --json) codegraph affected [files...] # Find test files affected by changes (see below) codegraph daemon # Manage background daemons — pick one to stop (alias: daemons) codegraph telemetry [on|off] # Show or change anonymous usage telemetry @@ -30,6 +30,10 @@ codegraph help [command] # Show help, optionally for one command The MCP server (`codegraph serve --mcp`) is launched automatically by your agent — you don't run it by hand. See [MCP Server](/codegraph/reference/mcp-server/). +## status + +`codegraph status --json` distinguishes tracked files from graph-producing files with `fileCount`, `graphFileNodeCount`, and `filesWithoutNodes`. It also reports attempted references that could not be resolved under `index.failedRefs`, grouped by language and reference kind, so a current index does not hide incomplete semantic coverage. + ## init, index, and sync `codegraph init` creates the local `.codegraph/` directory **and** builds the full graph in one step. (The old `-i`/`--index` flag is now a no-op, accepted only so existing scripts don't break.) After that the file watcher keeps the graph current automatically — `index` (a full rebuild from scratch) and `sync` (an incremental update) are only needed when the watcher is disabled or you're scripting against the index outside an agent session. @@ -42,8 +46,11 @@ The MCP server (`codegraph serve --mcp`) is launched automatically by your agent codegraph query UserService --kind class --limit 10 codegraph callers handleRequest --json codegraph impact AuthMiddleware --depth 3 +codegraph impact bufferResource --file src/renderer.cpp --line 8311 ``` +Use `--file` and `--line` together when one file contains repeated local functions or lambdas with the same name. + `explore` and `node` are the CLI faces of the `codegraph_explore` and `codegraph_node` MCP tools — same output — so subagents and non-MCP harnesses can reach the graph from a shell. ## affected diff --git a/site/src/content/docs/reference/languages.md b/site/src/content/docs/reference/languages.md index 0c5587773..20fc8b60b 100644 --- a/site/src/content/docs/reference/languages.md +++ b/site/src/content/docs/reference/languages.md @@ -18,6 +18,10 @@ Language support is automatic from the file extension — there's nothing to con | Ruby | `.rb` | Full support | | C | `.c`, `.h` | Full support | | C++ | `.cpp`, `.hpp`, `.cc` | Full support | +| GLSL / Vulkan GLSL | `.glsl`, `.vert`, `.frag`, `.comp`, `.geom`, `.tesc`, `.tese`, `.rgen`, `.rmiss`, `.rchit`, `.rahit`, `.rint`, `.rcall`, `.mesh`, `.task`, `.glslfx` | Full support (OpenUSD GLSLFX included) | +| HLSL | `.hlsl`, `.hlsli`, `.fx`, `.fxh` | Full support | +| CMake | `CMakeLists.txt`, `.cmake` | Build-script support | +| PowerShell | `.ps1`, `.psm1`, `.psd1` | Build-script support with `Import-Module`, cross-module functions, and script-scope call edges | | Objective-C | `.m`, `.mm`, `.h` | Partial support (classes, protocols, methods, `@property`, `#import`, message sends; `.mm` ObjC++ may parse incompletely) | | Swift | `.swift` | Full support | | Kotlin | `.kt`, `.kts` | Full support | From 3678dc21beae90214d45642bf7b5405992f67dc7 Mon Sep 17 00:00:00 2001 From: Manh Hung Date: Sat, 18 Jul 2026 11:23:29 +0700 Subject: [PATCH 3/3] fix: preserve dynamic dispatch labeling --- src/mcp/tools.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts index d21757e2b..6b550255e 100644 --- a/src/mcp/tools.ts +++ b/src/mcp/tools.ts @@ -2027,7 +2027,9 @@ export class ToolHandler { const paths = new Map(); const addPath = (source: Node, target: Node, edge: Edge) => { - if (edge.kind !== 'calls') return; + // Heuristic calls describe dynamic dispatch and need their synthesized + // label + wiring site; never flatten them into a proven static call path. + if (edge.kind !== 'calls' || edge.provenance === 'heuristic') return; if (fileHints.length > 0 && !matchesHint(source.filePath) && !matchesHint(target.filePath)) return; paths.set(`${source.id}\0${target.id}`, { source, target, edge }); };