kodingo-cli 1.0.38 → 1.1.1

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.
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ /**
3
+ * tree-sitter-parser.ts
4
+ *
5
+ * Real AST-based symbol extraction for non-TS/JS languages, using web-tree-sitter
6
+ * with prebuilt WASM grammars (tree-sitter-wasms) — no native compilation required,
7
+ * works identically on any developer machine.
8
+ *
9
+ * This replaces regex/brace-counting heuristics (parseStructural, parsePython in
10
+ * ast-parser.ts) with genuine parse trees. Call and declaration extraction is still
11
+ * heuristic in the sense that it isn't type-checked (that's Tier 1, TS/JS only via
12
+ * the compiler), but it is now grounded in a real AST rather than pattern matching
13
+ * over raw text — eliminating false positives from strings/comments that merely
14
+ * look like declarations or calls.
15
+ */
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.parseWithTreeSitter = parseWithTreeSitter;
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const path_1 = __importDefault(require("path"));
23
+ const web_tree_sitter_1 = __importDefault(require("web-tree-sitter"));
24
+ const ast_parser_1 = require("./ast-parser");
25
+ const LANGUAGE_CONFIGS = {
26
+ python: {
27
+ wasmFile: "tree-sitter-python.wasm",
28
+ functionTypes: ["function_definition"],
29
+ classTypes: ["class_definition"],
30
+ callTypes: ["call"],
31
+ calleeFieldCandidates: ["function"],
32
+ nameFieldCandidates: ["name"],
33
+ importTypes: ["import_statement", "import_from_statement"],
34
+ },
35
+ go: {
36
+ wasmFile: "tree-sitter-go.wasm",
37
+ functionTypes: ["function_declaration", "method_declaration"],
38
+ classTypes: [],
39
+ callTypes: ["call_expression"],
40
+ calleeFieldCandidates: ["function"],
41
+ nameFieldCandidates: ["name"],
42
+ importTypes: ["import_spec"],
43
+ },
44
+ rust: {
45
+ wasmFile: "tree-sitter-rust.wasm",
46
+ functionTypes: ["function_item"],
47
+ classTypes: ["impl_item"],
48
+ callTypes: ["call_expression"],
49
+ calleeFieldCandidates: ["function"],
50
+ nameFieldCandidates: ["name", "type"],
51
+ importTypes: ["use_declaration"],
52
+ },
53
+ java: {
54
+ wasmFile: "tree-sitter-java.wasm",
55
+ functionTypes: ["method_declaration", "constructor_declaration"],
56
+ classTypes: ["class_declaration", "interface_declaration"],
57
+ callTypes: ["method_invocation"],
58
+ calleeFieldCandidates: ["name"],
59
+ nameFieldCandidates: ["name"],
60
+ importTypes: ["import_declaration"],
61
+ },
62
+ kotlin: {
63
+ wasmFile: "tree-sitter-kotlin.wasm",
64
+ functionTypes: ["function_declaration"],
65
+ classTypes: ["class_declaration"],
66
+ callTypes: ["call_expression"],
67
+ calleeFieldCandidates: ["function"],
68
+ nameFieldCandidates: ["name"],
69
+ importTypes: ["import_header"],
70
+ },
71
+ ruby: {
72
+ wasmFile: "tree-sitter-ruby.wasm",
73
+ functionTypes: ["method", "singleton_method"],
74
+ classTypes: ["class", "module"],
75
+ callTypes: ["call", "method_call"],
76
+ calleeFieldCandidates: ["method"],
77
+ nameFieldCandidates: ["name"],
78
+ importTypes: [],
79
+ },
80
+ php: {
81
+ wasmFile: "tree-sitter-php.wasm",
82
+ functionTypes: ["function_definition", "method_declaration"],
83
+ classTypes: ["class_declaration"],
84
+ callTypes: ["function_call_expression", "member_call_expression", "scoped_call_expression"],
85
+ calleeFieldCandidates: ["function", "name"],
86
+ nameFieldCandidates: ["name"],
87
+ importTypes: ["namespace_use_declaration"],
88
+ },
89
+ c: {
90
+ wasmFile: "tree-sitter-c.wasm",
91
+ functionTypes: ["function_definition"],
92
+ classTypes: [],
93
+ callTypes: ["call_expression"],
94
+ calleeFieldCandidates: ["function"],
95
+ nameFieldCandidates: [], // handled via declarator fallback
96
+ importTypes: ["preproc_include"],
97
+ },
98
+ cpp: {
99
+ wasmFile: "tree-sitter-cpp.wasm",
100
+ functionTypes: ["function_definition"],
101
+ classTypes: ["class_specifier", "struct_specifier"],
102
+ callTypes: ["call_expression"],
103
+ calleeFieldCandidates: ["function"],
104
+ nameFieldCandidates: [], // handled via declarator fallback
105
+ importTypes: ["preproc_include"],
106
+ },
107
+ };
108
+ const EXT_TO_LANGUAGE = {
109
+ ".py": "python",
110
+ ".go": "go",
111
+ ".rs": "rust",
112
+ ".java": "java",
113
+ ".kt": "kotlin",
114
+ ".rb": "ruby",
115
+ ".php": "php",
116
+ ".c": "c",
117
+ ".cpp": "cpp",
118
+ };
119
+ let initialised = false;
120
+ const languageCache = new Map();
121
+ async function ensureInit() {
122
+ if (!initialised) {
123
+ await web_tree_sitter_1.default.init();
124
+ initialised = true;
125
+ }
126
+ }
127
+ async function loadLanguage(key, config) {
128
+ const cached = languageCache.get(key);
129
+ if (cached)
130
+ return cached;
131
+ const wasmDir = path_1.default.join(path_1.default.dirname(require.resolve("tree-sitter-wasms/package.json")), "out");
132
+ const wasmPath = path_1.default.join(wasmDir, config.wasmFile);
133
+ const wasmBuffer = fs_1.default.readFileSync(wasmPath);
134
+ const lang = await web_tree_sitter_1.default.Language.load(wasmBuffer);
135
+ languageCache.set(key, lang);
136
+ return lang;
137
+ }
138
+ // ── Node helpers ──────────────────────────────────────────────────────────────
139
+ function getFieldOrFallback(node, candidates) {
140
+ for (const field of candidates) {
141
+ const child = node.childForFieldName(field);
142
+ if (child)
143
+ return child;
144
+ }
145
+ return null;
146
+ }
147
+ /** Descends through nested declarators (C/C++ function name pattern) to find the innermost identifier. */
148
+ function extractDeclaratorName(node) {
149
+ let current = node.childForFieldName("declarator");
150
+ let guard = 0;
151
+ while (current && guard < 10) {
152
+ guard++;
153
+ if (current.type === "identifier" || current.type === "field_identifier") {
154
+ return current.text;
155
+ }
156
+ const nested = current.childForFieldName("declarator");
157
+ if (!nested)
158
+ break;
159
+ current = nested;
160
+ }
161
+ return null;
162
+ }
163
+ function getNodeName(node, config) {
164
+ const viaField = getFieldOrFallback(node, config.nameFieldCandidates);
165
+ if (viaField)
166
+ return viaField.text;
167
+ const viaDeclarator = extractDeclaratorName(node);
168
+ if (viaDeclarator)
169
+ return viaDeclarator;
170
+ // Last resort: first identifier-like named child
171
+ for (let i = 0; i < node.namedChildCount; i++) {
172
+ const child = node.namedChild(i);
173
+ if (child && (child.type === "identifier" || child.type === "type_identifier" || child.type === "simple_identifier")) {
174
+ return child.text;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+ /** Extracts a trailing identifier from a callee expression (e.g. "$this->service->method" -> "method"). */
180
+ function extractTrailingIdentifier(text) {
181
+ const match = text.match(/([A-Za-z_][A-Za-z0-9_]*)$/);
182
+ return match ? match[1] : null;
183
+ }
184
+ function extractCallNamesTs(node, config) {
185
+ const calls = new Set();
186
+ function walk(n) {
187
+ if (config.callTypes.includes(n.type)) {
188
+ const calleeNode = getFieldOrFallback(n, config.calleeFieldCandidates) ?? n.namedChild(0);
189
+ if (calleeNode) {
190
+ const name = extractTrailingIdentifier(calleeNode.text);
191
+ if (name)
192
+ calls.add(name);
193
+ }
194
+ }
195
+ for (let i = 0; i < n.namedChildCount; i++) {
196
+ const child = n.namedChild(i);
197
+ if (child)
198
+ walk(child);
199
+ }
200
+ }
201
+ walk(node);
202
+ return Array.from(calls);
203
+ }
204
+ function extractImportsTs(root, config) {
205
+ const imports = [];
206
+ if (config.importTypes.length === 0)
207
+ return imports;
208
+ function walk(n) {
209
+ if (config.importTypes.includes(n.type)) {
210
+ imports.push({ imported: "", source: n.text.trim(), isDefault: false });
211
+ }
212
+ for (let i = 0; i < n.namedChildCount; i++) {
213
+ const child = n.namedChild(i);
214
+ if (child)
215
+ walk(child);
216
+ }
217
+ }
218
+ walk(root);
219
+ return imports;
220
+ }
221
+ // ── Main extraction ───────────────────────────────────────────────────────────
222
+ async function parseWithTreeSitter(filePath, source) {
223
+ const ext = path_1.default.extname(filePath).toLowerCase();
224
+ const langKey = EXT_TO_LANGUAGE[ext];
225
+ if (!langKey)
226
+ return [];
227
+ const config = LANGUAGE_CONFIGS[langKey];
228
+ if (!config)
229
+ return [];
230
+ try {
231
+ await ensureInit();
232
+ const language = await loadLanguage(langKey, config);
233
+ const parser = new web_tree_sitter_1.default();
234
+ parser.setLanguage(language);
235
+ const tree = parser.parse(source);
236
+ const root = tree.rootNode;
237
+ const fileImports = extractImportsTs(root, config);
238
+ const symbols = [];
239
+ function visit(node, enclosingClassName) {
240
+ if (config.classTypes.includes(node.type)) {
241
+ const className = getNodeName(node, config);
242
+ for (let i = 0; i < node.namedChildCount; i++) {
243
+ const child = node.namedChild(i);
244
+ if (child)
245
+ visit(child, className ?? undefined);
246
+ }
247
+ return;
248
+ }
249
+ if (config.functionTypes.includes(node.type)) {
250
+ const rawName = getNodeName(node, config);
251
+ if (rawName) {
252
+ const qualifiedName = enclosingClassName ? enclosingClassName + "." + rawName : rawName;
253
+ const body = node.text;
254
+ symbols.push({
255
+ name: qualifiedName,
256
+ type: enclosingClassName ? "method" : "function",
257
+ file: filePath,
258
+ startLine: node.startPosition.row + 1,
259
+ endLine: node.endPosition.row + 1,
260
+ body,
261
+ fingerprint: (0, ast_parser_1.computeFingerprint)(body),
262
+ calls: extractCallNamesTs(node, config),
263
+ imports: fileImports,
264
+ });
265
+ }
266
+ // still recurse in case of nested closures/functions
267
+ }
268
+ for (let i = 0; i < node.namedChildCount; i++) {
269
+ const child = node.namedChild(i);
270
+ if (child)
271
+ visit(child, enclosingClassName);
272
+ }
273
+ }
274
+ visit(root);
275
+ return symbols;
276
+ }
277
+ catch {
278
+ // Parsing failures must never crash a full-repo scan — return empty for this file
279
+ return [];
280
+ }
281
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.0.38",
3
+ "version": "1.1.1",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -28,12 +28,14 @@
28
28
  "kodingo-core": "^0.2.1",
29
29
  "open": "^11.0.0",
30
30
  "simple-git": "^3.30.0",
31
- "uuid": "^10.0.0"
31
+ "tree-sitter-wasms": "^0.1.13",
32
+ "typescript": "^5.9.3",
33
+ "uuid": "^10.0.0",
34
+ "web-tree-sitter": "^0.20.8"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@types/babel__traverse": "^7.28.0",
35
38
  "@types/node": "^22.19.20",
36
- "@types/uuid": "^10.0.0",
37
- "typescript": "^5.6.0"
39
+ "@types/uuid": "^10.0.0"
38
40
  }
39
41
  }