code-auditor-mcp 3.4.12 → 3.4.14
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/CHANGELOG.md +74 -1
- package/dist/analyzers/__verify_self_probe.d.ts +2 -0
- package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
- package/dist/analyzers/__verify_self_probe.js +5 -0
- package/dist/analyzers/__verify_self_probe.js.map +1 -0
- package/dist/analyzers/analyzerUtils.d.ts +34 -1
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +61 -22
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +259 -198
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +57 -50
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.js +10 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.js +42 -44
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.js +30 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +62 -2
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +443 -349
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +11 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +188 -125
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +110 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +1714 -124
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +54 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -0
- package/dist/analyzers/ruleTiming.js +71 -0
- package/dist/analyzers/ruleTiming.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +17 -202
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1073 -1121
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -214
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1521
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -480
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
- package/dist/analyzers/universal/schema/config.d.ts +31 -0
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/config.js +57 -0
- package/dist/analyzers/universal/schema/config.js.map +1 -0
- package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/discovery.js +616 -0
- package/dist/analyzers/universal/schema/discovery.js.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
- package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/migrations.js +238 -0
- package/dist/analyzers/universal/schema/migrations.js.map +1 -0
- package/dist/analyzers/universal/schema/types.d.ts +131 -0
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/types.js +10 -0
- package/dist/analyzers/universal/schema/types.js.map +1 -0
- package/dist/analyzers/universal/schema/violations.d.ts +44 -0
- package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/violations.js +54 -0
- package/dist/analyzers/universal/schema/violations.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +89 -13
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +315 -40
- package/dist/cli.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +9 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +12 -3
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +16 -6
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +75 -0
- package/dist/config/effectiveConfig.d.ts.map +1 -0
- package/dist/config/effectiveConfig.js +213 -0
- package/dist/config/effectiveConfig.js.map +1 -0
- package/dist/config/pathProfiles.d.ts +7 -4
- package/dist/config/pathProfiles.d.ts.map +1 -1
- package/dist/config/pathProfiles.js +8 -10
- package/dist/config/pathProfiles.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +45 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -0
- package/dist/config/thresholdRationales.js +67 -0
- package/dist/config/thresholdRationales.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +6 -2
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/enforcement/diffGate.d.ts +55 -0
- package/dist/enforcement/diffGate.d.ts.map +1 -0
- package/dist/enforcement/diffGate.js +136 -0
- package/dist/enforcement/diffGate.js.map +1 -0
- package/dist/enforcement/gate.d.ts +36 -0
- package/dist/enforcement/gate.d.ts.map +1 -0
- package/dist/enforcement/gate.js +72 -0
- package/dist/enforcement/gate.js.map +1 -0
- package/dist/enforcement/suppressions.d.ts +41 -0
- package/dist/enforcement/suppressions.d.ts.map +1 -0
- package/dist/enforcement/suppressions.js +170 -0
- package/dist/enforcement/suppressions.js.map +1 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +4 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -4
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +2 -2
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +14 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +93 -55
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts +8 -0
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/LanguageRegistry.js +8 -0
- package/dist/languages/LanguageRegistry.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +82 -60
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +404 -349
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +51 -6
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +127 -88
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +36 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +89 -35
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +66 -23
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +553 -474
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +33 -4
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +101 -66
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +18 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +18 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +39 -2
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +93 -12
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/types.d.ts +67 -2
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -72
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1419 -923
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/pipeline.d.ts +13 -5
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +381 -200
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +3 -8
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +149 -94
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/presets/presets.d.ts +54 -0
- package/dist/presets/presets.d.ts.map +1 -0
- package/dist/presets/presets.js +181 -0
- package/dist/presets/presets.js.map +1 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +6 -3
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts +67 -0
- package/dist/ruleAliases.d.ts.map +1 -0
- package/dist/ruleAliases.js +102 -0
- package/dist/ruleAliases.js.map +1 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +78 -18
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts +6 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +26 -4
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +8 -6
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +61 -15
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts +31 -0
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindProbe.js +145 -4
- package/dist/styles/tailwindProbe.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -1
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/types.d.ts +181 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/fileDiscovery.d.ts +9 -0
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +21 -0
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/package.json +5 -3
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/scripts/hook-audit.sh +25 -7
- package/plugin/skills/code-auditor/SKILL.md +5 -5
|
@@ -3,330 +3,156 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides real AST-based analysis for Go source files. Replaces the previous
|
|
5
5
|
* regex-based stub that returned empty arrays for most queries.
|
|
6
|
+
*
|
|
7
|
+
* The class is split across a small inheritance chain (Spec-33
|
|
8
|
+
* interface-segregation): each link contributes a cohesive slice of the
|
|
9
|
+
* adapter so no single class exceeds the SOLID class-size threshold. The
|
|
10
|
+
* chain is ordered leaf-first (traversal/name/doc/build helpers) so every
|
|
11
|
+
* method's callee lives in an ancestor class.
|
|
6
12
|
*/
|
|
7
|
-
import { getParser } from '../tree-sitter/parser.js';
|
|
13
|
+
import { getParser, parseWithRecovery } from '../tree-sitter/parser.js';
|
|
8
14
|
import { toASTNode, toSourceLocation } from '../tree-sitter/converter.js';
|
|
9
15
|
// ---------------------------------------------------------------------------
|
|
10
16
|
// Source code storage
|
|
11
17
|
// ---------------------------------------------------------------------------
|
|
12
18
|
const sourceCodeMap = new WeakMap();
|
|
13
19
|
// ---------------------------------------------------------------------------
|
|
14
|
-
//
|
|
20
|
+
// Traversal + name/documentation + Go-specific helpers slice (leaf helpers,
|
|
21
|
+
// called across later chunks, so they form the base of the chain)
|
|
15
22
|
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// -- Parsing --------------------------------------------------------------
|
|
24
|
-
async parse(filePath, content) {
|
|
25
|
-
const parser = getParser('go');
|
|
26
|
-
const tree = parser.parse(content);
|
|
27
|
-
if (!tree)
|
|
28
|
-
throw new Error(`Failed to parse Go file: ${filePath}`);
|
|
29
|
-
const errors = [];
|
|
30
|
-
this.collectErrors(tree.rootNode, errors);
|
|
31
|
-
const root = toASTNode(tree.rootNode, undefined, 'go');
|
|
32
|
-
const ast = {
|
|
33
|
-
root,
|
|
34
|
-
language: 'go',
|
|
35
|
-
filePath,
|
|
36
|
-
errors,
|
|
37
|
-
};
|
|
38
|
-
sourceCodeMap.set(ast, content);
|
|
39
|
-
return ast;
|
|
40
|
-
}
|
|
41
|
-
// -- AST Navigation -------------------------------------------------------
|
|
42
|
-
findNodes(ast, pattern) {
|
|
43
|
-
const results = [];
|
|
44
|
-
this.walk(ast.root, (node) => {
|
|
45
|
-
if (this.matchesPattern(node, pattern)) {
|
|
46
|
-
results.push(node);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
return results;
|
|
50
|
-
}
|
|
51
|
-
getParent(node) {
|
|
52
|
-
return node.parent ?? null;
|
|
53
|
-
}
|
|
54
|
-
getChildren(node) {
|
|
55
|
-
return node.children ?? [];
|
|
56
|
-
}
|
|
57
|
-
getSiblings(node) {
|
|
58
|
-
if (!node.parent?.children)
|
|
59
|
-
return [];
|
|
60
|
-
return node.parent.children.filter((c) => c !== node);
|
|
61
|
-
}
|
|
62
|
-
// -- Node Information -----------------------------------------------------
|
|
63
|
-
getNodeType(node) {
|
|
64
|
-
return node.type;
|
|
65
|
-
}
|
|
66
|
-
getNodeText(node, sourceCode) {
|
|
67
|
-
return sourceCode.slice(node.range[0], node.range[1]);
|
|
68
|
-
}
|
|
69
|
-
getNodeName(node) {
|
|
70
|
-
return this.extractName(node.raw);
|
|
71
|
-
}
|
|
72
|
-
getNodeLocation(node) {
|
|
73
|
-
return node.location;
|
|
74
|
-
}
|
|
75
|
-
// -- Language-Specific Extraction -----------------------------------------
|
|
76
|
-
extractFunctions(ast) {
|
|
77
|
-
const sourceCode = sourceCodeMap.get(ast) ?? '';
|
|
78
|
-
const functions = [];
|
|
79
|
-
this.walk(ast.root, (astNode) => {
|
|
80
|
-
const node = astNode.raw;
|
|
81
|
-
if (node.type === 'function_declaration') {
|
|
82
|
-
const fn = this.buildFunctionInfo(node, sourceCode);
|
|
83
|
-
if (fn)
|
|
84
|
-
functions.push(fn);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
return functions;
|
|
88
|
-
}
|
|
89
|
-
extractClasses(ast) {
|
|
90
|
-
const sourceCode = sourceCodeMap.get(ast) ?? '';
|
|
91
|
-
const classes = [];
|
|
92
|
-
// Collect all function_declarations so we can match methods to structs
|
|
93
|
-
const allFunctions = this.extractFunctions(ast);
|
|
94
|
-
this.walk(ast.root, (astNode) => {
|
|
95
|
-
const node = astNode.raw;
|
|
96
|
-
if (node.type === 'type_declaration') {
|
|
97
|
-
const specs = this.findNamedChildren(node, 'type_spec');
|
|
98
|
-
for (const spec of specs) {
|
|
99
|
-
const nameNode = spec.childForFieldName?.('name');
|
|
100
|
-
if (!nameNode)
|
|
101
|
-
continue;
|
|
102
|
-
const typeNode = spec.childForFieldName?.('type');
|
|
103
|
-
if (!typeNode || typeNode.type !== 'struct_type')
|
|
104
|
-
continue;
|
|
105
|
-
const structInfo = this.buildStructAsClass(spec, nameNode, typeNode, allFunctions, sourceCode);
|
|
106
|
-
if (structInfo)
|
|
107
|
-
classes.push(structInfo);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
return classes;
|
|
112
|
-
}
|
|
113
|
-
extractImports(ast) {
|
|
114
|
-
const imports = [];
|
|
115
|
-
this.walk(ast.root, (astNode) => {
|
|
116
|
-
const node = astNode.raw;
|
|
117
|
-
if (node.type === 'import_declaration') {
|
|
118
|
-
for (const spec of node.namedChildren) {
|
|
119
|
-
if (spec.type === 'import_spec') {
|
|
120
|
-
const imp = this.buildImportInfo(spec);
|
|
121
|
-
if (imp)
|
|
122
|
-
imports.push(imp);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return imports;
|
|
128
|
-
}
|
|
129
|
-
extractExports(ast) {
|
|
130
|
-
const exports = [];
|
|
131
|
-
this.walk(ast.root, (astNode) => {
|
|
132
|
-
const node = astNode.raw;
|
|
133
|
-
if (node.type === 'function_declaration') {
|
|
134
|
-
const name = this.extractName(node);
|
|
135
|
-
if (name && this.isExportedGo(name)) {
|
|
136
|
-
exports.push({
|
|
137
|
-
name,
|
|
138
|
-
location: toSourceLocation(node),
|
|
139
|
-
isDefault: false,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
else if (node.type === 'type_declaration') {
|
|
144
|
-
for (const spec of this.findNamedChildren(node, 'type_spec')) {
|
|
145
|
-
const nameNode = spec.childForFieldName?.('name');
|
|
146
|
-
if (nameNode && this.isExportedGo(nameNode.text)) {
|
|
147
|
-
exports.push({
|
|
148
|
-
name: nameNode.text,
|
|
149
|
-
location: toSourceLocation(nameNode),
|
|
150
|
-
isDefault: false,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (node.type === 'var_declaration') {
|
|
156
|
-
for (const spec of this.findNamedChildren(node, 'var_spec')) {
|
|
157
|
-
const nameNode = spec.childForFieldName?.('name');
|
|
158
|
-
if (nameNode && this.isExportedGo(nameNode.text)) {
|
|
159
|
-
exports.push({
|
|
160
|
-
name: nameNode.text,
|
|
161
|
-
location: toSourceLocation(nameNode),
|
|
162
|
-
isDefault: false,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
23
|
+
class GoTraversalHelpers {
|
|
24
|
+
// -- Tree traversal helpers ------------------------------------------------
|
|
25
|
+
walk(node, visitor) {
|
|
26
|
+
visitor(node);
|
|
27
|
+
if (node.children) {
|
|
28
|
+
for (const child of node.children) {
|
|
29
|
+
this.walk(child, visitor);
|
|
166
30
|
}
|
|
167
|
-
});
|
|
168
|
-
return exports;
|
|
169
|
-
}
|
|
170
|
-
// -- Pattern Matching Helpers ---------------------------------------------
|
|
171
|
-
isClass(node) {
|
|
172
|
-
const n = node.raw;
|
|
173
|
-
if (n.type === 'type_spec') {
|
|
174
|
-
const typeNode = n.childForFieldName?.('type');
|
|
175
|
-
return typeNode?.type === 'struct_type';
|
|
176
31
|
}
|
|
177
|
-
return n.type === 'struct_type';
|
|
178
32
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const n = node.raw;
|
|
184
|
-
if (n.type !== 'function_declaration')
|
|
185
|
-
return false;
|
|
186
|
-
return n.childForFieldName?.('receiver') != null;
|
|
187
|
-
}
|
|
188
|
-
isInterface(node) {
|
|
189
|
-
const n = node.raw;
|
|
190
|
-
if (n.type === 'type_spec') {
|
|
191
|
-
const typeNode = n.childForFieldName?.('type');
|
|
192
|
-
return typeNode?.type === 'interface_type';
|
|
33
|
+
walkRaw(node, visitor) {
|
|
34
|
+
visitor(node);
|
|
35
|
+
for (const child of node.children) {
|
|
36
|
+
this.walkRaw(child, visitor);
|
|
193
37
|
}
|
|
194
|
-
return n.type === 'interface_type';
|
|
195
|
-
}
|
|
196
|
-
isImport(node) {
|
|
197
|
-
return node.raw.type === 'import_declaration';
|
|
198
|
-
}
|
|
199
|
-
isExport(node) {
|
|
200
|
-
return false; // Go has no export keyword
|
|
201
|
-
}
|
|
202
|
-
isLoop(node) {
|
|
203
|
-
return node.raw.type === 'for_statement';
|
|
204
|
-
}
|
|
205
|
-
isConditional(node) {
|
|
206
|
-
const t = node.raw.type;
|
|
207
|
-
return (t === 'if_statement' ||
|
|
208
|
-
t === 'switch_statement' ||
|
|
209
|
-
t === 'expression_switch_statement' ||
|
|
210
|
-
t === 'type_switch_statement');
|
|
211
|
-
}
|
|
212
|
-
isVariableDeclaration(node) {
|
|
213
|
-
const t = node.raw.type;
|
|
214
|
-
return (t === 'var_declaration' ||
|
|
215
|
-
t === 'short_var_declaration' ||
|
|
216
|
-
t === 'var_spec' ||
|
|
217
|
-
t === 'const_declaration');
|
|
218
38
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
if (n.type === 'var_spec' || n.type === 'const_spec') {
|
|
228
|
-
const typeNode = n.childForFieldName?.('type');
|
|
229
|
-
if (typeNode)
|
|
230
|
-
return typeNode.text.trim();
|
|
39
|
+
collectErrors(node, errors) {
|
|
40
|
+
if (node.type === 'ERROR' || node.isError) {
|
|
41
|
+
errors.push({
|
|
42
|
+
message: `Parse error near "${node.text.slice(0, 50)}"`,
|
|
43
|
+
location: toSourceLocation(node),
|
|
44
|
+
severity: 'error',
|
|
45
|
+
});
|
|
231
46
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (typeNode)
|
|
235
|
-
return typeNode.text.trim();
|
|
47
|
+
for (const child of node.children) {
|
|
48
|
+
this.collectErrors(child, errors);
|
|
236
49
|
}
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
getDocumentation(node) {
|
|
240
|
-
return this.extractDocumentation(node.raw);
|
|
241
|
-
}
|
|
242
|
-
getComplexity(node) {
|
|
243
|
-
return this.calculateComplexity(node.raw);
|
|
244
50
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (nameNode && typeNode?.type === 'interface_type') {
|
|
256
|
-
const ifaceInfo = this.buildInterfaceInfo(nameNode, typeNode);
|
|
257
|
-
if (ifaceInfo)
|
|
258
|
-
interfaces.push(ifaceInfo);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
51
|
+
extractName(node) {
|
|
52
|
+
switch (node.type) {
|
|
53
|
+
case 'function_declaration':
|
|
54
|
+
case 'type_spec':
|
|
55
|
+
case 'field_declaration':
|
|
56
|
+
case 'method_spec': {
|
|
57
|
+
const nameNode = node.childForFieldName?.('name');
|
|
58
|
+
if (nameNode)
|
|
59
|
+
return nameNode.text;
|
|
60
|
+
return null;
|
|
261
61
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (!tree)
|
|
271
|
-
return results;
|
|
272
|
-
this.walkRaw(tree.rootNode, (node) => {
|
|
273
|
-
if (node.type === 'import_declaration') {
|
|
274
|
-
for (const spec of node.namedChildren) {
|
|
275
|
-
if (spec.type === 'import_spec') {
|
|
276
|
-
for (const child of spec.namedChildren) {
|
|
277
|
-
if (child.type === 'interpreted_string_literal' ||
|
|
278
|
-
child.type === 'raw_string_literal') {
|
|
279
|
-
results.push({
|
|
280
|
-
moduleSpecifier: child.text.slice(1, -1),
|
|
281
|
-
isStatic: true,
|
|
282
|
-
isDynamic: false,
|
|
283
|
-
isRequire: false,
|
|
284
|
-
line: child.startPosition.row,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
62
|
+
case 'import_spec': {
|
|
63
|
+
const nameNode = node.childForFieldName?.('name');
|
|
64
|
+
if (nameNode)
|
|
65
|
+
return nameNode.text;
|
|
66
|
+
for (const child of node.namedChildren) {
|
|
67
|
+
if (child.type === 'interpreted_string_literal' ||
|
|
68
|
+
child.type === 'raw_string_literal') {
|
|
69
|
+
return child.text.slice(1, -1);
|
|
288
70
|
}
|
|
289
71
|
}
|
|
72
|
+
return null;
|
|
290
73
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
name: e.name,
|
|
298
|
-
line: e.location.start.line,
|
|
299
|
-
}));
|
|
74
|
+
case 'package_identifier':
|
|
75
|
+
case 'identifier':
|
|
76
|
+
return node.text;
|
|
77
|
+
default:
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
300
80
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
81
|
+
cleanCommentText(comment) {
|
|
82
|
+
if (comment.startsWith('/*')) {
|
|
83
|
+
let inner = comment.slice(2, -2);
|
|
84
|
+
inner = inner
|
|
85
|
+
.split('\n')
|
|
86
|
+
.map((line) => line.replace(/^\s*\*\s?/, ''))
|
|
87
|
+
.join('\n');
|
|
88
|
+
return inner.trim();
|
|
310
89
|
}
|
|
90
|
+
if (comment.startsWith('//')) {
|
|
91
|
+
return comment.replace(/^\/\/\s*/, '').trim();
|
|
92
|
+
}
|
|
93
|
+
return comment.trim();
|
|
311
94
|
}
|
|
312
|
-
|
|
313
|
-
visitor(node);
|
|
95
|
+
getOperator(node) {
|
|
314
96
|
for (const child of node.children) {
|
|
315
|
-
|
|
97
|
+
if (!child.isNamed &&
|
|
98
|
+
['&&', '||', '+', '-', '*', '/', '%', '==', '!='].includes(child.type)) {
|
|
99
|
+
return child.type;
|
|
100
|
+
}
|
|
316
101
|
}
|
|
102
|
+
return null;
|
|
317
103
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
104
|
+
// -- Go-specific helpers ---------------------------------------------------
|
|
105
|
+
isExportedGo(name) {
|
|
106
|
+
return name.length > 0 && name[0] === name[0].toUpperCase();
|
|
107
|
+
}
|
|
108
|
+
getReceiverType(node) {
|
|
109
|
+
const receiver = node.childForFieldName?.('receiver');
|
|
110
|
+
if (!receiver)
|
|
111
|
+
return undefined;
|
|
112
|
+
for (const child of receiver.namedChildren) {
|
|
113
|
+
if (child.type === 'parameter_declaration') {
|
|
114
|
+
const typeNode = child.childForFieldName?.('type');
|
|
115
|
+
if (typeNode)
|
|
116
|
+
return typeNode.text;
|
|
117
|
+
}
|
|
325
118
|
}
|
|
326
|
-
|
|
327
|
-
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
getReturnType(node) {
|
|
122
|
+
if (node.type !== 'function_declaration')
|
|
123
|
+
return null;
|
|
124
|
+
const result = node.childForFieldName?.('result');
|
|
125
|
+
if (result)
|
|
126
|
+
return result.text.trim();
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
extractParameters(node, _sourceCode) {
|
|
130
|
+
const params = [];
|
|
131
|
+
const paramList = node.childForFieldName?.('parameters');
|
|
132
|
+
if (!paramList)
|
|
133
|
+
return params;
|
|
134
|
+
for (const child of paramList.namedChildren) {
|
|
135
|
+
if (child.type === 'parameter_declaration') {
|
|
136
|
+
const nameNode = child.childForFieldName?.('name');
|
|
137
|
+
const typeNode = child.childForFieldName?.('type');
|
|
138
|
+
const name = nameNode?.text ?? typeNode?.text ?? child.text;
|
|
139
|
+
params.push({
|
|
140
|
+
name: name || '<unknown>',
|
|
141
|
+
type: typeNode?.text,
|
|
142
|
+
optional: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
328
145
|
}
|
|
146
|
+
return params;
|
|
147
|
+
}
|
|
148
|
+
findNamedChildren(node, type) {
|
|
149
|
+
return node.namedChildren.filter((c) => c.type === type);
|
|
329
150
|
}
|
|
151
|
+
}
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Pattern matching + documentation/complexity + build helpers slice
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
class GoAnalysis extends GoTraversalHelpers {
|
|
330
156
|
matchesPattern(node, pattern) {
|
|
331
157
|
const syntaxNode = node.raw;
|
|
332
158
|
if (pattern.type !== undefined) {
|
|
@@ -365,44 +191,14 @@ export class TreeSitterGoAdapter {
|
|
|
365
191
|
}
|
|
366
192
|
return true;
|
|
367
193
|
}
|
|
368
|
-
extractName(node) {
|
|
369
|
-
switch (node.type) {
|
|
370
|
-
case 'function_declaration':
|
|
371
|
-
case 'type_spec':
|
|
372
|
-
case 'field_declaration':
|
|
373
|
-
case 'method_spec': {
|
|
374
|
-
const nameNode = node.childForFieldName?.('name');
|
|
375
|
-
if (nameNode)
|
|
376
|
-
return nameNode.text;
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
case 'import_spec': {
|
|
380
|
-
const nameNode = node.childForFieldName?.('name');
|
|
381
|
-
if (nameNode)
|
|
382
|
-
return nameNode.text;
|
|
383
|
-
for (const child of node.namedChildren) {
|
|
384
|
-
if (child.type === 'interpreted_string_literal' ||
|
|
385
|
-
child.type === 'raw_string_literal') {
|
|
386
|
-
return child.text.slice(1, -1);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
case 'package_identifier':
|
|
392
|
-
case 'identifier':
|
|
393
|
-
return node.text;
|
|
394
|
-
default:
|
|
395
|
-
return null;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
194
|
extractDocumentation(node) {
|
|
399
195
|
const parent = node.parent;
|
|
400
196
|
if (!parent)
|
|
401
197
|
return null;
|
|
402
|
-
const siblings = parent.
|
|
198
|
+
const siblings = parent.children;
|
|
403
199
|
let myIndex = -1;
|
|
404
200
|
for (let i = 0; i < siblings.length; i++) {
|
|
405
|
-
if (siblings[i]
|
|
201
|
+
if (siblings[i].equals(node)) {
|
|
406
202
|
myIndex = i;
|
|
407
203
|
break;
|
|
408
204
|
}
|
|
@@ -415,7 +211,7 @@ export class TreeSitterGoAdapter {
|
|
|
415
211
|
if (sibling.type === 'comment') {
|
|
416
212
|
comments.unshift(sibling.text);
|
|
417
213
|
}
|
|
418
|
-
else {
|
|
214
|
+
else if (sibling.isNamed) {
|
|
419
215
|
break;
|
|
420
216
|
}
|
|
421
217
|
}
|
|
@@ -423,20 +219,6 @@ export class TreeSitterGoAdapter {
|
|
|
423
219
|
return null;
|
|
424
220
|
return this.cleanCommentText(comments.join('\n').trim());
|
|
425
221
|
}
|
|
426
|
-
cleanCommentText(comment) {
|
|
427
|
-
if (comment.startsWith('/*')) {
|
|
428
|
-
let inner = comment.slice(2, -2);
|
|
429
|
-
inner = inner
|
|
430
|
-
.split('\n')
|
|
431
|
-
.map((line) => line.replace(/^\s*\*\s?/, ''))
|
|
432
|
-
.join('\n');
|
|
433
|
-
return inner.trim();
|
|
434
|
-
}
|
|
435
|
-
if (comment.startsWith('//')) {
|
|
436
|
-
return comment.replace(/^\/\/\s*/, '').trim();
|
|
437
|
-
}
|
|
438
|
-
return comment.trim();
|
|
439
|
-
}
|
|
440
222
|
calculateComplexity(node) {
|
|
441
223
|
let complexity = 1;
|
|
442
224
|
this.walkRaw(node, (child) => {
|
|
@@ -465,15 +247,6 @@ export class TreeSitterGoAdapter {
|
|
|
465
247
|
});
|
|
466
248
|
return complexity;
|
|
467
249
|
}
|
|
468
|
-
getOperator(node) {
|
|
469
|
-
for (const child of node.children) {
|
|
470
|
-
if (!child.isNamed &&
|
|
471
|
-
['&&', '||', '+', '-', '*', '/', '%', '==', '!='].includes(child.type)) {
|
|
472
|
-
return child.type;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
250
|
// -- Build helpers ---------------------------------------------------------
|
|
478
251
|
buildFunctionInfo(node, sourceCode) {
|
|
479
252
|
const name = this.extractName(node);
|
|
@@ -495,10 +268,17 @@ export class TreeSitterGoAdapter {
|
|
|
495
268
|
jsDoc: jsDoc ?? undefined,
|
|
496
269
|
};
|
|
497
270
|
}
|
|
498
|
-
buildStructAsClass(_typeSpec, nameNode, typeNode, allFunctions
|
|
271
|
+
buildStructAsClass(_typeSpec, nameNode, typeNode, allFunctions) {
|
|
499
272
|
const name = nameNode.text;
|
|
500
273
|
const isExported = this.isExportedGo(name);
|
|
501
|
-
|
|
274
|
+
// A single `type Foo struct { ... }` declaration carries its doc comment
|
|
275
|
+
// as a sibling of the enclosing `type_declaration`; a grouped
|
|
276
|
+
// `type ( ... )` declaration carries it as a sibling of the `type_spec`.
|
|
277
|
+
// Try the `type_spec` level first, then fall back to the `type_declaration`.
|
|
278
|
+
let jsDoc = this.extractDocumentation(_typeSpec);
|
|
279
|
+
if (!jsDoc && _typeSpec.parent?.type === 'type_declaration') {
|
|
280
|
+
jsDoc = this.extractDocumentation(_typeSpec.parent);
|
|
281
|
+
}
|
|
502
282
|
// Collect fields as properties
|
|
503
283
|
const properties = [];
|
|
504
284
|
for (const field of this.findNamedChildren(typeNode, 'field_declaration')) {
|
|
@@ -569,72 +349,332 @@ export class TreeSitterGoAdapter {
|
|
|
569
349
|
});
|
|
570
350
|
}
|
|
571
351
|
}
|
|
572
|
-
else if (member.type === 'type_elem') {
|
|
573
|
-
const embeddedName = member.childForFieldName?.('name')?.text ?? member.text;
|
|
574
|
-
if (embeddedName) {
|
|
575
|
-
members.push({
|
|
576
|
-
name: embeddedName,
|
|
577
|
-
type: 'property',
|
|
578
|
-
location: toSourceLocation(member),
|
|
579
|
-
});
|
|
352
|
+
else if (member.type === 'type_elem') {
|
|
353
|
+
const embeddedName = member.childForFieldName?.('name')?.text ?? member.text;
|
|
354
|
+
if (embeddedName) {
|
|
355
|
+
members.push({
|
|
356
|
+
name: embeddedName,
|
|
357
|
+
type: 'property',
|
|
358
|
+
location: toSourceLocation(member),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
name,
|
|
365
|
+
location: toSourceLocation(nameNode),
|
|
366
|
+
members,
|
|
367
|
+
isExported,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
// Parsing + AST navigation slice
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
374
|
+
class GoParserCore extends GoAnalysis {
|
|
375
|
+
name = 'go';
|
|
376
|
+
fileExtensions = ['.go'];
|
|
377
|
+
// -- File detection -------------------------------------------------------
|
|
378
|
+
supportsFile(filePath) {
|
|
379
|
+
return filePath.endsWith('.go');
|
|
380
|
+
}
|
|
381
|
+
// -- Parsing --------------------------------------------------------------
|
|
382
|
+
async parse(filePath, content) {
|
|
383
|
+
const tree = await parseWithRecovery('go', false, content);
|
|
384
|
+
if (!tree)
|
|
385
|
+
throw new Error(`Failed to parse Go file: ${filePath}`);
|
|
386
|
+
const errors = [];
|
|
387
|
+
this.collectErrors(tree.rootNode, errors);
|
|
388
|
+
const root = toASTNode(tree.rootNode, undefined, 'go');
|
|
389
|
+
const ast = {
|
|
390
|
+
root,
|
|
391
|
+
language: 'go',
|
|
392
|
+
filePath,
|
|
393
|
+
errors,
|
|
394
|
+
dispose: () => tree.delete(),
|
|
395
|
+
};
|
|
396
|
+
sourceCodeMap.set(ast, content);
|
|
397
|
+
return ast;
|
|
398
|
+
}
|
|
399
|
+
// -- AST Navigation -------------------------------------------------------
|
|
400
|
+
findNodes(ast, pattern) {
|
|
401
|
+
const results = [];
|
|
402
|
+
this.walk(ast.root, (node) => {
|
|
403
|
+
if (this.matchesPattern(node, pattern)) {
|
|
404
|
+
results.push(node);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
return results;
|
|
408
|
+
}
|
|
409
|
+
getParent(node) {
|
|
410
|
+
return node.parent ?? null;
|
|
411
|
+
}
|
|
412
|
+
getChildren(node) {
|
|
413
|
+
return node.children ?? [];
|
|
414
|
+
}
|
|
415
|
+
getSiblings(node) {
|
|
416
|
+
if (!node.parent?.children)
|
|
417
|
+
return [];
|
|
418
|
+
return node.parent.children.filter((c) => c !== node);
|
|
419
|
+
}
|
|
420
|
+
// -- Node Information -----------------------------------------------------
|
|
421
|
+
getNodeType(node) {
|
|
422
|
+
return node.type;
|
|
423
|
+
}
|
|
424
|
+
getNodeText(node, sourceCode) {
|
|
425
|
+
return sourceCode.slice(node.range[0], node.range[1]);
|
|
426
|
+
}
|
|
427
|
+
getNodeName(node) {
|
|
428
|
+
return this.extractName(node.raw);
|
|
429
|
+
}
|
|
430
|
+
getNodeLocation(node) {
|
|
431
|
+
return node.location;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Language-specific extraction slice
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
class GoExtraction extends GoParserCore {
|
|
438
|
+
extractFunctions(ast) {
|
|
439
|
+
const sourceCode = sourceCodeMap.get(ast) ?? '';
|
|
440
|
+
const functions = [];
|
|
441
|
+
this.walk(ast.root, (astNode) => {
|
|
442
|
+
const node = astNode.raw;
|
|
443
|
+
if (node.type === 'function_declaration') {
|
|
444
|
+
const fn = this.buildFunctionInfo(node, sourceCode);
|
|
445
|
+
if (fn)
|
|
446
|
+
functions.push(fn);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
return functions;
|
|
450
|
+
}
|
|
451
|
+
extractClasses(ast) {
|
|
452
|
+
const classes = [];
|
|
453
|
+
// Collect all function_declarations so we can match methods to structs
|
|
454
|
+
const allFunctions = this.extractFunctions(ast);
|
|
455
|
+
this.walk(ast.root, (astNode) => {
|
|
456
|
+
const node = astNode.raw;
|
|
457
|
+
if (node.type === 'type_declaration') {
|
|
458
|
+
const specs = this.findNamedChildren(node, 'type_spec');
|
|
459
|
+
for (const spec of specs) {
|
|
460
|
+
const nameNode = spec.childForFieldName?.('name');
|
|
461
|
+
if (!nameNode)
|
|
462
|
+
continue;
|
|
463
|
+
const typeNode = spec.childForFieldName?.('type');
|
|
464
|
+
if (!typeNode || typeNode.type !== 'struct_type')
|
|
465
|
+
continue;
|
|
466
|
+
const structInfo = this.buildStructAsClass(spec, nameNode, typeNode, allFunctions);
|
|
467
|
+
if (structInfo)
|
|
468
|
+
classes.push(structInfo);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
return classes;
|
|
473
|
+
}
|
|
474
|
+
extractImports(ast) {
|
|
475
|
+
const imports = [];
|
|
476
|
+
this.walk(ast.root, (astNode) => {
|
|
477
|
+
const node = astNode.raw;
|
|
478
|
+
if (node.type === 'import_declaration') {
|
|
479
|
+
for (const spec of node.namedChildren) {
|
|
480
|
+
if (spec.type === 'import_spec') {
|
|
481
|
+
const imp = this.buildImportInfo(spec);
|
|
482
|
+
if (imp)
|
|
483
|
+
imports.push(imp);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
return imports;
|
|
489
|
+
}
|
|
490
|
+
extractExports(ast) {
|
|
491
|
+
const exports = [];
|
|
492
|
+
this.walk(ast.root, (astNode) => {
|
|
493
|
+
const node = astNode.raw;
|
|
494
|
+
if (node.type === 'function_declaration') {
|
|
495
|
+
const name = this.extractName(node);
|
|
496
|
+
if (name && this.isExportedGo(name)) {
|
|
497
|
+
exports.push({
|
|
498
|
+
name,
|
|
499
|
+
location: toSourceLocation(node),
|
|
500
|
+
isDefault: false,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (node.type === 'type_declaration') {
|
|
505
|
+
for (const spec of this.findNamedChildren(node, 'type_spec')) {
|
|
506
|
+
const nameNode = spec.childForFieldName?.('name');
|
|
507
|
+
if (nameNode && this.isExportedGo(nameNode.text)) {
|
|
508
|
+
exports.push({
|
|
509
|
+
name: nameNode.text,
|
|
510
|
+
location: toSourceLocation(nameNode),
|
|
511
|
+
isDefault: false,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
580
514
|
}
|
|
581
515
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if (!paramList)
|
|
594
|
-
return params;
|
|
595
|
-
for (const child of paramList.namedChildren) {
|
|
596
|
-
if (child.type === 'parameter_declaration') {
|
|
597
|
-
const nameNode = child.childForFieldName?.('name');
|
|
598
|
-
const typeNode = child.childForFieldName?.('type');
|
|
599
|
-
const name = nameNode?.text ?? typeNode?.text ?? child.text;
|
|
600
|
-
params.push({
|
|
601
|
-
name: name || '<unknown>',
|
|
602
|
-
type: typeNode?.text,
|
|
603
|
-
optional: false,
|
|
604
|
-
});
|
|
516
|
+
else if (node.type === 'var_declaration') {
|
|
517
|
+
for (const spec of this.findNamedChildren(node, 'var_spec')) {
|
|
518
|
+
const nameNode = spec.childForFieldName?.('name');
|
|
519
|
+
if (nameNode && this.isExportedGo(nameNode.text)) {
|
|
520
|
+
exports.push({
|
|
521
|
+
name: nameNode.text,
|
|
522
|
+
location: toSourceLocation(nameNode),
|
|
523
|
+
isDefault: false,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
605
527
|
}
|
|
528
|
+
});
|
|
529
|
+
return exports;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// ---------------------------------------------------------------------------
|
|
533
|
+
// Predicates + advanced-features slice
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
class GoPredicates extends GoExtraction {
|
|
536
|
+
isClass(node) {
|
|
537
|
+
const n = node.raw;
|
|
538
|
+
if (n.type === 'type_spec') {
|
|
539
|
+
const typeNode = n.childForFieldName?.('type');
|
|
540
|
+
return typeNode?.type === 'struct_type';
|
|
606
541
|
}
|
|
607
|
-
return
|
|
542
|
+
return n.type === 'struct_type';
|
|
608
543
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
return name.length > 0 && name[0] === name[0].toUpperCase();
|
|
544
|
+
isFunction(node) {
|
|
545
|
+
return node.raw.type === 'function_declaration';
|
|
612
546
|
}
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
if (
|
|
616
|
-
return
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
547
|
+
isMethod(node) {
|
|
548
|
+
const n = node.raw;
|
|
549
|
+
if (n.type !== 'function_declaration')
|
|
550
|
+
return false;
|
|
551
|
+
return n.childForFieldName?.('receiver') != null;
|
|
552
|
+
}
|
|
553
|
+
isInterface(node) {
|
|
554
|
+
const n = node.raw;
|
|
555
|
+
if (n.type === 'type_spec') {
|
|
556
|
+
const typeNode = n.childForFieldName?.('type');
|
|
557
|
+
return typeNode?.type === 'interface_type';
|
|
623
558
|
}
|
|
624
|
-
return
|
|
559
|
+
return n.type === 'interface_type';
|
|
625
560
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
561
|
+
isImport(node) {
|
|
562
|
+
return node.raw.type === 'import_declaration';
|
|
563
|
+
}
|
|
564
|
+
isExport(node) {
|
|
565
|
+
return false; // Go has no export keyword
|
|
566
|
+
}
|
|
567
|
+
isLoop(node) {
|
|
568
|
+
return node.raw.type === 'for_statement';
|
|
569
|
+
}
|
|
570
|
+
isConditional(node) {
|
|
571
|
+
const t = node.raw.type;
|
|
572
|
+
return (t === 'if_statement' ||
|
|
573
|
+
t === 'switch_statement' ||
|
|
574
|
+
t === 'expression_switch_statement' ||
|
|
575
|
+
t === 'type_switch_statement');
|
|
576
|
+
}
|
|
577
|
+
isVariableDeclaration(node) {
|
|
578
|
+
const t = node.raw.type;
|
|
579
|
+
return (t === 'var_declaration' ||
|
|
580
|
+
t === 'short_var_declaration' ||
|
|
581
|
+
t === 'var_spec' ||
|
|
582
|
+
t === 'const_declaration');
|
|
583
|
+
}
|
|
584
|
+
// -- Advanced Features ----------------------------------------------------
|
|
585
|
+
getTypeInfo(node) {
|
|
586
|
+
const n = node.raw;
|
|
587
|
+
if (n.type === 'function_declaration') {
|
|
588
|
+
const result = n.childForFieldName?.('result');
|
|
589
|
+
if (result)
|
|
590
|
+
return result.text.trim();
|
|
591
|
+
}
|
|
592
|
+
if (n.type === 'var_spec' || n.type === 'const_spec') {
|
|
593
|
+
const typeNode = n.childForFieldName?.('type');
|
|
594
|
+
if (typeNode)
|
|
595
|
+
return typeNode.text.trim();
|
|
596
|
+
}
|
|
597
|
+
if (n.type === 'field_declaration') {
|
|
598
|
+
const typeNode = n.childForFieldName?.('type');
|
|
599
|
+
if (typeNode)
|
|
600
|
+
return typeNode.text.trim();
|
|
601
|
+
}
|
|
632
602
|
return null;
|
|
633
603
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
604
|
+
getDocumentation(node) {
|
|
605
|
+
return this.extractDocumentation(node.raw);
|
|
606
|
+
}
|
|
607
|
+
getComplexity(node) {
|
|
608
|
+
return this.calculateComplexity(node.raw);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// ---------------------------------------------------------------------------
|
|
612
|
+
// Optional capabilities slice
|
|
613
|
+
// ---------------------------------------------------------------------------
|
|
614
|
+
class GoOptionalCapabilities extends GoPredicates {
|
|
615
|
+
extractInterfaces(ast) {
|
|
616
|
+
const interfaces = [];
|
|
617
|
+
this.walk(ast.root, (astNode) => {
|
|
618
|
+
const node = astNode.raw;
|
|
619
|
+
if (node.type === 'type_declaration') {
|
|
620
|
+
const specs = this.findNamedChildren(node, 'type_spec');
|
|
621
|
+
for (const spec of specs) {
|
|
622
|
+
const nameNode = spec.childForFieldName?.('name');
|
|
623
|
+
const typeNode = spec.childForFieldName?.('type');
|
|
624
|
+
if (nameNode && typeNode?.type === 'interface_type') {
|
|
625
|
+
const ifaceInfo = this.buildInterfaceInfo(nameNode, typeNode);
|
|
626
|
+
if (ifaceInfo)
|
|
627
|
+
interfaces.push(ifaceInfo);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
return interfaces;
|
|
633
|
+
}
|
|
634
|
+
// -- Optional: Raw imports ------------------------------------------------
|
|
635
|
+
extractRawImports(_filePath, content) {
|
|
636
|
+
const results = [];
|
|
637
|
+
const parser = getParser('go');
|
|
638
|
+
const tree = parser.parse(content);
|
|
639
|
+
if (!tree)
|
|
640
|
+
return results;
|
|
641
|
+
this.walkRaw(tree.rootNode, (node) => {
|
|
642
|
+
if (node.type === 'import_declaration') {
|
|
643
|
+
for (const spec of node.namedChildren) {
|
|
644
|
+
if (spec.type === 'import_spec') {
|
|
645
|
+
for (const child of spec.namedChildren) {
|
|
646
|
+
if (child.type === 'interpreted_string_literal' ||
|
|
647
|
+
child.type === 'raw_string_literal') {
|
|
648
|
+
results.push({
|
|
649
|
+
moduleSpecifier: child.text.slice(1, -1),
|
|
650
|
+
isStatic: true,
|
|
651
|
+
isDynamic: false,
|
|
652
|
+
isRequire: false,
|
|
653
|
+
line: child.startPosition.row,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
return results;
|
|
662
|
+
}
|
|
663
|
+
// -- Optional: Exported symbols -------------------------------------------
|
|
664
|
+
extractExportedSymbols(ast) {
|
|
665
|
+
return this.extractExports(ast).map((e) => ({
|
|
666
|
+
name: e.name,
|
|
667
|
+
line: e.location.start.line,
|
|
668
|
+
}));
|
|
637
669
|
}
|
|
670
|
+
}
|
|
671
|
+
// ---------------------------------------------------------------------------
|
|
672
|
+
// Adapter (composes the slices above)
|
|
673
|
+
// ---------------------------------------------------------------------------
|
|
674
|
+
/**
|
|
675
|
+
* Tree sitter go adapter.
|
|
676
|
+
*/
|
|
677
|
+
export class TreeSitterGoAdapter extends GoOptionalCapabilities {
|
|
638
678
|
// -- String Construction Capabilities (v3.4.7) ----------------------------
|
|
639
679
|
/**
|
|
640
680
|
* Returns true when the node is a dynamically-constructed string in Go:
|
|
@@ -642,6 +682,8 @@ export class TreeSitterGoAdapter {
|
|
|
642
682
|
* - binary_expression with + operator (string concatenation)
|
|
643
683
|
* Plain string literals (interpreted_string_literal, raw_string_literal)
|
|
644
684
|
* are never dynamic.
|
|
685
|
+
* @param node
|
|
686
|
+
* @returns
|
|
645
687
|
*/
|
|
646
688
|
isDynamicStringConstruction(node) {
|
|
647
689
|
const raw = node.raw;
|
|
@@ -653,22 +695,9 @@ export class TreeSitterGoAdapter {
|
|
|
653
695
|
// fmt.Sprintf("format", args...) — dynamic
|
|
654
696
|
// strings.Join(parts, "sep") — dynamic
|
|
655
697
|
// fmt.Sprint(args...) — dynamic
|
|
656
|
-
|
|
657
|
-
if (funcText === 'fmt.Sprintf' || funcText === 'fmt.Sprintf' ||
|
|
658
|
-
funcText === 'fmt.Sprint' || funcText === 'fmt.Sprintln' ||
|
|
659
|
-
funcText === 'fmt.Appendf' || funcText === 'fmt.Appendln' ||
|
|
660
|
-
funcText === 'fmt.Append' || funcText === 'fmt.Errorf' ||
|
|
661
|
-
funcText === 'fmt.Fprintf' || funcText === 'fmt.Fprintln' ||
|
|
662
|
-
funcText === 'fmt.Fprint' || funcText === 'strings.Join' ||
|
|
663
|
-
funcText === 'fmt.Scanf') {
|
|
698
|
+
if (isDynamicFormatFunction(func.text)) {
|
|
664
699
|
return true;
|
|
665
700
|
}
|
|
666
|
-
// Also check for selector_expression patterns: pkg.Symbol()
|
|
667
|
-
const selectorMatch = /^(.*?\.)?(Sprintf|Sprintf|Sprint|Sprintln|Join|Appendf|Errorf|Fprintf)$/.test(funcText);
|
|
668
|
-
if (selectorMatch) {
|
|
669
|
-
// Verify it's actually fmt.* or strings.*
|
|
670
|
-
return funcText.startsWith('fmt.') || funcText.startsWith('strings.');
|
|
671
|
-
}
|
|
672
701
|
// Recurse into arguments: query(fmt.Sprintf(...)) where the outer
|
|
673
702
|
// call isn't itself a format function but passes a dynamic argument.
|
|
674
703
|
for (const child of node.children ?? []) {
|
|
@@ -699,74 +728,80 @@ export class TreeSitterGoAdapter {
|
|
|
699
728
|
* - For fmt.Sprintf: args after the format string are dynamic
|
|
700
729
|
* - For binary +: non-literal operands are dynamic
|
|
701
730
|
* - For strings.Join: the parts slice is dynamic
|
|
731
|
+
* @param node
|
|
732
|
+
* @param sourceCode
|
|
733
|
+
* @returns
|
|
702
734
|
*/
|
|
703
735
|
getDynamicParts(node, sourceCode) {
|
|
704
736
|
const raw = node.raw;
|
|
705
737
|
const type = raw.type;
|
|
706
|
-
const parts = [];
|
|
707
738
|
// For call_expressions that aren't known fmt/strings functions,
|
|
708
739
|
// walk into the first argument that is itself a dynamic string construction.
|
|
709
740
|
if (type === 'call_expression') {
|
|
710
741
|
const funcText = raw.childForFieldName?.('function')?.text ?? '';
|
|
711
742
|
const isKnownFormatter = /^(fmt\.|strings\.)(Sprintf|Fprintf|Errorf|Appendf|Sprint|Sprintln|Append|Appendln|Join|Scanf|Fprint|Fprintln|Sscanf)$/u.test(funcText);
|
|
712
743
|
if (!isKnownFormatter) {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const argType = arg.raw.type;
|
|
717
|
-
if (argType === '(' || argType === ')' || argType === ',')
|
|
718
|
-
continue;
|
|
719
|
-
if (this.isDynamicStringConstruction(arg)) {
|
|
720
|
-
return this.getDynamicParts(arg, sourceCode);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (type === 'call_expression') {
|
|
728
|
-
const argsNode = raw.childForFieldName?.('arguments');
|
|
729
|
-
if (!argsNode)
|
|
730
|
-
return parts;
|
|
731
|
-
const namedChildren = argsNode.namedChildren;
|
|
732
|
-
// fmt.Sprintf("format", arg1, arg2...): skip format string (first arg)
|
|
733
|
-
// strings.Join(parts, "sep"): first arg is dynamic
|
|
734
|
-
const funcText = raw.childForFieldName?.('function')?.text ?? '';
|
|
735
|
-
const isFormatFunc = /^fmt\.(Sprintf|Fprintf|Errorf|Appendf)$/u.test(funcText);
|
|
736
|
-
const startIdx = isFormatFunc ? 1 : 0;
|
|
737
|
-
for (let i = startIdx; i < namedChildren.length; i++) {
|
|
738
|
-
const arg = namedChildren[i];
|
|
739
|
-
const argType = arg.type;
|
|
740
|
-
// Skip string literals (they're the separator or static parts)
|
|
741
|
-
if (argType === 'interpreted_string_literal' || argType === 'raw_string_literal')
|
|
742
|
-
continue;
|
|
743
|
-
const text = sourceCode.slice(arg.startIndex, arg.endIndex);
|
|
744
|
-
const isId = /^[\p{L}_][\p{L}\p{N}_]*$/u.test(text.trim());
|
|
745
|
-
const idNode = isId ? {
|
|
746
|
-
type: argType,
|
|
747
|
-
range: [arg.startIndex, arg.endIndex],
|
|
748
|
-
location: {
|
|
749
|
-
start: { line: arg.startPosition.row, column: arg.startPosition.column },
|
|
750
|
-
end: { line: arg.endPosition.row, column: arg.endPosition.column },
|
|
751
|
-
},
|
|
752
|
-
raw: arg,
|
|
753
|
-
} : undefined;
|
|
754
|
-
parts.push({ text: text.trim(), isIdentifier: isId, node: idNode });
|
|
744
|
+
const arg = this.findFirstDynamicArgument(node);
|
|
745
|
+
if (arg)
|
|
746
|
+
return this.getDynamicParts(arg, sourceCode);
|
|
755
747
|
}
|
|
756
|
-
return
|
|
748
|
+
return this.extractCallDynamicParts(raw, sourceCode);
|
|
757
749
|
}
|
|
758
750
|
if (type === 'binary_expression') {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
751
|
+
return extractBinaryDynamicParts(node, sourceCode);
|
|
752
|
+
}
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Finds the first argument of a call_expression that is itself a dynamic
|
|
757
|
+
* string construction, or null if none exists.
|
|
758
|
+
*/
|
|
759
|
+
findFirstDynamicArgument(node) {
|
|
760
|
+
for (const child of node.children ?? []) {
|
|
761
|
+
if (child.raw.type !== 'argument_list')
|
|
762
|
+
continue;
|
|
763
|
+
for (const arg of child.children ?? []) {
|
|
764
|
+
const argType = arg.raw.type;
|
|
765
|
+
if (argType === '(' || argType === ')' || argType === ',')
|
|
764
766
|
continue;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
parts.push({ text: text.trim(), isIdentifier: isId, node: isId ? child : undefined });
|
|
767
|
+
if (this.isDynamicStringConstruction(arg))
|
|
768
|
+
return arg;
|
|
768
769
|
}
|
|
770
|
+
}
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Extracts dynamic parts from a call_expression's argument list.
|
|
775
|
+
* fmt.Sprintf("format", arg1...): skips the format string (first arg).
|
|
776
|
+
* strings.Join(parts, "sep"): first arg is dynamic.
|
|
777
|
+
*/
|
|
778
|
+
extractCallDynamicParts(raw, sourceCode) {
|
|
779
|
+
const parts = [];
|
|
780
|
+
const argsNode = raw.childForFieldName?.('arguments');
|
|
781
|
+
if (!argsNode)
|
|
769
782
|
return parts;
|
|
783
|
+
const namedChildren = argsNode.namedChildren;
|
|
784
|
+
const funcText = raw.childForFieldName?.('function')?.text ?? '';
|
|
785
|
+
const isFormatFunc = /^fmt\.(Sprintf|Fprintf|Errorf|Appendf)$/u.test(funcText);
|
|
786
|
+
const startIdx = isFormatFunc ? 1 : 0;
|
|
787
|
+
for (let i = startIdx; i < namedChildren.length; i++) {
|
|
788
|
+
const arg = namedChildren[i];
|
|
789
|
+
const argType = arg.type;
|
|
790
|
+
// Skip string literals (they're the separator or static parts)
|
|
791
|
+
if (argType === 'interpreted_string_literal' || argType === 'raw_string_literal')
|
|
792
|
+
continue;
|
|
793
|
+
const text = sourceCode.slice(arg.startIndex, arg.endIndex);
|
|
794
|
+
const isId = /^[\p{L}_][\p{L}\p{N}_]*$/u.test(text.trim());
|
|
795
|
+
const idNode = isId ? {
|
|
796
|
+
type: argType,
|
|
797
|
+
range: [arg.startIndex, arg.endIndex],
|
|
798
|
+
location: {
|
|
799
|
+
start: { line: arg.startPosition.row, column: arg.startPosition.column },
|
|
800
|
+
end: { line: arg.endPosition.row, column: arg.endPosition.column },
|
|
801
|
+
},
|
|
802
|
+
raw: arg,
|
|
803
|
+
} : undefined;
|
|
804
|
+
parts.push({ text: text.trim(), isIdentifier: isId, node: idNode });
|
|
770
805
|
}
|
|
771
806
|
return parts;
|
|
772
807
|
}
|
|
@@ -774,6 +809,10 @@ export class TreeSitterGoAdapter {
|
|
|
774
809
|
* Resolves a Go local constant declaration for an identifier node.
|
|
775
810
|
* Searches within the enclosing function scope for var/const/:=
|
|
776
811
|
* declarations. Returns null for unresolvable identifiers.
|
|
812
|
+
* @param ast
|
|
813
|
+
* @param identifierNode
|
|
814
|
+
* @param sourceCode
|
|
815
|
+
* @returns
|
|
777
816
|
*/
|
|
778
817
|
resolveLocalConstant(identifierNode, ast, sourceCode) {
|
|
779
818
|
const idName = sourceCode.slice(identifierNode.range[0], identifierNode.range[1]).trim();
|
|
@@ -880,4 +919,44 @@ export class TreeSitterGoAdapter {
|
|
|
880
919
|
return found;
|
|
881
920
|
}
|
|
882
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Whether a call_expression's function name denotes a dynamic string formatter
|
|
924
|
+
* (fmt.Sprint*, fmt.Errorf, strings.Join, etc.).
|
|
925
|
+
*/
|
|
926
|
+
function isDynamicFormatFunction(funcText) {
|
|
927
|
+
if (funcText === 'fmt.Sprintf' || funcText === 'fmt.Sprint' ||
|
|
928
|
+
funcText === 'fmt.Sprintln' || funcText === 'fmt.Appendf' ||
|
|
929
|
+
funcText === 'fmt.Appendln' || funcText === 'fmt.Append' ||
|
|
930
|
+
funcText === 'fmt.Errorf' || funcText === 'fmt.Fprintf' ||
|
|
931
|
+
funcText === 'fmt.Fprintln' || funcText === 'fmt.Fprint' ||
|
|
932
|
+
funcText === 'strings.Join' || funcText === 'fmt.Scanf') {
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
// Also check for selector_expression patterns: pkg.Symbol()
|
|
936
|
+
const selectorMatch = /^(.*?\.)?(Sprintf|Sprint|Sprintln|Join|Appendf|Errorf|Fprintf)$/.test(funcText);
|
|
937
|
+
if (selectorMatch) {
|
|
938
|
+
// Verify it's actually fmt.* or strings.*
|
|
939
|
+
return funcText.startsWith('fmt.') || funcText.startsWith('strings.');
|
|
940
|
+
}
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Extracts dynamic parts from a Go binary string-concatenation expression
|
|
945
|
+
* (a + b + c). Non-literal operands are dynamic; literals and the `+` operator
|
|
946
|
+
* are skipped.
|
|
947
|
+
*/
|
|
948
|
+
function extractBinaryDynamicParts(node, sourceCode) {
|
|
949
|
+
const parts = [];
|
|
950
|
+
for (const child of node.children ?? []) {
|
|
951
|
+
const childType = child.raw.type;
|
|
952
|
+
if (childType === 'interpreted_string_literal' ||
|
|
953
|
+
childType === 'raw_string_literal' ||
|
|
954
|
+
childType === '+')
|
|
955
|
+
continue;
|
|
956
|
+
const text = sourceCode.slice(child.range[0], child.range[1]);
|
|
957
|
+
const isId = /^[\p{L}_][\p{L}\p{N}_]*$/u.test(text.trim());
|
|
958
|
+
parts.push({ text: text.trim(), isIdentifier: isId, node: isId ? child : undefined });
|
|
959
|
+
}
|
|
960
|
+
return parts;
|
|
961
|
+
}
|
|
883
962
|
//# sourceMappingURL=GoAdapter.js.map
|