@veewo/gitnexus 1.5.0-rc.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/benchmark/analyze-runner.d.ts +1 -1
- package/dist/benchmark/analyze-runner.js +4 -3
- package/dist/benchmark/analyze-runner.test.js +7 -0
- package/dist/cli/ai-context.d.ts +0 -1
- package/dist/cli/ai-context.js +15 -6
- package/dist/cli/analyze-options.js +58 -34
- package/dist/cli/analyze-options.test.js +57 -0
- package/dist/cli/analyze-runtime-summary.js +1 -0
- package/dist/cli/analyze-runtime-summary.test.js +10 -0
- package/dist/cli/analyze-summary.d.ts +2 -0
- package/dist/cli/analyze-summary.js +19 -0
- package/dist/cli/analyze.d.ts +11 -0
- package/dist/cli/analyze.js +30 -5
- package/dist/cli/analyze.test.d.ts +1 -0
- package/dist/cli/analyze.test.js +25 -0
- package/dist/cli/benchmark-agent-context.js +1 -1
- package/dist/cli/benchmark-unity.js +1 -1
- package/dist/cli/benchmark-unity.test.js +5 -1
- package/dist/cli/index.js +4 -2
- package/dist/cli/scope-manifest-config.d.ts +9 -0
- package/dist/cli/scope-manifest-config.js +37 -0
- package/dist/cli/setup.js +40 -41
- package/dist/cli/setup.test.js +14 -14
- package/dist/cli/sync-manifest.d.ts +27 -0
- package/dist/cli/sync-manifest.js +200 -0
- package/dist/cli/sync-manifest.test.d.ts +1 -0
- package/dist/cli/sync-manifest.test.js +88 -0
- package/dist/core/config/unity-config.d.ts +1 -0
- package/dist/core/config/unity-config.js +1 -0
- package/dist/core/ingestion/call-processor.d.ts +2 -1
- package/dist/core/ingestion/call-processor.js +28 -6
- package/dist/core/ingestion/heritage-processor.d.ts +2 -1
- package/dist/core/ingestion/heritage-processor.js +30 -7
- package/dist/core/ingestion/import-processor.d.ts +2 -1
- package/dist/core/ingestion/import-processor.js +28 -6
- package/dist/core/ingestion/parsing-processor.d.ts +5 -3
- package/dist/core/ingestion/parsing-processor.js +46 -13
- package/dist/core/ingestion/pipeline.js +65 -13
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +1 -1
- package/dist/core/ingestion/unity-runtime-binding-rules.js +21 -18
- package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
- package/dist/core/ingestion/workers/parse-worker.js +50 -6
- package/dist/core/tree-sitter/csharp-define-profile.d.ts +6 -0
- package/dist/core/tree-sitter/csharp-define-profile.js +43 -0
- package/dist/core/tree-sitter/csharp-preproc-normalizer.d.ts +14 -0
- package/dist/core/tree-sitter/csharp-preproc-normalizer.js +261 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +10 -0
- package/dist/core/tree-sitter/parser-loader.js +19 -0
- package/dist/types/pipeline.d.ts +13 -0
- package/package.json +12 -12
- package/scripts/check-sync-manifest-traceability.mjs +203 -0
- package/scripts/tree-sitter-audit-classify.mjs +172 -0
- package/skills/gitnexus-cli.md +36 -4
- package/skills/gitnexus-unity-rule-gen.md +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateId } from '../../lib/utils.js';
|
|
2
2
|
const RULE_EDGE_CONFIDENCE = 0.75;
|
|
3
|
-
export function applyUnityRuntimeBindingRules(graph, rules,
|
|
3
|
+
export function applyUnityRuntimeBindingRules(graph, rules, config) {
|
|
4
4
|
const ruleResults = [];
|
|
5
5
|
let totalEdges = 0;
|
|
6
6
|
const existingPairs = new Set();
|
|
@@ -27,7 +27,10 @@ export function applyUnityRuntimeBindingRules(graph, rules, _config) {
|
|
|
27
27
|
};
|
|
28
28
|
// Pre-build indexes
|
|
29
29
|
const methodsByClassId = new Map();
|
|
30
|
-
const
|
|
30
|
+
const containerNodes = [];
|
|
31
|
+
const containerLabels = config.enableContainerNodes
|
|
32
|
+
? new Set(['Class', 'Struct', 'Interface', 'Record'])
|
|
33
|
+
: new Set(['Class']);
|
|
31
34
|
for (const rel of graph.iterRelationships()) {
|
|
32
35
|
if (rel.type !== 'HAS_METHOD')
|
|
33
36
|
continue;
|
|
@@ -39,8 +42,8 @@ export function applyUnityRuntimeBindingRules(graph, rules, _config) {
|
|
|
39
42
|
methodsByClassId.set(rel.sourceId, list);
|
|
40
43
|
}
|
|
41
44
|
for (const node of graph.iterNodes()) {
|
|
42
|
-
if (node.label
|
|
43
|
-
|
|
45
|
+
if (containerLabels.has(node.label))
|
|
46
|
+
containerNodes.push(node);
|
|
44
47
|
}
|
|
45
48
|
// Collect UNITY_ASSET_GUID_REF and UNITY_COMPONENT_INSTANCE edges
|
|
46
49
|
const assetGuidRefs = [];
|
|
@@ -67,10 +70,10 @@ export function applyUnityRuntimeBindingRules(graph, rules, _config) {
|
|
|
67
70
|
for (const rule of rules) {
|
|
68
71
|
let ruleEdges = 0;
|
|
69
72
|
for (const binding of rule.resource_bindings ?? []) {
|
|
70
|
-
ruleEdges += processBinding(binding, rule.id, assetGuidRefs, componentInstances, methodsByClassId,
|
|
73
|
+
ruleEdges += processBinding(binding, rule.id, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addSyntheticEdge);
|
|
71
74
|
}
|
|
72
75
|
if (rule.lifecycle_overrides?.additional_entry_points?.length) {
|
|
73
|
-
ruleEdges += processLifecycleOverrides(rule, methodsByClassId,
|
|
76
|
+
ruleEdges += processLifecycleOverrides(rule, methodsByClassId, containerNodes, addSyntheticEdge);
|
|
74
77
|
}
|
|
75
78
|
totalEdges += ruleEdges;
|
|
76
79
|
ruleResults.push({ ruleId: rule.id, edgesInjected: ruleEdges });
|
|
@@ -91,18 +94,18 @@ function findMethodsOnResource(resourceFileId, componentInstances, methodsByClas
|
|
|
91
94
|
}
|
|
92
95
|
return results;
|
|
93
96
|
}
|
|
94
|
-
function processBinding(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId,
|
|
97
|
+
function processBinding(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addEdge) {
|
|
95
98
|
if (binding.kind === 'asset_ref_loads_components') {
|
|
96
99
|
return processAssetRefLoadsComponents(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, addEdge);
|
|
97
100
|
}
|
|
98
101
|
if (binding.kind === 'method_triggers_field_load') {
|
|
99
|
-
return processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId,
|
|
102
|
+
return processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, addEdge);
|
|
100
103
|
}
|
|
101
104
|
if (binding.kind === 'method_triggers_scene_load') {
|
|
102
|
-
return processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId,
|
|
105
|
+
return processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addEdge);
|
|
103
106
|
}
|
|
104
107
|
if (binding.kind === 'method_triggers_method') {
|
|
105
|
-
return processMethodTriggersMethod(binding, ruleId, methodsByClassId,
|
|
108
|
+
return processMethodTriggersMethod(binding, ruleId, methodsByClassId, containerNodes, addEdge);
|
|
106
109
|
}
|
|
107
110
|
return 0;
|
|
108
111
|
}
|
|
@@ -132,7 +135,7 @@ function processAssetRefLoadsComponents(binding, ruleId, assetGuidRefs, componen
|
|
|
132
135
|
}
|
|
133
136
|
return count;
|
|
134
137
|
}
|
|
135
|
-
function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId,
|
|
138
|
+
function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componentInstances, methodsByClassId, containerNodes, addEdge) {
|
|
136
139
|
let count = 0;
|
|
137
140
|
const classPattern = binding.host_class_pattern ? new RegExp(binding.host_class_pattern) : null;
|
|
138
141
|
const loaderMethodNames = new Set(binding.loader_methods ?? []);
|
|
@@ -148,7 +151,7 @@ function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componen
|
|
|
148
151
|
list.push(ref);
|
|
149
152
|
refsBySource.set(ref.sourceId, list);
|
|
150
153
|
}
|
|
151
|
-
for (const cls of
|
|
154
|
+
for (const cls of containerNodes) {
|
|
152
155
|
if (!classPattern.test(cls.properties.name))
|
|
153
156
|
continue;
|
|
154
157
|
const methods = methodsByClassId.get(cls.id) ?? [];
|
|
@@ -176,7 +179,7 @@ function processMethodTriggersFieldLoad(binding, ruleId, assetGuidRefs, componen
|
|
|
176
179
|
}
|
|
177
180
|
return count;
|
|
178
181
|
}
|
|
179
|
-
function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId,
|
|
182
|
+
function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, methodsByClassId, containerNodes, sceneFilesByName, addEdge) {
|
|
180
183
|
const classPattern = binding.host_class_pattern ? new RegExp(binding.host_class_pattern) : null;
|
|
181
184
|
const loaderMethodNames = new Set(binding.loader_methods ?? []);
|
|
182
185
|
const sceneName = binding.scene_name;
|
|
@@ -190,7 +193,7 @@ function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, met
|
|
|
190
193
|
if (sceneFileIds.length === 0)
|
|
191
194
|
return 0;
|
|
192
195
|
let count = 0;
|
|
193
|
-
for (const cls of
|
|
196
|
+
for (const cls of containerNodes) {
|
|
194
197
|
if (!classPattern.test(cls.properties.name))
|
|
195
198
|
continue;
|
|
196
199
|
const methods = methodsByClassId.get(cls.id) ?? [];
|
|
@@ -209,7 +212,7 @@ function processMethodTriggersSceneLoad(binding, ruleId, componentInstances, met
|
|
|
209
212
|
}
|
|
210
213
|
return count;
|
|
211
214
|
}
|
|
212
|
-
function processMethodTriggersMethod(binding, ruleId, methodsByClassId,
|
|
215
|
+
function processMethodTriggersMethod(binding, ruleId, methodsByClassId, containerNodes, addEdge) {
|
|
213
216
|
const { source_class_pattern, source_method, target_class_pattern, target_method } = binding;
|
|
214
217
|
if (!source_class_pattern || !source_method || !target_class_pattern || !target_method)
|
|
215
218
|
return 0;
|
|
@@ -217,7 +220,7 @@ function processMethodTriggersMethod(binding, ruleId, methodsByClassId, classNod
|
|
|
217
220
|
const tgtPattern = new RegExp(target_class_pattern);
|
|
218
221
|
let sourceMethodId;
|
|
219
222
|
let targetMethodId;
|
|
220
|
-
for (const cls of
|
|
223
|
+
for (const cls of containerNodes) {
|
|
221
224
|
if (!sourceMethodId && srcPattern.test(cls.properties.name)) {
|
|
222
225
|
const match = (methodsByClassId.get(cls.id) ?? []).find(m => m.properties.name === source_method);
|
|
223
226
|
if (match)
|
|
@@ -235,7 +238,7 @@ function processMethodTriggersMethod(binding, ruleId, methodsByClassId, classNod
|
|
|
235
238
|
return 0;
|
|
236
239
|
return addEdge(sourceMethodId, targetMethodId, `unity-rule-method-bridge:${ruleId}`) ? 1 : 0;
|
|
237
240
|
}
|
|
238
|
-
function processLifecycleOverrides(rule, methodsByClassId,
|
|
241
|
+
function processLifecycleOverrides(rule, methodsByClassId, containerNodes, addEdge) {
|
|
239
242
|
const overrides = rule.lifecycle_overrides;
|
|
240
243
|
if (!overrides?.additional_entry_points?.length)
|
|
241
244
|
return 0;
|
|
@@ -243,7 +246,7 @@ function processLifecycleOverrides(rule, methodsByClassId, classNodes, addEdge)
|
|
|
243
246
|
const scopePattern = overrides.scope ? new RegExp(overrides.scope) : null;
|
|
244
247
|
let count = 0;
|
|
245
248
|
const runtimeRootId = generateId('Method', 'unity-runtime-root');
|
|
246
|
-
for (const cls of
|
|
249
|
+
for (const cls of containerNodes) {
|
|
247
250
|
if (scopePattern && !scopePattern.test(cls.properties.filePath ?? cls.properties.name))
|
|
248
251
|
continue;
|
|
249
252
|
for (const method of methodsByClassId.get(cls.id) ?? []) {
|
|
@@ -97,10 +97,12 @@ export interface ParseWorkerResult {
|
|
|
97
97
|
routes: ExtractedRoute[];
|
|
98
98
|
constructorBindings: FileConstructorBindings[];
|
|
99
99
|
skippedLanguages: Record<string, number>;
|
|
100
|
+
csharpPreprocFallbackFiles: number;
|
|
100
101
|
fileCount: number;
|
|
101
102
|
}
|
|
102
103
|
export interface ParseWorkerInput {
|
|
103
104
|
path: string;
|
|
104
105
|
content: string;
|
|
106
|
+
rawContent?: string;
|
|
105
107
|
}
|
|
106
108
|
export {};
|
|
@@ -14,7 +14,7 @@ import Ruby from 'tree-sitter-ruby';
|
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
16
16
|
import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
|
|
17
|
-
import {
|
|
17
|
+
import { TREE_SITTER_MAX_BUFFER } from '../constants.js';
|
|
18
18
|
// tree-sitter-swift is an optionalDependency — may not be installed
|
|
19
19
|
const _require = createRequire(import.meta.url);
|
|
20
20
|
let Swift = null;
|
|
@@ -166,6 +166,7 @@ const processBatch = (files, onProgress) => {
|
|
|
166
166
|
routes: [],
|
|
167
167
|
constructorBindings: [],
|
|
168
168
|
skippedLanguages: {},
|
|
169
|
+
csharpPreprocFallbackFiles: 0,
|
|
169
170
|
fileCount: 0,
|
|
170
171
|
};
|
|
171
172
|
// Group by language to minimize setLanguage calls
|
|
@@ -716,14 +717,56 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
716
717
|
if (file.content.length > TREE_SITTER_MAX_BUFFER)
|
|
717
718
|
continue;
|
|
718
719
|
let tree;
|
|
720
|
+
let usedRawContentFallback = false;
|
|
719
721
|
try {
|
|
720
|
-
|
|
722
|
+
const MAX_CHUNK = 4096;
|
|
723
|
+
tree = parser.parse((index) => {
|
|
724
|
+
if (index >= file.content.length)
|
|
725
|
+
return null;
|
|
726
|
+
return file.content.slice(index, index + MAX_CHUNK);
|
|
727
|
+
});
|
|
721
728
|
}
|
|
722
729
|
catch (err) {
|
|
723
|
-
|
|
724
|
-
|
|
730
|
+
if (file.rawContent && file.rawContent !== file.content) {
|
|
731
|
+
try {
|
|
732
|
+
const MAX_CHUNK = 4096;
|
|
733
|
+
tree = parser.parse((index) => {
|
|
734
|
+
if (index >= file.rawContent.length)
|
|
735
|
+
return null;
|
|
736
|
+
return file.rawContent.slice(index, index + MAX_CHUNK);
|
|
737
|
+
});
|
|
738
|
+
usedRawContentFallback = true;
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (file.rawContent && file.rawContent !== file.content && tree.rootNode?.hasError) {
|
|
751
|
+
try {
|
|
752
|
+
const MAX_CHUNK = 4096;
|
|
753
|
+
const rawTree = parser.parse((index) => {
|
|
754
|
+
if (index >= file.rawContent.length)
|
|
755
|
+
return null;
|
|
756
|
+
return file.rawContent.slice(index, index + MAX_CHUNK);
|
|
757
|
+
});
|
|
758
|
+
if (!rawTree.rootNode?.hasError) {
|
|
759
|
+
tree = rawTree;
|
|
760
|
+
usedRawContentFallback = true;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
// Keep normalized parse result when raw fallback parsing fails
|
|
765
|
+
}
|
|
725
766
|
}
|
|
726
767
|
result.fileCount++;
|
|
768
|
+
if (usedRawContentFallback)
|
|
769
|
+
result.csharpPreprocFallbackFiles += 1;
|
|
727
770
|
onFileProcessed?.();
|
|
728
771
|
// Build per-file type environment + constructor bindings in a single AST walk.
|
|
729
772
|
// Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
|
|
@@ -1030,7 +1073,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1030
1073
|
/** Accumulated result across sub-batches */
|
|
1031
1074
|
let accumulated = {
|
|
1032
1075
|
nodes: [], relationships: [], symbols: [],
|
|
1033
|
-
imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
|
|
1076
|
+
imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, csharpPreprocFallbackFiles: 0, fileCount: 0,
|
|
1034
1077
|
};
|
|
1035
1078
|
let cumulativeProcessed = 0;
|
|
1036
1079
|
const mergeResult = (target, src) => {
|
|
@@ -1045,6 +1088,7 @@ const mergeResult = (target, src) => {
|
|
|
1045
1088
|
for (const [lang, count] of Object.entries(src.skippedLanguages)) {
|
|
1046
1089
|
target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
|
|
1047
1090
|
}
|
|
1091
|
+
target.csharpPreprocFallbackFiles += src.csharpPreprocFallbackFiles;
|
|
1048
1092
|
target.fileCount += src.fileCount;
|
|
1049
1093
|
};
|
|
1050
1094
|
parentPort.on('message', (msg) => {
|
|
@@ -1064,7 +1108,7 @@ parentPort.on('message', (msg) => {
|
|
|
1064
1108
|
if (msg && msg.type === 'flush') {
|
|
1065
1109
|
parentPort.postMessage({ type: 'result', data: accumulated });
|
|
1066
1110
|
// Reset for potential reuse
|
|
1067
|
-
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
|
|
1111
|
+
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, csharpPreprocFallbackFiles: 0, fileCount: 0 };
|
|
1068
1112
|
cumulativeProcessed = 0;
|
|
1069
1113
|
return;
|
|
1070
1114
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
const DEFINE_CONSTANTS_RE = /<DefineConstants>\s*([\s\S]*?)\s*<\/DefineConstants>/gi;
|
|
4
|
+
const CSHARP_SYMBOL_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5
|
+
function parseSymbols(rawDefineConstants) {
|
|
6
|
+
const symbols = new Set();
|
|
7
|
+
for (const raw of rawDefineConstants) {
|
|
8
|
+
for (const token of raw.split(';')) {
|
|
9
|
+
const symbol = token.trim();
|
|
10
|
+
if (!symbol)
|
|
11
|
+
continue;
|
|
12
|
+
// Drop MSBuild placeholders such as $(DefineConstants)
|
|
13
|
+
if (symbol.includes('$('))
|
|
14
|
+
continue;
|
|
15
|
+
if (!CSHARP_SYMBOL_RE.test(symbol))
|
|
16
|
+
continue;
|
|
17
|
+
symbols.add(symbol);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return symbols;
|
|
21
|
+
}
|
|
22
|
+
export async function loadCSharpDefineProfileFromCsproj(csprojPath) {
|
|
23
|
+
const sourcePath = path.resolve(csprojPath);
|
|
24
|
+
let content;
|
|
25
|
+
try {
|
|
26
|
+
content = await fs.readFile(sourcePath, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
throw new Error(`Failed to read C# csproj: ${sourcePath} (${message})`);
|
|
31
|
+
}
|
|
32
|
+
const rawDefineConstants = [];
|
|
33
|
+
for (const match of content.matchAll(DEFINE_CONSTANTS_RE)) {
|
|
34
|
+
const raw = (match[1] || '').trim();
|
|
35
|
+
if (raw)
|
|
36
|
+
rawDefineConstants.push(raw);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
symbols: parseSymbols(rawDefineConstants),
|
|
40
|
+
sourcePath,
|
|
41
|
+
rawDefineConstants,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CSharpPreprocNormalizationDiagnostics {
|
|
2
|
+
directivesSeen: number;
|
|
3
|
+
inactiveLines: number;
|
|
4
|
+
undefinedSymbols: string[];
|
|
5
|
+
expressionErrors: number;
|
|
6
|
+
unmatchedEndif: number;
|
|
7
|
+
unterminatedIfBlocks: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CSharpPreprocNormalizationResult {
|
|
10
|
+
normalizedText: string;
|
|
11
|
+
changed: boolean;
|
|
12
|
+
diagnostics: CSharpPreprocNormalizationDiagnostics;
|
|
13
|
+
}
|
|
14
|
+
export declare function normalizeCSharpPreprocessorBranches(source: string, defines: Set<string>): CSharpPreprocNormalizationResult;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
class ExprParser {
|
|
2
|
+
tokens;
|
|
3
|
+
index = 0;
|
|
4
|
+
defines;
|
|
5
|
+
undefinedSymbols;
|
|
6
|
+
constructor(tokens, defines, undefinedSymbols) {
|
|
7
|
+
this.tokens = tokens;
|
|
8
|
+
this.defines = defines;
|
|
9
|
+
this.undefinedSymbols = undefinedSymbols;
|
|
10
|
+
}
|
|
11
|
+
parse() {
|
|
12
|
+
const value = this.parseOr();
|
|
13
|
+
this.expect('eof');
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
parseOr() {
|
|
17
|
+
let value = this.parseAnd();
|
|
18
|
+
while (this.match('or')) {
|
|
19
|
+
value = value || this.parseAnd();
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
parseAnd() {
|
|
24
|
+
let value = this.parseUnary();
|
|
25
|
+
while (this.match('and')) {
|
|
26
|
+
value = value && this.parseUnary();
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
parseUnary() {
|
|
31
|
+
if (this.match('not')) {
|
|
32
|
+
return !this.parseUnary();
|
|
33
|
+
}
|
|
34
|
+
return this.parsePrimary();
|
|
35
|
+
}
|
|
36
|
+
parsePrimary() {
|
|
37
|
+
if (this.match('lparen')) {
|
|
38
|
+
const value = this.parseOr();
|
|
39
|
+
this.expect('rparen');
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
const token = this.current();
|
|
43
|
+
if (token.type === 'bool') {
|
|
44
|
+
this.index += 1;
|
|
45
|
+
return token.value === 'true';
|
|
46
|
+
}
|
|
47
|
+
if (token.type === 'ident') {
|
|
48
|
+
this.index += 1;
|
|
49
|
+
const symbol = token.value || '';
|
|
50
|
+
if (!this.defines.has(symbol))
|
|
51
|
+
this.undefinedSymbols.add(symbol);
|
|
52
|
+
return this.defines.has(symbol);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Unexpected token "${token.type}" in preprocessor expression`);
|
|
55
|
+
}
|
|
56
|
+
current() {
|
|
57
|
+
return this.tokens[this.index] || { type: 'eof' };
|
|
58
|
+
}
|
|
59
|
+
match(type) {
|
|
60
|
+
if (this.current().type !== type)
|
|
61
|
+
return false;
|
|
62
|
+
this.index += 1;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
expect(type) {
|
|
66
|
+
if (!this.match(type)) {
|
|
67
|
+
throw new Error(`Expected token "${type}" in preprocessor expression`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function tokenizeExpression(expression) {
|
|
72
|
+
const tokens = [];
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < expression.length) {
|
|
75
|
+
const ch = expression[i] || '';
|
|
76
|
+
if (/\s/.test(ch)) {
|
|
77
|
+
i += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (expression.startsWith('&&', i)) {
|
|
81
|
+
tokens.push({ type: 'and' });
|
|
82
|
+
i += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (expression.startsWith('||', i)) {
|
|
86
|
+
tokens.push({ type: 'or' });
|
|
87
|
+
i += 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (ch === '!') {
|
|
91
|
+
tokens.push({ type: 'not' });
|
|
92
|
+
i += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ch === '(') {
|
|
96
|
+
tokens.push({ type: 'lparen' });
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (ch === ')') {
|
|
101
|
+
tokens.push({ type: 'rparen' });
|
|
102
|
+
i += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
106
|
+
let j = i + 1;
|
|
107
|
+
while (j < expression.length && /[A-Za-z0-9_]/.test(expression[j] || '')) {
|
|
108
|
+
j += 1;
|
|
109
|
+
}
|
|
110
|
+
const ident = expression.slice(i, j);
|
|
111
|
+
if (ident === 'true' || ident === 'false') {
|
|
112
|
+
tokens.push({ type: 'bool', value: ident });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
tokens.push({ type: 'ident', value: ident });
|
|
116
|
+
}
|
|
117
|
+
i = j;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Unsupported token "${ch}" in preprocessor expression: ${expression}`);
|
|
121
|
+
}
|
|
122
|
+
tokens.push({ type: 'eof' });
|
|
123
|
+
return tokens;
|
|
124
|
+
}
|
|
125
|
+
function evaluateExpression(expression, defines, undefinedSymbols) {
|
|
126
|
+
if (!expression.trim())
|
|
127
|
+
return { value: false, ok: false };
|
|
128
|
+
try {
|
|
129
|
+
const parser = new ExprParser(tokenizeExpression(expression), defines, undefinedSymbols);
|
|
130
|
+
return { value: parser.parse(), ok: true };
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return { value: false, ok: false };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function deriveLineEnding(source) {
|
|
137
|
+
return source.includes('\r\n') ? '\r\n' : '\n';
|
|
138
|
+
}
|
|
139
|
+
function hasTrailingLineEnding(source) {
|
|
140
|
+
return /\r?\n$/.test(source);
|
|
141
|
+
}
|
|
142
|
+
export function normalizeCSharpPreprocessorBranches(source, defines) {
|
|
143
|
+
if (!source.includes('#if') && !source.includes('#elif') && !source.includes('#else') && !source.includes('#endif')) {
|
|
144
|
+
return {
|
|
145
|
+
normalizedText: source,
|
|
146
|
+
changed: false,
|
|
147
|
+
diagnostics: {
|
|
148
|
+
directivesSeen: 0,
|
|
149
|
+
inactiveLines: 0,
|
|
150
|
+
undefinedSymbols: [],
|
|
151
|
+
expressionErrors: 0,
|
|
152
|
+
unmatchedEndif: 0,
|
|
153
|
+
unterminatedIfBlocks: 0,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const lineEnding = deriveLineEnding(source);
|
|
158
|
+
const trailingLineEnding = hasTrailingLineEnding(source);
|
|
159
|
+
const lines = source.split(/\r?\n/);
|
|
160
|
+
const outputLines = [];
|
|
161
|
+
const stack = [];
|
|
162
|
+
const undefinedSymbols = new Set();
|
|
163
|
+
let active = true;
|
|
164
|
+
let changed = false;
|
|
165
|
+
let directivesSeen = 0;
|
|
166
|
+
let inactiveLines = 0;
|
|
167
|
+
let expressionErrors = 0;
|
|
168
|
+
let unmatchedEndif = 0;
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const directive = line.match(/^\s*#\s*(if|elif|else|endif)\b(.*)$/);
|
|
171
|
+
if (directive) {
|
|
172
|
+
directivesSeen += 1;
|
|
173
|
+
changed = true;
|
|
174
|
+
const keyword = directive[1] || '';
|
|
175
|
+
const rawExpression = (directive[2] || '').trim();
|
|
176
|
+
if (keyword === 'if') {
|
|
177
|
+
const evalResult = evaluateExpression(rawExpression, defines, undefinedSymbols);
|
|
178
|
+
if (!evalResult.ok)
|
|
179
|
+
expressionErrors += 1;
|
|
180
|
+
const branchActive = active && evalResult.value;
|
|
181
|
+
stack.push({
|
|
182
|
+
parentActive: active,
|
|
183
|
+
branchTaken: branchActive,
|
|
184
|
+
currentActive: branchActive,
|
|
185
|
+
seenElse: false,
|
|
186
|
+
});
|
|
187
|
+
active = branchActive;
|
|
188
|
+
}
|
|
189
|
+
else if (keyword === 'elif') {
|
|
190
|
+
const frame = stack[stack.length - 1];
|
|
191
|
+
if (!frame) {
|
|
192
|
+
unmatchedEndif += 1;
|
|
193
|
+
}
|
|
194
|
+
else if (frame.seenElse) {
|
|
195
|
+
expressionErrors += 1;
|
|
196
|
+
frame.currentActive = false;
|
|
197
|
+
active = false;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const evalResult = evaluateExpression(rawExpression, defines, undefinedSymbols);
|
|
201
|
+
if (!evalResult.ok)
|
|
202
|
+
expressionErrors += 1;
|
|
203
|
+
const branchActive = frame.parentActive && !frame.branchTaken && evalResult.value;
|
|
204
|
+
frame.currentActive = branchActive;
|
|
205
|
+
frame.branchTaken = frame.branchTaken || branchActive;
|
|
206
|
+
active = frame.currentActive;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (keyword === 'else') {
|
|
210
|
+
const frame = stack[stack.length - 1];
|
|
211
|
+
if (!frame) {
|
|
212
|
+
unmatchedEndif += 1;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const branchActive = frame.parentActive && !frame.branchTaken;
|
|
216
|
+
frame.currentActive = branchActive;
|
|
217
|
+
frame.branchTaken = true;
|
|
218
|
+
frame.seenElse = true;
|
|
219
|
+
active = frame.currentActive;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (keyword === 'endif') {
|
|
223
|
+
if (stack.length === 0) {
|
|
224
|
+
unmatchedEndif += 1;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
stack.pop();
|
|
228
|
+
}
|
|
229
|
+
const top = stack[stack.length - 1];
|
|
230
|
+
active = top ? top.currentActive : true;
|
|
231
|
+
}
|
|
232
|
+
outputLines.push('');
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (active) {
|
|
236
|
+
outputLines.push(line);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (line.length > 0) {
|
|
240
|
+
changed = true;
|
|
241
|
+
inactiveLines += 1;
|
|
242
|
+
}
|
|
243
|
+
outputLines.push('');
|
|
244
|
+
}
|
|
245
|
+
let normalizedText = outputLines.join(lineEnding);
|
|
246
|
+
if (trailingLineEnding && !normalizedText.endsWith(lineEnding)) {
|
|
247
|
+
normalizedText += lineEnding;
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
normalizedText,
|
|
251
|
+
changed,
|
|
252
|
+
diagnostics: {
|
|
253
|
+
directivesSeen,
|
|
254
|
+
inactiveLines,
|
|
255
|
+
undefinedSymbols: [...undefinedSymbols].sort(),
|
|
256
|
+
expressionErrors,
|
|
257
|
+
unmatchedEndif,
|
|
258
|
+
unterminatedIfBlocks: stack.length,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -3,3 +3,13 @@ import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
|
3
3
|
export declare const isLanguageAvailable: (language: SupportedLanguages) => boolean;
|
|
4
4
|
export declare const loadParser: () => Promise<Parser>;
|
|
5
5
|
export declare const loadLanguage: (language: SupportedLanguages, filePath?: string) => Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Parse source code using tree-sitter's chunked callback API.
|
|
8
|
+
* Avoids the native binding's single-buffer size limit (< 32768 bytes)
|
|
9
|
+
* that causes "Invalid argument" errors on large files.
|
|
10
|
+
*
|
|
11
|
+
* @param content - Full source file content as UTF-8 string
|
|
12
|
+
* @param oldTree - Optional previous tree for incremental parsing (must call tree.edit() first)
|
|
13
|
+
* @returns Parsed syntax tree
|
|
14
|
+
*/
|
|
15
|
+
export declare const parseContent: (content: string, oldTree?: any) => any;
|
|
@@ -61,3 +61,22 @@ export const loadLanguage = async (language, filePath) => {
|
|
|
61
61
|
}
|
|
62
62
|
parser.setLanguage(lang);
|
|
63
63
|
};
|
|
64
|
+
const MAX_CHUNK = 4096;
|
|
65
|
+
/**
|
|
66
|
+
* Parse source code using tree-sitter's chunked callback API.
|
|
67
|
+
* Avoids the native binding's single-buffer size limit (< 32768 bytes)
|
|
68
|
+
* that causes "Invalid argument" errors on large files.
|
|
69
|
+
*
|
|
70
|
+
* @param content - Full source file content as UTF-8 string
|
|
71
|
+
* @param oldTree - Optional previous tree for incremental parsing (must call tree.edit() first)
|
|
72
|
+
* @returns Parsed syntax tree
|
|
73
|
+
*/
|
|
74
|
+
export const parseContent = (content, oldTree) => {
|
|
75
|
+
if (!parser)
|
|
76
|
+
throw new Error('Parser not initialized — call loadParser() first');
|
|
77
|
+
return parser.parse((index) => {
|
|
78
|
+
if (index >= content.length)
|
|
79
|
+
return null;
|
|
80
|
+
return content.slice(index, index + MAX_CHUNK);
|
|
81
|
+
}, oldTree);
|
|
82
|
+
};
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -18,6 +18,17 @@ export interface PipelineProgress {
|
|
|
18
18
|
export interface PipelineRunOptions {
|
|
19
19
|
includeExtensions?: string[];
|
|
20
20
|
scopeRules?: string[];
|
|
21
|
+
csharpDefineCsproj?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CSharpPreprocDiagnostics {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
sourcePath?: string;
|
|
26
|
+
defineSymbolCount: number;
|
|
27
|
+
normalizedFiles: number;
|
|
28
|
+
fallbackFiles: number;
|
|
29
|
+
skippedFiles: number;
|
|
30
|
+
expressionErrors: number;
|
|
31
|
+
undefinedSymbols: string[];
|
|
21
32
|
}
|
|
22
33
|
export interface PipelineResult {
|
|
23
34
|
graph: KnowledgeGraph;
|
|
@@ -29,6 +40,7 @@ export interface PipelineResult {
|
|
|
29
40
|
processResult?: ProcessDetectionResult;
|
|
30
41
|
unityResult?: UnityResourceProcessingResult;
|
|
31
42
|
scopeDiagnostics?: ScopeSelectionDiagnostics;
|
|
43
|
+
csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
|
|
32
44
|
}
|
|
33
45
|
export interface PipelineRuntimeSummary {
|
|
34
46
|
totalFileCount: number;
|
|
@@ -36,6 +48,7 @@ export interface PipelineRuntimeSummary {
|
|
|
36
48
|
processResult?: ProcessDetectionResult;
|
|
37
49
|
unityResult?: UnityResourceProcessingResult;
|
|
38
50
|
scopeDiagnostics?: ScopeSelectionDiagnostics;
|
|
51
|
+
csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
|
|
39
52
|
}
|
|
40
53
|
export interface SerializablePipelineResult {
|
|
41
54
|
nodes: GraphNode[];
|