project-graph-mcp 1.3.0 → 2.0.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.
Files changed (113) hide show
  1. package/README.md +223 -17
  2. package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +87 -30
  3. package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +23 -8
  4. package/package.json +12 -8
  5. package/src/.project-graph-cache.json +1 -0
  6. package/src/analysis/analysis-cache.js +7 -0
  7. package/src/analysis/complexity.js +14 -0
  8. package/src/analysis/custom-rules.js +36 -0
  9. package/src/analysis/db-analysis.js +9 -0
  10. package/src/analysis/dead-code.js +19 -0
  11. package/src/analysis/full-analysis.js +18 -0
  12. package/src/analysis/jsdoc-checker.js +24 -0
  13. package/src/analysis/jsdoc-generator.js +10 -0
  14. package/src/analysis/large-files.js +11 -0
  15. package/src/analysis/outdated-patterns.js +12 -0
  16. package/src/analysis/similar-functions.js +16 -0
  17. package/src/analysis/test-annotations.js +21 -0
  18. package/src/analysis/type-checker.js +8 -0
  19. package/src/analysis/undocumented.js +14 -0
  20. package/src/cli/cli-handlers.js +4 -0
  21. package/src/cli/cli.js +5 -0
  22. package/src/compact/ai-context.js +7 -0
  23. package/src/compact/compact.js +18 -0
  24. package/src/compact/compress.js +13 -0
  25. package/src/compact/ctx-to-jsdoc.js +29 -0
  26. package/src/compact/doc-dialect.js +30 -0
  27. package/src/compact/expand.js +37 -0
  28. package/src/compact/framework-references.js +5 -0
  29. package/src/compact/instructions.js +3 -0
  30. package/src/compact/mode-config.js +8 -0
  31. package/src/compact/validate-pipeline.js +9 -0
  32. package/src/core/event-bus.js +9 -0
  33. package/src/core/filters.js +14 -0
  34. package/src/core/graph-builder.js +12 -0
  35. package/src/core/parser.js +31 -0
  36. package/src/core/workspace.js +8 -0
  37. package/src/lang/lang-go.js +17 -0
  38. package/src/lang/lang-python.js +12 -0
  39. package/src/lang/lang-sql.js +23 -0
  40. package/src/lang/lang-typescript.js +9 -0
  41. package/src/lang/lang-utils.js +4 -0
  42. package/src/mcp/mcp-server.js +17 -0
  43. package/src/mcp/tool-defs.js +3 -0
  44. package/src/mcp/tools.js +25 -0
  45. package/src/network/backend-lifecycle.js +19 -0
  46. package/src/network/backend.js +5 -0
  47. package/src/network/local-gateway.js +23 -0
  48. package/src/network/mdns.js +13 -0
  49. package/src/network/server.js +10 -0
  50. package/src/network/web-server.js +34 -0
  51. package/vendor/terser.mjs +49 -0
  52. package/web/.project-graph-cache.json +1 -0
  53. package/web/app.js +16 -0
  54. package/web/components/code-block.js +3 -0
  55. package/web/components/quick-open.js +5 -0
  56. package/web/dashboard-state.js +3 -0
  57. package/web/dashboard.html +27 -0
  58. package/web/dashboard.js +8 -0
  59. package/web/highlight.js +13 -0
  60. package/web/index.html +35 -0
  61. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  62. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  63. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  64. package/web/panels/EventItem/EventItem.css.js +1 -0
  65. package/web/panels/EventItem/EventItem.js +4 -0
  66. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  67. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  68. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  69. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  70. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  71. package/web/panels/ProjectList/ProjectList.js +4 -0
  72. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  73. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  74. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  75. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  76. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  77. package/web/panels/code-viewer.js +5 -0
  78. package/web/panels/ctx-panel.js +4 -0
  79. package/web/panels/dep-graph.js +6 -0
  80. package/web/panels/file-tree.js +188 -0
  81. package/web/panels/health-panel.js +3 -0
  82. package/web/panels/live-monitor.js +3 -0
  83. package/web/state.js +17 -0
  84. package/web/style.css +157 -0
  85. package/references/symbiote-3x.md +0 -834
  86. package/src/cli-handlers.js +0 -140
  87. package/src/cli.js +0 -83
  88. package/src/complexity.js +0 -223
  89. package/src/custom-rules.js +0 -583
  90. package/src/db-analysis.js +0 -194
  91. package/src/dead-code.js +0 -468
  92. package/src/filters.js +0 -227
  93. package/src/framework-references.js +0 -177
  94. package/src/full-analysis.js +0 -174
  95. package/src/graph-builder.js +0 -299
  96. package/src/instructions.js +0 -175
  97. package/src/jsdoc-generator.js +0 -214
  98. package/src/lang-go.js +0 -285
  99. package/src/lang-python.js +0 -197
  100. package/src/lang-sql.js +0 -309
  101. package/src/lang-typescript.js +0 -190
  102. package/src/lang-utils.js +0 -124
  103. package/src/large-files.js +0 -162
  104. package/src/mcp-server.js +0 -468
  105. package/src/outdated-patterns.js +0 -295
  106. package/src/parser.js +0 -452
  107. package/src/server.js +0 -28
  108. package/src/similar-functions.js +0 -278
  109. package/src/test-annotations.js +0 -301
  110. package/src/tool-defs.js +0 -525
  111. package/src/tools.js +0 -470
  112. package/src/undocumented.js +0 -260
  113. package/src/workspace.js +0 -70
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
- }
@@ -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
- }