project-graph-mcp 1.5.0 → 2.1.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/README.md +171 -31
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/package.json +12 -8
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +7 -0
- package/src/analysis/complexity.js +14 -0
- package/src/analysis/custom-rules.js +36 -0
- package/src/analysis/db-analysis.js +9 -0
- package/src/analysis/dead-code.js +19 -0
- package/src/analysis/full-analysis.js +18 -0
- package/src/analysis/jsdoc-checker.js +24 -0
- package/src/analysis/jsdoc-generator.js +10 -0
- package/src/analysis/large-files.js +11 -0
- package/src/analysis/outdated-patterns.js +12 -0
- package/src/analysis/similar-functions.js +16 -0
- package/src/analysis/test-annotations.js +21 -0
- package/src/analysis/type-checker.js +8 -0
- package/src/analysis/undocumented.js +14 -0
- package/src/cli/cli-handlers.js +4 -0
- package/src/cli/cli.js +5 -0
- package/src/compact/.project-graph-cache.json +1 -0
- package/src/compact/ai-context.js +7 -0
- package/src/compact/compact-migrate.js +17 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +14 -0
- package/src/compact/ctx-to-jsdoc.js +29 -0
- package/src/compact/doc-dialect.js +30 -0
- package/src/compact/expand.js +37 -0
- package/src/compact/framework-references.js +5 -0
- package/src/compact/instructions.js +3 -0
- package/src/compact/mode-config.js +8 -0
- package/src/compact/validate-pipeline.js +9 -0
- package/src/core/event-bus.js +9 -0
- package/src/core/filters.js +14 -0
- package/src/core/graph-builder.js +12 -0
- package/src/core/parser.js +31 -0
- package/src/core/workspace.js +8 -0
- package/src/lang/lang-go.js +17 -0
- package/src/lang/lang-python.js +12 -0
- package/src/lang/lang-sql.js +23 -0
- package/src/lang/lang-typescript.js +9 -0
- package/src/lang/lang-utils.js +4 -0
- package/src/mcp/mcp-server.js +17 -0
- package/src/mcp/tool-defs.js +3 -0
- package/src/mcp/tools.js +25 -0
- package/src/network/backend-lifecycle.js +19 -0
- package/src/network/backend.js +5 -0
- package/src/network/local-gateway.js +23 -0
- package/src/network/mdns.js +13 -0
- package/src/network/server.js +10 -0
- package/src/network/web-server.js +34 -0
- package/web/.project-graph-cache.json +1 -0
- package/web/app.js +17 -0
- package/web/components/code-block.js +3 -0
- package/web/components/quick-open.js +5 -0
- package/web/dashboard-state.js +3 -0
- package/web/dashboard.html +27 -0
- package/web/dashboard.js +8 -0
- package/web/highlight.js +13 -0
- package/web/index.html +35 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +4 -0
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -0
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.js +5 -0
- package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +4 -0
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +5 -0
- package/web/panels/ctx-panel.js +4 -0
- package/web/panels/dep-graph.js +6 -0
- package/web/panels/file-tree.js +188 -0
- package/web/panels/health-panel.js +3 -0
- package/web/panels/live-monitor.js +3 -0
- package/web/state.js +17 -0
- package/web/style.css +157 -0
- package/references/symbiote-3x.md +0 -834
- package/src/ai-context.js +0 -113
- package/src/analysis-cache.js +0 -155
- package/src/cli-handlers.js +0 -271
- package/src/cli.js +0 -95
- package/src/compact.js +0 -207
- package/src/complexity.js +0 -237
- package/src/compress.js +0 -319
- package/src/ctx-to-jsdoc.js +0 -514
- package/src/custom-rules.js +0 -584
- package/src/db-analysis.js +0 -194
- package/src/dead-code.js +0 -468
- package/src/doc-dialect.js +0 -716
- package/src/filters.js +0 -227
- package/src/framework-references.js +0 -177
- package/src/full-analysis.js +0 -470
- package/src/graph-builder.js +0 -299
- package/src/instructions.js +0 -73
- package/src/jsdoc-checker.js +0 -351
- package/src/jsdoc-generator.js +0 -203
- package/src/lang-go.js +0 -285
- package/src/lang-python.js +0 -197
- package/src/lang-sql.js +0 -309
- package/src/lang-typescript.js +0 -190
- package/src/lang-utils.js +0 -124
- package/src/large-files.js +0 -163
- package/src/mcp-server.js +0 -675
- package/src/mode-config.js +0 -127
- package/src/outdated-patterns.js +0 -296
- package/src/parser.js +0 -662
- package/src/server.js +0 -28
- package/src/similar-functions.js +0 -279
- package/src/test-annotations.js +0 -323
- package/src/tool-defs.js +0 -793
- package/src/tools.js +0 -470
- package/src/type-checker.js +0 -188
- package/src/undocumented.js +0 -259
- package/src/workspace.js +0 -70
- /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
- /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
package/src/lang-sql.js
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQL Language Support for Project Graph
|
|
3
|
-
*
|
|
4
|
-
* Parses .sql files (schema dumps) and extracts SQL queries from code strings.
|
|
5
|
-
* Provides table/column extraction via regex (zero-dependency, ~80% accuracy).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {Object} TableInfo
|
|
10
|
-
* @property {string} name - Table name
|
|
11
|
-
* @property {{name: string, type: string}[]} columns - Column definitions
|
|
12
|
-
* @property {string} file - Source file
|
|
13
|
-
* @property {number} line - Line number
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {Object} SQLExtraction
|
|
18
|
-
* @property {string[]} reads - Tables read (SELECT/FROM/JOIN)
|
|
19
|
-
* @property {string[]} writes - Tables written (INSERT/UPDATE/DELETE)
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* SQL keywords that should NOT be treated as table names.
|
|
24
|
-
* Used to filter false positives from regex extraction.
|
|
25
|
-
*/
|
|
26
|
-
const SQL_KEYWORDS = new Set([
|
|
27
|
-
'select', 'from', 'where', 'and', 'or', 'not', 'in', 'on',
|
|
28
|
-
'as', 'join', 'left', 'right', 'inner', 'outer', 'cross', 'full',
|
|
29
|
-
'group', 'order', 'by', 'having', 'limit', 'offset', 'union',
|
|
30
|
-
'all', 'distinct', 'case', 'when', 'then', 'else', 'end',
|
|
31
|
-
'null', 'true', 'false', 'is', 'between', 'like', 'ilike',
|
|
32
|
-
'exists', 'any', 'some', 'set', 'values', 'into', 'table',
|
|
33
|
-
'create', 'alter', 'drop', 'index', 'primary', 'key', 'foreign',
|
|
34
|
-
'references', 'constraint', 'default', 'check', 'unique',
|
|
35
|
-
'if', 'begin', 'commit', 'rollback', 'transaction',
|
|
36
|
-
'returning', 'conflict', 'nothing', 'do', 'update',
|
|
37
|
-
'cascade', 'restrict', 'lateral', 'each', 'row',
|
|
38
|
-
'with', 'recursive', 'only',
|
|
39
|
-
// PostgreSQL data types (prevent false positives)
|
|
40
|
-
'integer', 'int', 'bigint', 'smallint', 'serial', 'bigserial',
|
|
41
|
-
'text', 'varchar', 'char', 'character', 'boolean', 'bool',
|
|
42
|
-
'timestamp', 'timestamptz', 'date', 'time', 'timetz', 'interval',
|
|
43
|
-
'numeric', 'decimal', 'real', 'float', 'double',
|
|
44
|
-
'json', 'jsonb', 'uuid', 'bytea', 'inet', 'cidr', 'macaddr',
|
|
45
|
-
'array', 'point', 'line', 'box', 'circle', 'polygon', 'path',
|
|
46
|
-
// Common false positives
|
|
47
|
-
'count', 'sum', 'avg', 'min', 'max', 'coalesce', 'cast',
|
|
48
|
-
'extract', 'now', 'current_timestamp', 'current_date',
|
|
49
|
-
'generate_series', 'unnest', 'string_agg', 'array_agg',
|
|
50
|
-
'row_number', 'rank', 'dense_rank', 'over', 'partition',
|
|
51
|
-
'asc', 'desc', 'nulls', 'first', 'last', 'filter',
|
|
52
|
-
// PostgreSQL system/meta identifiers
|
|
53
|
-
'columns', 'rows', 'tables', 'schema', 'schemas',
|
|
54
|
-
'information_schema', 'pg_catalog', 'pg_tables', 'pg_class',
|
|
55
|
-
]);
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Check if a string looks like a SQL query.
|
|
59
|
-
* Requires SQL keyword anchor at the start to minimize false positives.
|
|
60
|
-
* @param {string} str
|
|
61
|
-
* @returns {boolean}
|
|
62
|
-
*/
|
|
63
|
-
export function isSQLString(str) {
|
|
64
|
-
if (!str || typeof str !== 'string') return false;
|
|
65
|
-
return /^\s*(SELECT|INSERT|UPDATE|DELETE|WITH|CREATE\s+TABLE)\b/i.test(str);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Check if identifier is a valid table name (not a SQL keyword or too short).
|
|
70
|
-
* @param {string} name
|
|
71
|
-
* @returns {boolean}
|
|
72
|
-
*/
|
|
73
|
-
function isValidTableName(name) {
|
|
74
|
-
if (!name || name.length < 2) return false;
|
|
75
|
-
if (SQL_KEYWORDS.has(name.toLowerCase())) return false;
|
|
76
|
-
// Must look like a valid identifier
|
|
77
|
-
if (!/^[a-zA-Z_]\w*$/.test(name)) return false;
|
|
78
|
-
// Reject ALL-UPPERCASE or PascalCase (real PG tables are snake_case/lowercase)
|
|
79
|
-
if (/^[A-Z][A-Z_]*$/.test(name)) return false; // SKIP, API, etc.
|
|
80
|
-
if (/^[A-Z][a-z]/.test(name)) return false; // Job, Organization, etc.
|
|
81
|
-
// Reject PostgreSQL built-in functions/types
|
|
82
|
-
if (/^(pg_|jsonb_|array_|string_|regexp_)/.test(name)) return false;
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Extract table names from a SQL string.
|
|
88
|
-
* Returns lists of tables being read and written.
|
|
89
|
-
* @param {string} sql
|
|
90
|
-
* @returns {SQLExtraction}
|
|
91
|
-
*/
|
|
92
|
-
export function extractSQLFromString(sql) {
|
|
93
|
-
if (!sql || typeof sql !== 'string') {
|
|
94
|
-
return { reads: [], writes: [] };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const reads = new Set();
|
|
98
|
-
const writes = new Set();
|
|
99
|
-
|
|
100
|
-
// Normalize: collapse whitespace, strip comments
|
|
101
|
-
const normalized = sql
|
|
102
|
-
.replace(/--[^\n]*/g, '') // single-line SQL comments
|
|
103
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // multi-line SQL comments
|
|
104
|
-
.replace(/\s+/g, ' ')
|
|
105
|
-
.trim();
|
|
106
|
-
|
|
107
|
-
// READ patterns
|
|
108
|
-
// FROM table [alias] — skip function calls: FROM func(...)
|
|
109
|
-
const fromRegex = /\bFROM\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;
|
|
110
|
-
let match;
|
|
111
|
-
while ((match = fromRegex.exec(normalized)) !== null) {
|
|
112
|
-
// Skip if followed by ( — it's a function call, not a table
|
|
113
|
-
const afterMatch = normalized.slice(match.index + match[0].length).trimStart();
|
|
114
|
-
if (afterMatch.startsWith('(')) continue;
|
|
115
|
-
const name = match[1].split('.').pop(); // handle schema.table
|
|
116
|
-
if (isValidTableName(name)) reads.add(name);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// JOIN table [alias] — same check for safety
|
|
120
|
-
const joinRegex = /\bJOIN\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;
|
|
121
|
-
while ((match = joinRegex.exec(normalized)) !== null) {
|
|
122
|
-
const afterMatch = normalized.slice(match.index + match[0].length).trimStart();
|
|
123
|
-
if (afterMatch.startsWith('(')) continue;
|
|
124
|
-
const name = match[1].split('.').pop();
|
|
125
|
-
if (isValidTableName(name)) reads.add(name);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// WRITE patterns
|
|
129
|
-
// INSERT INTO table
|
|
130
|
-
const insertRegex = /\bINSERT\s+INTO\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;
|
|
131
|
-
while ((match = insertRegex.exec(normalized)) !== null) {
|
|
132
|
-
const name = match[1].split('.').pop();
|
|
133
|
-
if (isValidTableName(name)) writes.add(name);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// UPDATE table
|
|
137
|
-
const updateRegex = /\bUPDATE\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;
|
|
138
|
-
while ((match = updateRegex.exec(normalized)) !== null) {
|
|
139
|
-
const name = match[1].split('.').pop();
|
|
140
|
-
if (isValidTableName(name)) writes.add(name);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// DELETE FROM table
|
|
144
|
-
const deleteRegex = /\bDELETE\s+FROM\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;
|
|
145
|
-
while ((match = deleteRegex.exec(normalized)) !== null) {
|
|
146
|
-
const name = match[1].split('.').pop();
|
|
147
|
-
if (isValidTableName(name)) writes.add(name);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Per peer review: remove DELETE primary target from reads to avoid double-counting.
|
|
151
|
-
// Only the primary mutation target is removed — subquery reads are preserved.
|
|
152
|
-
for (const w of writes) {
|
|
153
|
-
if (/\bDELETE\s+FROM\s+/i.test(normalized)) {
|
|
154
|
-
const deleteTargetMatch = normalized.match(/\bDELETE\s+FROM\s+([a-zA-Z_]\w*)/i);
|
|
155
|
-
if (deleteTargetMatch) {
|
|
156
|
-
const primaryTarget = deleteTargetMatch[1].split('.').pop();
|
|
157
|
-
if (w === primaryTarget) reads.delete(primaryTarget);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
reads: [...reads],
|
|
164
|
-
writes: [...writes],
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Parse a .sql file (schema dump / migration).
|
|
170
|
-
* Extracts CREATE TABLE statements with column definitions.
|
|
171
|
-
* @param {string} code - SQL file content
|
|
172
|
-
* @param {string} filename - File path
|
|
173
|
-
* @returns {Object} ParseResult-compatible + tables[]
|
|
174
|
-
*/
|
|
175
|
-
export function parseSQL(code = '', filename = '') {
|
|
176
|
-
const result = {
|
|
177
|
-
file: filename,
|
|
178
|
-
classes: [],
|
|
179
|
-
functions: [],
|
|
180
|
-
imports: [],
|
|
181
|
-
exports: [],
|
|
182
|
-
tables: [],
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
if (!code) return result;
|
|
186
|
-
|
|
187
|
-
// Match CREATE TABLE statements
|
|
188
|
-
// Handles: CREATE TABLE [IF NOT EXISTS] [schema.]name (columns...)
|
|
189
|
-
const createTableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:[a-zA-Z_]\w*\.)?([a-zA-Z_]\w*)\s*\(([\s\S]*?)\);/gi;
|
|
190
|
-
let match;
|
|
191
|
-
|
|
192
|
-
while ((match = createTableRegex.exec(code)) !== null) {
|
|
193
|
-
const tableName = match[1];
|
|
194
|
-
const columnsBlock = match[2];
|
|
195
|
-
const line = code.substring(0, match.index).split('\n').length;
|
|
196
|
-
|
|
197
|
-
const columns = parseColumns(columnsBlock);
|
|
198
|
-
|
|
199
|
-
result.tables.push({
|
|
200
|
-
name: tableName,
|
|
201
|
-
columns,
|
|
202
|
-
file: filename,
|
|
203
|
-
line,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return result;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Parse column definitions from CREATE TABLE body.
|
|
212
|
-
* @param {string} block - The content between parentheses
|
|
213
|
-
* @returns {{name: string, type: string}[]}
|
|
214
|
-
*/
|
|
215
|
-
function parseColumns(block) {
|
|
216
|
-
const columns = [];
|
|
217
|
-
// Split by commas, but respect parentheses (for types like NUMERIC(10,2))
|
|
218
|
-
const parts = splitByTopLevelComma(block);
|
|
219
|
-
|
|
220
|
-
for (const part of parts) {
|
|
221
|
-
const trimmed = part.trim();
|
|
222
|
-
// Skip constraints (PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK, CONSTRAINT)
|
|
223
|
-
if (/^\s*(PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT|EXCLUDE)\b/i.test(trimmed)) {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Match: column_name TYPE [constraints...]
|
|
228
|
-
const colMatch = trimmed.match(/^([a-zA-Z_]\w*)\s+([A-Za-z]\w*(?:\s*\([^)]*\))?(?:\s*\[\])?)/);
|
|
229
|
-
if (colMatch) {
|
|
230
|
-
const name = colMatch[1];
|
|
231
|
-
const type = colMatch[2].trim();
|
|
232
|
-
// Skip if "name" is a SQL keyword (misparse)
|
|
233
|
-
if (!SQL_KEYWORDS.has(name.toLowerCase())) {
|
|
234
|
-
columns.push({ name, type });
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return columns;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Split string by commas, respecting parentheses depth.
|
|
244
|
-
* @param {string} str
|
|
245
|
-
* @returns {string[]}
|
|
246
|
-
*/
|
|
247
|
-
function splitByTopLevelComma(str) {
|
|
248
|
-
const parts = [];
|
|
249
|
-
let current = '';
|
|
250
|
-
let depth = 0;
|
|
251
|
-
|
|
252
|
-
for (let i = 0; i < str.length; i++) {
|
|
253
|
-
const ch = str[i];
|
|
254
|
-
if (ch === '(') depth++;
|
|
255
|
-
else if (ch === ')') depth--;
|
|
256
|
-
else if (ch === ',' && depth === 0) {
|
|
257
|
-
parts.push(current);
|
|
258
|
-
current = '';
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
current += ch;
|
|
262
|
-
}
|
|
263
|
-
if (current.trim()) parts.push(current);
|
|
264
|
-
|
|
265
|
-
return parts;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Extract SQL queries from raw source code (Python/Go pre-pass).
|
|
270
|
-
* Scans string literals and finds SQL patterns before stripStringsAndComments
|
|
271
|
-
* destroys the string contents.
|
|
272
|
-
*
|
|
273
|
-
* Returns aggregated reads/writes for the entire file.
|
|
274
|
-
* @param {string} code - Raw source code
|
|
275
|
-
* @returns {SQLExtraction}
|
|
276
|
-
*/
|
|
277
|
-
export function extractSQLFromCode(code) {
|
|
278
|
-
const allReads = new Set();
|
|
279
|
-
const allWrites = new Set();
|
|
280
|
-
|
|
281
|
-
if (!code) return { reads: [], writes: [] };
|
|
282
|
-
|
|
283
|
-
// Find all string literals that look like SQL
|
|
284
|
-
// Match: "...", '...', `...`, """...""", '''...'''
|
|
285
|
-
const stringPatterns = [
|
|
286
|
-
/"""([\s\S]*?)"""/g, // Python triple double-quote
|
|
287
|
-
/'''([\s\S]*?)'''/g, // Python triple single-quote
|
|
288
|
-
/`([\s\S]*?)`/g, // Go/JS backtick
|
|
289
|
-
/"((?:[^"\\]|\\.)*)"/g, // Double-quoted
|
|
290
|
-
/'((?:[^'\\]|\\.)*)'/g, // Single-quoted
|
|
291
|
-
];
|
|
292
|
-
|
|
293
|
-
for (const pattern of stringPatterns) {
|
|
294
|
-
let match;
|
|
295
|
-
while ((match = pattern.exec(code)) !== null) {
|
|
296
|
-
const content = match[1];
|
|
297
|
-
if (isSQLString(content)) {
|
|
298
|
-
const extraction = extractSQLFromString(content);
|
|
299
|
-
extraction.reads.forEach(t => allReads.add(t));
|
|
300
|
-
extraction.writes.forEach(t => allWrites.add(t));
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return {
|
|
306
|
-
reads: [...allReads],
|
|
307
|
-
writes: [...allWrites],
|
|
308
|
-
};
|
|
309
|
-
}
|
package/src/lang-typescript.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { stripStringsAndComments } from './lang-utils.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TypeScript/TSX regex-based parser.
|
|
5
|
-
* Extracts structural information (classes, functions, imports, exports, calls)
|
|
6
|
-
* directly from TypeScript code without relying on Acorn.
|
|
7
|
-
*
|
|
8
|
-
* Strategy: Instead of stripping TS syntax to feed Acorn (which causes
|
|
9
|
-
* catastrophic backtracking in regex), parse structural elements directly
|
|
10
|
-
* — same approach as lang-python.js and lang-go.js.
|
|
11
|
-
*
|
|
12
|
-
* @param {string} code - TypeScript source code
|
|
13
|
-
* @param {string} filename - File path for the result
|
|
14
|
-
* @returns {ParseResult}
|
|
15
|
-
*/
|
|
16
|
-
export function parseTypeScript(code, filename) {
|
|
17
|
-
const result = {
|
|
18
|
-
file: filename,
|
|
19
|
-
classes: [],
|
|
20
|
-
functions: [],
|
|
21
|
-
imports: [],
|
|
22
|
-
exports: [],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Strip strings, template literals, and comments to avoid false matches
|
|
26
|
-
const cleaned = stripStringsAndComments(code);
|
|
27
|
-
const lines = cleaned.split('\n');
|
|
28
|
-
|
|
29
|
-
let currentClass = null;
|
|
30
|
-
let currentFunc = null;
|
|
31
|
-
|
|
32
|
-
for (let i = 0; i < lines.length; i++) {
|
|
33
|
-
const line = lines[i];
|
|
34
|
-
const lineNum = i + 1;
|
|
35
|
-
|
|
36
|
-
// --- Imports ---
|
|
37
|
-
// import { A, B } from 'module'
|
|
38
|
-
const importFromMatch = line.match(/^\s*import\s+(?:type\s+)?(?:\{([^}]+)\}|(\w+))\s+from\s/);
|
|
39
|
-
if (importFromMatch) {
|
|
40
|
-
if (importFromMatch[1]) {
|
|
41
|
-
importFromMatch[1].split(',').forEach(s => {
|
|
42
|
-
const name = s.trim().replace(/\s+as\s+\w+/, '').replace(/^type\s+/, '');
|
|
43
|
-
if (name) result.imports.push(name);
|
|
44
|
-
});
|
|
45
|
-
} else if (importFromMatch[2]) {
|
|
46
|
-
result.imports.push(importFromMatch[2]);
|
|
47
|
-
}
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
// import * as name from 'module'
|
|
51
|
-
const importStarMatch = line.match(/^\s*import\s+\*\s+as\s+(\w+)\s+from\s/);
|
|
52
|
-
if (importStarMatch) {
|
|
53
|
-
result.imports.push(importStarMatch[1]);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// --- Exports ---
|
|
58
|
-
const exportMatch = line.match(/^\s*export\s+(?:default\s+)?(?:class|function|const|let|var|type|interface|enum|abstract)\s+(\w+)/);
|
|
59
|
-
if (exportMatch) {
|
|
60
|
-
result.exports.push(exportMatch[1]);
|
|
61
|
-
}
|
|
62
|
-
// export { A, B }
|
|
63
|
-
const exportBraceMatch = line.match(/^\s*export\s+\{([^}]+)\}/);
|
|
64
|
-
if (exportBraceMatch) {
|
|
65
|
-
exportBraceMatch[1].split(',').forEach(s => {
|
|
66
|
-
const name = s.trim().replace(/\s+as\s+\w+/, '');
|
|
67
|
-
if (name) result.exports.push(name);
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Skip type-only declarations (no runtime code)
|
|
72
|
-
if (/^\s*(type|interface)\s+\w+/.test(line)) {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// --- Classes ---
|
|
77
|
-
const classMatch = line.match(/^\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/);
|
|
78
|
-
if (classMatch) {
|
|
79
|
-
currentClass = {
|
|
80
|
-
name: classMatch[1],
|
|
81
|
-
extends: classMatch[2] || null,
|
|
82
|
-
methods: [],
|
|
83
|
-
properties: [],
|
|
84
|
-
calls: [],
|
|
85
|
-
file: filename,
|
|
86
|
-
line: lineNum,
|
|
87
|
-
};
|
|
88
|
-
result.classes.push(currentClass);
|
|
89
|
-
currentFunc = null;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Detect end of class or function (closing brace at col 0)
|
|
94
|
-
if (/^}/.test(line)) {
|
|
95
|
-
currentClass = null;
|
|
96
|
-
currentFunc = null;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// --- Methods (inside class) ---
|
|
101
|
-
if (currentClass) {
|
|
102
|
-
// public/private/protected/static/async methodName(
|
|
103
|
-
const methodMatch = line.match(/^\s+(?:(?:public|private|protected|static|readonly|abstract|override|async)\s+)*(\w+)\s*(?:<[^>]*>)?\s*\(/);
|
|
104
|
-
if (methodMatch && methodMatch[1] !== 'if' && methodMatch[1] !== 'for' &&
|
|
105
|
-
methodMatch[1] !== 'while' && methodMatch[1] !== 'switch' &&
|
|
106
|
-
methodMatch[1] !== 'catch' && methodMatch[1] !== 'return' &&
|
|
107
|
-
methodMatch[1] !== 'new' && methodMatch[1] !== 'constructor' &&
|
|
108
|
-
methodMatch[1] !== 'super') {
|
|
109
|
-
currentClass.methods.push(methodMatch[1]);
|
|
110
|
-
}
|
|
111
|
-
// constructor
|
|
112
|
-
if (/^\s+constructor\s*\(/.test(line)) {
|
|
113
|
-
currentClass.methods.push('constructor');
|
|
114
|
-
}
|
|
115
|
-
// Property: name: Type or name = value
|
|
116
|
-
const propMatch = line.match(/^\s+(?:(?:public|private|protected|static|readonly|declare|override|abstract)\s+)*(\w+)\s*[?!]?\s*[:=]/);
|
|
117
|
-
if (propMatch && !methodMatch && propMatch[1] !== 'if' && propMatch[1] !== 'const' &&
|
|
118
|
-
propMatch[1] !== 'let' && propMatch[1] !== 'var' && propMatch[1] !== 'return') {
|
|
119
|
-
currentClass.properties.push(propMatch[1]);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// --- Functions (top-level) ---
|
|
124
|
-
if (!currentClass) {
|
|
125
|
-
// function name(, async function name(, export function, export default function
|
|
126
|
-
const fnMatch = line.match(/^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
127
|
-
if (fnMatch) {
|
|
128
|
-
currentFunc = {
|
|
129
|
-
name: fnMatch[1],
|
|
130
|
-
exported: /^\s*export\s+/.test(line),
|
|
131
|
-
calls: [],
|
|
132
|
-
params: extractParams(line),
|
|
133
|
-
file: filename,
|
|
134
|
-
line: lineNum,
|
|
135
|
-
};
|
|
136
|
-
result.functions.push(currentFunc);
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
// Arrow functions: const name = (...) => or export const name = (
|
|
140
|
-
const arrowMatch = line.match(/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*(?::\s*\w+(?:<[^>]*>)?)?\s*=>/);
|
|
141
|
-
if (arrowMatch) {
|
|
142
|
-
currentFunc = {
|
|
143
|
-
name: arrowMatch[1],
|
|
144
|
-
exported: /^\s*export\s+/.test(line),
|
|
145
|
-
calls: [],
|
|
146
|
-
params: extractParams(line),
|
|
147
|
-
file: filename,
|
|
148
|
-
line: lineNum,
|
|
149
|
-
};
|
|
150
|
-
result.functions.push(currentFunc);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// --- Calls ---
|
|
156
|
-
const callRegex = /\b([a-zA-Z_$]\w*)\s*(?:<[^>]*>)?\s*\(/g;
|
|
157
|
-
let callMatch;
|
|
158
|
-
while ((callMatch = callRegex.exec(line)) !== null) {
|
|
159
|
-
const name = callMatch[1];
|
|
160
|
-
// Skip keywords and common built-ins
|
|
161
|
-
if (['if', 'for', 'while', 'switch', 'catch', 'return', 'new', 'throw',
|
|
162
|
-
'typeof', 'delete', 'void', 'import', 'export', 'class', 'function',
|
|
163
|
-
'const', 'let', 'var', 'async', 'await', 'super', 'this',
|
|
164
|
-
'interface', 'type', 'enum', 'declare', 'abstract'].includes(name)) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
if (currentClass) {
|
|
168
|
-
currentClass.calls.push(name);
|
|
169
|
-
} else if (currentFunc) {
|
|
170
|
-
currentFunc.calls.push(name);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Extract parameter names from a function signature line.
|
|
180
|
-
* @param {string} line
|
|
181
|
-
* @returns {string[]}
|
|
182
|
-
*/
|
|
183
|
-
function extractParams(line) {
|
|
184
|
-
const match = line.match(/\(([^)]*)\)/);
|
|
185
|
-
if (!match) return [];
|
|
186
|
-
return match[1]
|
|
187
|
-
.split(',')
|
|
188
|
-
.map(p => p.trim().replace(/[?!]?\s*:.*$/, '').replace(/\s*=.*$/, '').trim())
|
|
189
|
-
.filter(p => p && !p.startsWith('...'));
|
|
190
|
-
}
|
package/src/lang-utils.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strip strings, template literals, and comments from source code.
|
|
3
|
-
* Preserves line structure (newlines are kept) and character positions.
|
|
4
|
-
* @param {string} code
|
|
5
|
-
* @param {Object} [options]
|
|
6
|
-
* @param {boolean} [options.singleQuote=true] - Handle single-quoted strings
|
|
7
|
-
* @param {boolean} [options.backtick=true] - Handle backtick strings/templates
|
|
8
|
-
* @param {boolean} [options.hashComment=false] - Handle # comments (Python)
|
|
9
|
-
* @param {boolean} [options.tripleQuote=false] - Handle ''' and """ (Python)
|
|
10
|
-
* @param {boolean} [options.templateInterpolation=true] - Handle ${} in backticks
|
|
11
|
-
* @returns {string}
|
|
12
|
-
*/
|
|
13
|
-
export function stripStringsAndComments(code, options = {}) {
|
|
14
|
-
const {
|
|
15
|
-
singleQuote = true,
|
|
16
|
-
backtick = true,
|
|
17
|
-
hashComment = false,
|
|
18
|
-
tripleQuote = false,
|
|
19
|
-
templateInterpolation = true
|
|
20
|
-
} = options;
|
|
21
|
-
|
|
22
|
-
let result = '';
|
|
23
|
-
let i = 0;
|
|
24
|
-
|
|
25
|
-
while (i < code.length) {
|
|
26
|
-
// Hash comment
|
|
27
|
-
if (hashComment && code[i] === '#') {
|
|
28
|
-
while (i < code.length && code[i] !== '\n') {
|
|
29
|
-
result += ' ';
|
|
30
|
-
i++;
|
|
31
|
-
}
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Triple quotes
|
|
36
|
-
if (tripleQuote && (
|
|
37
|
-
(code[i] === "'" && code[i+1] === "'" && code[i+2] === "'") ||
|
|
38
|
-
(code[i] === '"' && code[i+1] === '"' && code[i+2] === '"')
|
|
39
|
-
)) {
|
|
40
|
-
const quote = code[i];
|
|
41
|
-
result += ' ';
|
|
42
|
-
i += 3;
|
|
43
|
-
while (i < code.length) {
|
|
44
|
-
if (code[i] === '\\') {
|
|
45
|
-
result += ' ';
|
|
46
|
-
i += 2;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (code[i] === quote && code[i+1] === quote && code[i+2] === quote) {
|
|
50
|
-
result += ' ';
|
|
51
|
-
i += 3;
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
result += code[i] === '\n' ? '\n' : ' ';
|
|
55
|
-
i++;
|
|
56
|
-
}
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Single-line comment //
|
|
61
|
-
if (!hashComment && code[i] === '/' && code[i + 1] === '/') {
|
|
62
|
-
while (i < code.length && code[i] !== '\n') {
|
|
63
|
-
result += ' ';
|
|
64
|
-
i++;
|
|
65
|
-
}
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Multi-line comment /* ... */
|
|
70
|
-
if (!hashComment && code[i] === '/' && code[i + 1] === '*') {
|
|
71
|
-
i += 2;
|
|
72
|
-
result += ' ';
|
|
73
|
-
while (i < code.length && !(code[i] === '*' && code[i + 1] === '/')) {
|
|
74
|
-
result += code[i] === '\n' ? '\n' : ' ';
|
|
75
|
-
i++;
|
|
76
|
-
}
|
|
77
|
-
if (i < code.length) { result += ' '; i += 2; }
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// String literals
|
|
82
|
-
if (code[i] === '"' || (singleQuote && code[i] === "'") || (backtick && code[i] === '`')) {
|
|
83
|
-
const quote = code[i];
|
|
84
|
-
result += ' ';
|
|
85
|
-
i++;
|
|
86
|
-
while (i < code.length) {
|
|
87
|
-
if (code[i] === '\\') {
|
|
88
|
-
result += ' ';
|
|
89
|
-
i += 2;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (code[i] === quote) {
|
|
93
|
-
result += ' ';
|
|
94
|
-
i++;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
// Template literal: ${...} — keep the expression
|
|
98
|
-
if (templateInterpolation && quote === '`' && code[i] === '$' && code[i + 1] === '{') {
|
|
99
|
-
result += '${';
|
|
100
|
-
i += 2;
|
|
101
|
-
let depth = 1;
|
|
102
|
-
while (i < code.length && depth > 0) {
|
|
103
|
-
if (code[i] === '{') depth++;
|
|
104
|
-
if (code[i] === '}') depth--;
|
|
105
|
-
if (depth > 0) {
|
|
106
|
-
result += code[i] === '\n' ? '\n' : code[i];
|
|
107
|
-
} else {
|
|
108
|
-
result += '}';
|
|
109
|
-
}
|
|
110
|
-
i++;
|
|
111
|
-
}
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
result += code[i] === '\n' ? '\n' : ' ';
|
|
115
|
-
i++;
|
|
116
|
-
}
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
result += code[i];
|
|
120
|
-
i++;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return result;
|
|
124
|
-
}
|