modwire 1.0.0__py3-none-any.whl
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.
- modwire/__init__.py +15 -0
- modwire/_version.py +24 -0
- modwire/architecture/__init__.py +10 -0
- modwire/architecture/analyzers.py +140 -0
- modwire/architecture/matching.py +58 -0
- modwire/architecture/policy.py +63 -0
- modwire/architecture/render.py +98 -0
- modwire/architecture/violations.py +24 -0
- modwire/definitions.py +101 -0
- modwire/extraction.py +73 -0
- modwire/extractors/__init__.py +5 -0
- modwire/extractors/base.py +177 -0
- modwire/extractors/loader.py +31 -0
- modwire/extractors/php.py +170 -0
- modwire/extractors/python.py +113 -0
- modwire/extractors/scripts/php_extractor.php +816 -0
- modwire/extractors/scripts/python_extractor.py +398 -0
- modwire/extractors/scripts/typescript_extractor.js +1030 -0
- modwire/extractors/typescript.py +48 -0
- modwire/graph.py +56 -0
- modwire-1.0.0.dist-info/METADATA +111 -0
- modwire-1.0.0.dist-info/RECORD +25 -0
- modwire-1.0.0.dist-info/WHEEL +5 -0
- modwire-1.0.0.dist-info/licenses/LICENSE +21 -0
- modwire-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function buildLineStarts(content) {
|
|
6
|
+
const lineStarts = [0];
|
|
7
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
8
|
+
if (content[index] === '\n') {
|
|
9
|
+
lineStarts.push(index + 1);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return lineStarts;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function lineNumberAt(lineStarts, index) {
|
|
16
|
+
let low = 0;
|
|
17
|
+
let high = lineStarts.length - 1;
|
|
18
|
+
while (low <= high) {
|
|
19
|
+
const middle = Math.floor((low + high) / 2);
|
|
20
|
+
if (lineStarts[middle] <= index) {
|
|
21
|
+
low = middle + 1;
|
|
22
|
+
} else {
|
|
23
|
+
high = middle - 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return high + 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function lineSpan(lineStarts, startIndex, endIndex) {
|
|
30
|
+
return lineNumberAt(lineStarts, endIndex) - lineNumberAt(lineStarts, startIndex) + 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findMatchingBrace(content, openBraceIndex) {
|
|
34
|
+
let depth = 0;
|
|
35
|
+
let inSingleQuote = false;
|
|
36
|
+
let inDoubleQuote = false;
|
|
37
|
+
let inTemplate = false;
|
|
38
|
+
let inLineComment = false;
|
|
39
|
+
let inBlockComment = false;
|
|
40
|
+
|
|
41
|
+
for (let index = openBraceIndex; index < content.length; index += 1) {
|
|
42
|
+
const current = content[index];
|
|
43
|
+
const next = content[index + 1];
|
|
44
|
+
|
|
45
|
+
if (inLineComment) {
|
|
46
|
+
if (current === '\n') {
|
|
47
|
+
inLineComment = false;
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inBlockComment) {
|
|
52
|
+
if (current === '*' && next === '/') {
|
|
53
|
+
inBlockComment = false;
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (inSingleQuote) {
|
|
59
|
+
if (current === '\\') {
|
|
60
|
+
index += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (current === '\'') {
|
|
64
|
+
inSingleQuote = false;
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (inDoubleQuote) {
|
|
69
|
+
if (current === '\\') {
|
|
70
|
+
index += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (current === '"') {
|
|
74
|
+
inDoubleQuote = false;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (inTemplate) {
|
|
79
|
+
if (current === '\\') {
|
|
80
|
+
index += 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (current === '`') {
|
|
84
|
+
inTemplate = false;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (current === '/' && next === '/') {
|
|
90
|
+
inLineComment = true;
|
|
91
|
+
index += 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (current === '/' && next === '*') {
|
|
95
|
+
inBlockComment = true;
|
|
96
|
+
index += 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (current === '\'') {
|
|
100
|
+
inSingleQuote = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (current === '"') {
|
|
104
|
+
inDoubleQuote = true;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (current === '`') {
|
|
108
|
+
inTemplate = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (current === '{') {
|
|
112
|
+
depth += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (current === '}') {
|
|
116
|
+
depth -= 1;
|
|
117
|
+
if (depth === 0) {
|
|
118
|
+
return index;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return openBraceIndex;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function findMatchingParen(content, openParenIndex) {
|
|
126
|
+
let depth = 0;
|
|
127
|
+
let inSingleQuote = false;
|
|
128
|
+
let inDoubleQuote = false;
|
|
129
|
+
let inTemplate = false;
|
|
130
|
+
let inLineComment = false;
|
|
131
|
+
let inBlockComment = false;
|
|
132
|
+
|
|
133
|
+
for (let index = openParenIndex; index < content.length; index += 1) {
|
|
134
|
+
const current = content[index];
|
|
135
|
+
const next = content[index + 1];
|
|
136
|
+
|
|
137
|
+
if (inLineComment) {
|
|
138
|
+
if (current === '\n') {
|
|
139
|
+
inLineComment = false;
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (inBlockComment) {
|
|
144
|
+
if (current === '*' && next === '/') {
|
|
145
|
+
inBlockComment = false;
|
|
146
|
+
index += 1;
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (inSingleQuote) {
|
|
151
|
+
if (current === '\\') {
|
|
152
|
+
index += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (current === '\'') {
|
|
156
|
+
inSingleQuote = false;
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (inDoubleQuote) {
|
|
161
|
+
if (current === '\\') {
|
|
162
|
+
index += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (current === '"') {
|
|
166
|
+
inDoubleQuote = false;
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (inTemplate) {
|
|
171
|
+
if (current === '\\') {
|
|
172
|
+
index += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (current === '`') {
|
|
176
|
+
inTemplate = false;
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (current === '/' && next === '/') {
|
|
182
|
+
inLineComment = true;
|
|
183
|
+
index += 1;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (current === '/' && next === '*') {
|
|
187
|
+
inBlockComment = true;
|
|
188
|
+
index += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (current === '\'') {
|
|
192
|
+
inSingleQuote = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (current === '"') {
|
|
196
|
+
inDoubleQuote = true;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (current === '`') {
|
|
200
|
+
inTemplate = true;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (current === '(') {
|
|
204
|
+
depth += 1;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (current === ')') {
|
|
208
|
+
depth -= 1;
|
|
209
|
+
if (depth === 0) {
|
|
210
|
+
return index;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return openParenIndex;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function splitTopLevelParameters(parameterText) {
|
|
218
|
+
const parameters = [];
|
|
219
|
+
let startIndex = 0;
|
|
220
|
+
let depth = 0;
|
|
221
|
+
let inSingleQuote = false;
|
|
222
|
+
let inDoubleQuote = false;
|
|
223
|
+
let inTemplate = false;
|
|
224
|
+
|
|
225
|
+
for (let index = 0; index < parameterText.length; index += 1) {
|
|
226
|
+
const current = parameterText[index];
|
|
227
|
+
|
|
228
|
+
if (inSingleQuote) {
|
|
229
|
+
if (current === '\\') {
|
|
230
|
+
index += 1;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (current === '\'') {
|
|
234
|
+
inSingleQuote = false;
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (inDoubleQuote) {
|
|
239
|
+
if (current === '\\') {
|
|
240
|
+
index += 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (current === '"') {
|
|
244
|
+
inDoubleQuote = false;
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (inTemplate) {
|
|
249
|
+
if (current === '\\') {
|
|
250
|
+
index += 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (current === '`') {
|
|
254
|
+
inTemplate = false;
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (current === '\'') {
|
|
260
|
+
inSingleQuote = true;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (current === '"') {
|
|
264
|
+
inDoubleQuote = true;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (current === '`') {
|
|
268
|
+
inTemplate = true;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if ('({[<'.includes(current)) {
|
|
272
|
+
depth += 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (')}]>'.includes(current)) {
|
|
276
|
+
depth = Math.max(0, depth - 1);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (current === ',' && depth === 0) {
|
|
280
|
+
parameters.push(parameterText.slice(startIndex, index).trim());
|
|
281
|
+
startIndex = index + 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
parameters.push(parameterText.slice(startIndex).trim());
|
|
286
|
+
return parameters.filter(Boolean);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function hasTopLevelCharacter(value, character) {
|
|
290
|
+
let depth = 0;
|
|
291
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
292
|
+
const current = value[index];
|
|
293
|
+
if ('({[<'.includes(current)) {
|
|
294
|
+
depth += 1;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (')}]>'.includes(current)) {
|
|
298
|
+
depth = Math.max(0, depth - 1);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (current === character && depth === 0) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function parameterCounts(parameterText) {
|
|
309
|
+
const parameters = splitTopLevelParameters(parameterText);
|
|
310
|
+
return {
|
|
311
|
+
declared_args: parameters.length,
|
|
312
|
+
optional_args: parameters.filter(parameter => {
|
|
313
|
+
const withoutModifiers = parameter
|
|
314
|
+
.replace(/^(?:(?:public|protected|private|readonly|override|static)\s+)*/g, '')
|
|
315
|
+
.trim();
|
|
316
|
+
return withoutModifiers.startsWith('...')
|
|
317
|
+
|| hasTopLevelCharacter(withoutModifiers, '=')
|
|
318
|
+
|| /^[A-Za-z_$][\w$]*\?(\s*[:=]|$)/.test(withoutModifiers);
|
|
319
|
+
}).length,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function parameterCountsFromParen(content, openParenIndex) {
|
|
324
|
+
const closeParenIndex = findMatchingParen(content, openParenIndex);
|
|
325
|
+
return parameterCounts(content.slice(openParenIndex + 1, closeParenIndex));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function parameterPropertiesFromParen(content, openParenIndex) {
|
|
329
|
+
const closeParenIndex = findMatchingParen(content, openParenIndex);
|
|
330
|
+
return splitTopLevelParameters(content.slice(openParenIndex + 1, closeParenIndex))
|
|
331
|
+
.map(parameter => parameterProperty(parameter))
|
|
332
|
+
.filter(property => property !== null);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parameterProperty(parameter) {
|
|
336
|
+
const withoutDecorators = parameter.replace(/^@\w+(?:\([^)]*\))?\s*/g, '').trim();
|
|
337
|
+
const match = withoutDecorators.match(/^(?:(?:public|protected|private|readonly|override)\s+)+(?:\.\.\.\s*)?([A-Za-z_$][\w$]*)(\?)?/);
|
|
338
|
+
if (match === null) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
name: match[1],
|
|
343
|
+
is_optional: Boolean(match[2]) || propertyTextIsOptional(withoutDecorators),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function propertyTextIsOptional(value) {
|
|
348
|
+
const typeMatch = value.match(/:\s*([^=;]+)/);
|
|
349
|
+
const initializerMatch = value.match(/=\s*([^;]+)/);
|
|
350
|
+
return Boolean(typeMatch && /\b(null|undefined)\b/.test(typeMatch[1]))
|
|
351
|
+
|| Boolean(initializerMatch && /^(null|undefined)\b/.test(initializerMatch[1].trim()));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function addProperty(properties, name, isOptional) {
|
|
355
|
+
const existing = properties.get(name);
|
|
356
|
+
properties.set(name, Boolean(existing) || isOptional);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function collectSignaturesFromText(value, lineStarts, baseIndex) {
|
|
360
|
+
const signatures = [];
|
|
361
|
+
|
|
362
|
+
function addSignature(kind, matchIndex, matchText, parameterText) {
|
|
363
|
+
const counts = kind === 'index'
|
|
364
|
+
? { declared_args: 1, optional_args: 0 }
|
|
365
|
+
: parameterCounts(parameterText);
|
|
366
|
+
signatures.push({
|
|
367
|
+
kind,
|
|
368
|
+
line_count: lineSpan(
|
|
369
|
+
lineStarts,
|
|
370
|
+
baseIndex + matchIndex,
|
|
371
|
+
baseIndex + matchIndex + matchText.length,
|
|
372
|
+
),
|
|
373
|
+
declared_args: counts.declared_args,
|
|
374
|
+
optional_args: counts.optional_args,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let match;
|
|
379
|
+
const constructRegex = /(?:^|\n|;)\s*new\s*\(([^)]*)\)\s*:/g;
|
|
380
|
+
while ((match = constructRegex.exec(value)) !== null) {
|
|
381
|
+
addSignature('construct', match.index, match[0], match[1]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const callRegex = /(?:^|\n|;)\s*\(([^)]*)\)\s*:/g;
|
|
385
|
+
while ((match = callRegex.exec(value)) !== null) {
|
|
386
|
+
addSignature('call', match.index, match[0], match[1]);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const indexRegex = /(?:^|\n|;)\s*\[[^\]]+\]\s*:/g;
|
|
390
|
+
while ((match = indexRegex.exec(value)) !== null) {
|
|
391
|
+
addSignature('index', match.index, match[0], '');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return signatures;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isWithinRanges(index, ranges) {
|
|
398
|
+
return ranges.some(range => index >= range.start && index <= range.end);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function codeLineCount(content) {
|
|
402
|
+
return content
|
|
403
|
+
.split('\n')
|
|
404
|
+
.filter(line => {
|
|
405
|
+
const trimmed = line.trim();
|
|
406
|
+
return trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.startsWith('*');
|
|
407
|
+
})
|
|
408
|
+
.length;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function withoutExtension(value) {
|
|
412
|
+
return value.replace(/\.[cm]?[jt]sx?$/, '');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function normalizedImportPath(importPath, isRelative, filePath, sourcesRoot) {
|
|
416
|
+
if (!isRelative) {
|
|
417
|
+
return importPath.replace(/^\/+|\/+$/g, '');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const absolutePath = path.resolve(path.dirname(filePath), importPath);
|
|
421
|
+
return withoutExtension(path.relative(sourcesRoot, absolutePath).split(path.sep).join('/'));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function collectImports(content, filePath, sourcesRoot) {
|
|
425
|
+
const imports = [];
|
|
426
|
+
let statementId = 0;
|
|
427
|
+
|
|
428
|
+
function addImport(
|
|
429
|
+
importPath,
|
|
430
|
+
crossingType = 'module',
|
|
431
|
+
isAliased = false,
|
|
432
|
+
joinable = false,
|
|
433
|
+
) {
|
|
434
|
+
const isRelative = importPath.startsWith('.');
|
|
435
|
+
const normalizedPath = normalizedImportPath(
|
|
436
|
+
importPath,
|
|
437
|
+
isRelative,
|
|
438
|
+
filePath,
|
|
439
|
+
sourcesRoot,
|
|
440
|
+
);
|
|
441
|
+
statementId += 1;
|
|
442
|
+
imports.push({
|
|
443
|
+
path: importPath,
|
|
444
|
+
is_relative: isRelative,
|
|
445
|
+
normalized_path: normalizedPath,
|
|
446
|
+
imported_name: '',
|
|
447
|
+
is_aliased: isAliased,
|
|
448
|
+
crossing_type: crossingType,
|
|
449
|
+
file_barrier_crossed: true,
|
|
450
|
+
statement_id: statementId,
|
|
451
|
+
join_key: joinable ? normalizedPath : '',
|
|
452
|
+
uses_joined_import: joinable,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function hasAliasedImport(importSpecifiers) {
|
|
457
|
+
const specifiers = importSpecifiers.trim().replace(/^type\s+/, '');
|
|
458
|
+
if (!specifiers) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
if (/\*\s+as\s+[$\w]+/.test(specifiers)) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const namedImport = specifiers.match(/\{([\s\S]*?)\}/);
|
|
466
|
+
return namedImport !== null && /\bas\b/.test(namedImport[1]);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let match;
|
|
470
|
+
const importRegex = /import\s+([\s\S]*?)\s+from\s+['"](.*?)['"]/g;
|
|
471
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
472
|
+
const importSpecifiers = match[1].trim().replace(/^type\s+/, '');
|
|
473
|
+
const isNamespaceImport = importSpecifiers.startsWith('*');
|
|
474
|
+
addImport(
|
|
475
|
+
match[2],
|
|
476
|
+
Boolean(importSpecifiers) && !isNamespaceImport
|
|
477
|
+
? 'symbol'
|
|
478
|
+
: 'module',
|
|
479
|
+
hasAliasedImport(importSpecifiers),
|
|
480
|
+
!isNamespaceImport,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const sideEffectImportRegex = /import\s+['"](.*?)['"]/g;
|
|
485
|
+
while ((match = sideEffectImportRegex.exec(content)) !== null) {
|
|
486
|
+
addImport(match[1]);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const dynamicImportRegex = /import\(['"](.*?)['"]\)/g;
|
|
490
|
+
while ((match = dynamicImportRegex.exec(content)) !== null) {
|
|
491
|
+
addImport(match[1]);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const requireRegex = /require\(['"](.*?)['"]\)/g;
|
|
495
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
496
|
+
addImport(match[1]);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return imports;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function collectClasses(content, lineStarts) {
|
|
503
|
+
const classes = [];
|
|
504
|
+
const classRanges = [];
|
|
505
|
+
const classRegex = /\b(export\s+(?:default\s+)?)?class\s+([A-Za-z_$][\w$]*)[^{]*\{/g;
|
|
506
|
+
let match;
|
|
507
|
+
|
|
508
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
509
|
+
if (/\babstract\s+$/.test(content.slice(Math.max(0, match.index - 32), match.index))) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const openBraceIndex = content.indexOf('{', match.index);
|
|
513
|
+
const closeBraceIndex = findMatchingBrace(content, openBraceIndex);
|
|
514
|
+
const body = content.slice(openBraceIndex + 1, closeBraceIndex);
|
|
515
|
+
const methods = [];
|
|
516
|
+
const methodRanges = [];
|
|
517
|
+
const properties = new Map();
|
|
518
|
+
const seenMethods = new Set();
|
|
519
|
+
const methodRegex = /(?:^|\n)\s*((?:public|protected|private|static|async|get|set|readonly|override)\s+)*([#A-Za-z_$][\w$]*)\s*\(/g;
|
|
520
|
+
let methodMatch;
|
|
521
|
+
|
|
522
|
+
while ((methodMatch = methodRegex.exec(body)) !== null) {
|
|
523
|
+
const methodName = methodMatch[2];
|
|
524
|
+
if (seenMethods.has(methodName)) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
seenMethods.add(methodName);
|
|
528
|
+
const methodStart = openBraceIndex + 1 + methodMatch.index;
|
|
529
|
+
const methodOpenParen = body.indexOf('(', methodMatch.index);
|
|
530
|
+
const methodCloseParen = findMatchingParen(body, methodOpenParen);
|
|
531
|
+
const methodOpenBrace = body.indexOf('{', methodCloseParen);
|
|
532
|
+
if (methodOpenBrace === -1) {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
const methodBraceIndex = openBraceIndex + 1 + methodOpenBrace;
|
|
536
|
+
const methodCloseBraceIndex = findMatchingBrace(content, methodBraceIndex);
|
|
537
|
+
methodRanges.push({
|
|
538
|
+
start: methodMatch.index,
|
|
539
|
+
end: methodCloseBraceIndex - openBraceIndex - 1,
|
|
540
|
+
});
|
|
541
|
+
const counts = parameterCountsFromParen(
|
|
542
|
+
body,
|
|
543
|
+
methodOpenParen,
|
|
544
|
+
);
|
|
545
|
+
const methodVisibility = methodVisibilityFromTokens(
|
|
546
|
+
methodMatch[1] || '',
|
|
547
|
+
methodName,
|
|
548
|
+
);
|
|
549
|
+
methods.push({
|
|
550
|
+
name: methodName,
|
|
551
|
+
visibility: methodVisibility,
|
|
552
|
+
visibility_intent: visibilityIntent(methodName, methodVisibility),
|
|
553
|
+
line_count: lineSpan(lineStarts, methodStart, methodCloseBraceIndex),
|
|
554
|
+
declared_args: counts.declared_args,
|
|
555
|
+
optional_args: counts.optional_args,
|
|
556
|
+
});
|
|
557
|
+
if (methodName === 'constructor') {
|
|
558
|
+
for (const property of parameterPropertiesFromParen(body, methodOpenParen)) {
|
|
559
|
+
addProperty(properties, property.name, property.is_optional);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const propertyRegex = /(?:^|\n)\s*(?:(?:public|protected|private|static|readonly|declare|override|abstract|accessor)\s+)*([#A-Za-z_$][\w$]*)(\?)?\s*(?:(?::([^;=\n{]+))|(?:=\s*([^;\n]+))|;)/g;
|
|
565
|
+
let propertyMatch;
|
|
566
|
+
while ((propertyMatch = propertyRegex.exec(body)) !== null) {
|
|
567
|
+
if (isWithinRanges(propertyMatch.index, methodRanges)) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
addProperty(
|
|
571
|
+
properties,
|
|
572
|
+
propertyMatch[1],
|
|
573
|
+
Boolean(propertyMatch[2]) || propertyTextIsOptional(propertyMatch[0]),
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
classes.push({
|
|
577
|
+
name: match[2],
|
|
578
|
+
visibility: match[1] ? 'public' : 'private',
|
|
579
|
+
visibility_intent: visibilityIntent(match[2], match[1] ? 'public' : 'private'),
|
|
580
|
+
methods,
|
|
581
|
+
properties: Array.from(properties, ([name, is_optional]) => ({
|
|
582
|
+
name,
|
|
583
|
+
is_optional,
|
|
584
|
+
})),
|
|
585
|
+
line_count: lineSpan(lineStarts, match.index, closeBraceIndex),
|
|
586
|
+
});
|
|
587
|
+
classRanges.push({ start: match.index, end: closeBraceIndex });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return { classes, classRanges };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function collectInterfaces(content, lineStarts) {
|
|
594
|
+
const interfaces = [];
|
|
595
|
+
const interfaceRegex = /\b(export\s+)?interface\s+([A-Za-z_$][\w$]*)([^{]*)\{/g;
|
|
596
|
+
let match;
|
|
597
|
+
|
|
598
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
599
|
+
const openBraceIndex = content.indexOf('{', match.index);
|
|
600
|
+
const closeBraceIndex = findMatchingBrace(content, openBraceIndex);
|
|
601
|
+
const body = content.slice(openBraceIndex + 1, closeBraceIndex);
|
|
602
|
+
const methods = [];
|
|
603
|
+
const properties = new Map();
|
|
604
|
+
const signatures = collectSignaturesFromText(
|
|
605
|
+
body,
|
|
606
|
+
lineStarts,
|
|
607
|
+
openBraceIndex + 1,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
const methodRegex = /(?:^|\n)\s*([A-Za-z_$][\w$]*)\??\s*\(([^)]*)\)/g;
|
|
611
|
+
let methodMatch;
|
|
612
|
+
while ((methodMatch = methodRegex.exec(body)) !== null) {
|
|
613
|
+
const methodName = methodMatch[1];
|
|
614
|
+
if (methodName === 'new') {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const openParenIndex = body.indexOf('(', methodMatch.index);
|
|
618
|
+
const closeParenIndex = findMatchingParen(body, openParenIndex);
|
|
619
|
+
const counts = parameterCountsFromParen(body, openParenIndex);
|
|
620
|
+
methods.push({
|
|
621
|
+
name: methodName,
|
|
622
|
+
visibility: 'public',
|
|
623
|
+
visibility_intent: visibilityIntent(methodName, 'public'),
|
|
624
|
+
line_count: lineSpan(
|
|
625
|
+
lineStarts,
|
|
626
|
+
openBraceIndex + 1 + methodMatch.index,
|
|
627
|
+
openBraceIndex + 1 + closeParenIndex,
|
|
628
|
+
),
|
|
629
|
+
declared_args: counts.declared_args,
|
|
630
|
+
optional_args: counts.optional_args,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const propertyRegex = /(?:^|\n)\s*(?:(?:readonly)\s+)*([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^;\n]+)/g;
|
|
635
|
+
let propertyMatch;
|
|
636
|
+
while ((propertyMatch = propertyRegex.exec(body)) !== null) {
|
|
637
|
+
const propertyName = propertyMatch[1];
|
|
638
|
+
addProperty(
|
|
639
|
+
properties,
|
|
640
|
+
propertyName,
|
|
641
|
+
Boolean(propertyMatch[2]) || propertyTextIsOptional(propertyMatch[0]),
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
interfaces.push({
|
|
646
|
+
name: match[2],
|
|
647
|
+
visibility: match[1] ? 'public' : 'private',
|
|
648
|
+
visibility_intent: visibilityIntent(match[2], match[1] ? 'public' : 'private'),
|
|
649
|
+
methods,
|
|
650
|
+
properties: Array.from(properties, ([name, is_optional]) => ({
|
|
651
|
+
name,
|
|
652
|
+
is_optional,
|
|
653
|
+
})),
|
|
654
|
+
signatures,
|
|
655
|
+
line_count: lineSpan(lineStarts, match.index, closeBraceIndex),
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return interfaces;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function findTopLevelSemicolon(content, startIndex) {
|
|
663
|
+
let depth = 0;
|
|
664
|
+
let inSingleQuote = false;
|
|
665
|
+
let inDoubleQuote = false;
|
|
666
|
+
let inTemplate = false;
|
|
667
|
+
let inLineComment = false;
|
|
668
|
+
let inBlockComment = false;
|
|
669
|
+
|
|
670
|
+
for (let index = startIndex; index < content.length; index += 1) {
|
|
671
|
+
const current = content[index];
|
|
672
|
+
const next = content[index + 1];
|
|
673
|
+
|
|
674
|
+
if (inLineComment) {
|
|
675
|
+
if (current === '\n') {
|
|
676
|
+
inLineComment = false;
|
|
677
|
+
}
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (inBlockComment) {
|
|
681
|
+
if (current === '*' && next === '/') {
|
|
682
|
+
inBlockComment = false;
|
|
683
|
+
index += 1;
|
|
684
|
+
}
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (inSingleQuote) {
|
|
688
|
+
if (current === '\\') {
|
|
689
|
+
index += 1;
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (current === '\'') {
|
|
693
|
+
inSingleQuote = false;
|
|
694
|
+
}
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
if (inDoubleQuote) {
|
|
698
|
+
if (current === '\\') {
|
|
699
|
+
index += 1;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (current === '"') {
|
|
703
|
+
inDoubleQuote = false;
|
|
704
|
+
}
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (inTemplate) {
|
|
708
|
+
if (current === '\\') {
|
|
709
|
+
index += 1;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (current === '`') {
|
|
713
|
+
inTemplate = false;
|
|
714
|
+
}
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (current === '/' && next === '/') {
|
|
719
|
+
inLineComment = true;
|
|
720
|
+
index += 1;
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
if (current === '/' && next === '*') {
|
|
724
|
+
inBlockComment = true;
|
|
725
|
+
index += 1;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (current === '\'') {
|
|
729
|
+
inSingleQuote = true;
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (current === '"') {
|
|
733
|
+
inDoubleQuote = true;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (current === '`') {
|
|
737
|
+
inTemplate = true;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if ('({[<'.includes(current)) {
|
|
741
|
+
depth += 1;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (')}]>'.includes(current)) {
|
|
745
|
+
depth = Math.max(0, depth - 1);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (current === ';' && depth === 0) {
|
|
749
|
+
return index;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return content.length - 1;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function collectTypes(content, lineStarts) {
|
|
756
|
+
const types = [];
|
|
757
|
+
const typeRegex = /\b(export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/g;
|
|
758
|
+
let match;
|
|
759
|
+
|
|
760
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
761
|
+
const valueStartIndex = typeRegex.lastIndex;
|
|
762
|
+
const endIndex = findTopLevelSemicolon(content, valueStartIndex);
|
|
763
|
+
const value = content.slice(valueStartIndex, endIndex);
|
|
764
|
+
const properties = new Map();
|
|
765
|
+
const signatures = collectSignaturesFromText(value, lineStarts, valueStartIndex);
|
|
766
|
+
const openBraceIndex = value.indexOf('{');
|
|
767
|
+
|
|
768
|
+
if (openBraceIndex !== -1) {
|
|
769
|
+
const closeBraceIndex = findMatchingBrace(value, openBraceIndex);
|
|
770
|
+
const body = value.slice(openBraceIndex + 1, closeBraceIndex);
|
|
771
|
+
const propertyRegex = /(?:^|\n|;)\s*(?:(?:readonly)\s+)*([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^;\n]+)/g;
|
|
772
|
+
let propertyMatch;
|
|
773
|
+
while ((propertyMatch = propertyRegex.exec(body)) !== null) {
|
|
774
|
+
const propertyName = propertyMatch[1];
|
|
775
|
+
addProperty(
|
|
776
|
+
properties,
|
|
777
|
+
propertyName,
|
|
778
|
+
Boolean(propertyMatch[2]) || propertyTextIsOptional(propertyMatch[0]),
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
types.push({
|
|
784
|
+
name: match[2],
|
|
785
|
+
visibility: match[1] ? 'public' : 'private',
|
|
786
|
+
visibility_intent: visibilityIntent(match[2], match[1] ? 'public' : 'private'),
|
|
787
|
+
properties: Array.from(properties, ([name, is_optional]) => ({
|
|
788
|
+
name,
|
|
789
|
+
is_optional,
|
|
790
|
+
})),
|
|
791
|
+
signatures,
|
|
792
|
+
line_count: lineSpan(lineStarts, match.index, endIndex),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return types;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function collectAbstractClasses(content, lineStarts) {
|
|
800
|
+
const abstractClasses = [];
|
|
801
|
+
const classRegex = /\b(export\s+(?:default\s+)?)?abstract\s+class\s+([A-Za-z_$][\w$]*)[^{]*\{/g;
|
|
802
|
+
let match;
|
|
803
|
+
|
|
804
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
805
|
+
const openBraceIndex = content.indexOf('{', match.index);
|
|
806
|
+
const closeBraceIndex = findMatchingBrace(content, openBraceIndex);
|
|
807
|
+
const body = content.slice(openBraceIndex + 1, closeBraceIndex);
|
|
808
|
+
const abstractMethods = [];
|
|
809
|
+
const concreteMethods = [];
|
|
810
|
+
const methodRanges = [];
|
|
811
|
+
const properties = new Map();
|
|
812
|
+
const seenMethods = new Set();
|
|
813
|
+
const methodRegex = /(?:^|\n)\s*((?:public|protected|private|static|async|get|set|readonly|override|abstract)\s+)*([#A-Za-z_$][\w$]*)\s*\(/g;
|
|
814
|
+
let methodMatch;
|
|
815
|
+
|
|
816
|
+
while ((methodMatch = methodRegex.exec(body)) !== null) {
|
|
817
|
+
const methodName = methodMatch[2];
|
|
818
|
+
if (seenMethods.has(methodName)) {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
seenMethods.add(methodName);
|
|
822
|
+
const methodStart = openBraceIndex + 1 + methodMatch.index;
|
|
823
|
+
const methodOpenParen = body.indexOf('(', methodMatch.index);
|
|
824
|
+
const methodCloseParen = findMatchingParen(body, methodOpenParen);
|
|
825
|
+
const methodOpenBrace = body.indexOf('{', methodCloseParen);
|
|
826
|
+
const methodSemicolon = body.indexOf(';', methodCloseParen);
|
|
827
|
+
const isAbstract = /\babstract\b/.test(methodMatch[1] || '')
|
|
828
|
+
|| methodOpenBrace === -1
|
|
829
|
+
|| (methodSemicolon !== -1 && methodSemicolon < methodOpenBrace);
|
|
830
|
+
const methodEnd = isAbstract
|
|
831
|
+
? (methodSemicolon === -1 ? methodCloseParen : methodSemicolon)
|
|
832
|
+
: findMatchingBrace(content, openBraceIndex + 1 + methodOpenBrace);
|
|
833
|
+
methodRanges.push({
|
|
834
|
+
start: methodMatch.index,
|
|
835
|
+
end: methodEnd - openBraceIndex - 1,
|
|
836
|
+
});
|
|
837
|
+
const counts = parameterCountsFromParen(body, methodOpenParen);
|
|
838
|
+
const methodVisibility = methodVisibilityFromTokens(
|
|
839
|
+
methodMatch[1] || '',
|
|
840
|
+
methodName,
|
|
841
|
+
);
|
|
842
|
+
const method = {
|
|
843
|
+
name: methodName,
|
|
844
|
+
visibility: methodVisibility,
|
|
845
|
+
visibility_intent: visibilityIntent(methodName, methodVisibility),
|
|
846
|
+
line_count: lineSpan(lineStarts, methodStart, methodEnd),
|
|
847
|
+
declared_args: counts.declared_args,
|
|
848
|
+
optional_args: counts.optional_args,
|
|
849
|
+
};
|
|
850
|
+
if (isAbstract) {
|
|
851
|
+
abstractMethods.push(method);
|
|
852
|
+
} else {
|
|
853
|
+
concreteMethods.push(method);
|
|
854
|
+
}
|
|
855
|
+
if (methodName === 'constructor') {
|
|
856
|
+
for (const property of parameterPropertiesFromParen(body, methodOpenParen)) {
|
|
857
|
+
addProperty(properties, property.name, property.is_optional);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const propertyRegex = /(?:^|\n)\s*(?:(?:public|protected|private|static|readonly|declare|override|abstract|accessor)\s+)*([#A-Za-z_$][\w$]*)(\?)?\s*(?:(?::([^;=\n{]+))|(?:=\s*([^;\n]+))|;)/g;
|
|
863
|
+
let propertyMatch;
|
|
864
|
+
while ((propertyMatch = propertyRegex.exec(body)) !== null) {
|
|
865
|
+
if (isWithinRanges(propertyMatch.index, methodRanges)) {
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
addProperty(
|
|
869
|
+
properties,
|
|
870
|
+
propertyMatch[1],
|
|
871
|
+
Boolean(propertyMatch[2]) || propertyTextIsOptional(propertyMatch[0]),
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
abstractClasses.push({
|
|
875
|
+
name: match[2],
|
|
876
|
+
visibility: match[1] ? 'public' : 'private',
|
|
877
|
+
visibility_intent: visibilityIntent(match[2], match[1] ? 'public' : 'private'),
|
|
878
|
+
abstract_methods: abstractMethods,
|
|
879
|
+
concrete_methods: concreteMethods,
|
|
880
|
+
properties: Array.from(properties, ([name, is_optional]) => ({
|
|
881
|
+
name,
|
|
882
|
+
is_optional,
|
|
883
|
+
})),
|
|
884
|
+
line_count: lineSpan(lineStarts, match.index, closeBraceIndex),
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return abstractClasses;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function collectFunctions(content, lineStarts, classRanges) {
|
|
892
|
+
const functions = [];
|
|
893
|
+
const seenNames = new Set();
|
|
894
|
+
|
|
895
|
+
function addFunction(name, startIndex, endIndex, counts, visibility) {
|
|
896
|
+
if (!name || seenNames.has(name) || isWithinRanges(startIndex, classRanges)) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
seenNames.add(name);
|
|
900
|
+
functions.push({
|
|
901
|
+
name,
|
|
902
|
+
visibility,
|
|
903
|
+
visibility_intent: visibilityIntent(name, visibility),
|
|
904
|
+
line_count: lineSpan(lineStarts, startIndex, endIndex),
|
|
905
|
+
declared_args: counts.declared_args,
|
|
906
|
+
optional_args: counts.optional_args,
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
let match;
|
|
911
|
+
const functionRegex = /\b(export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/g;
|
|
912
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
913
|
+
const openParenIndex = content.indexOf('(', match.index);
|
|
914
|
+
const closeParenIndex = findMatchingParen(content, openParenIndex);
|
|
915
|
+
const openBraceIndex = content.indexOf('{', closeParenIndex);
|
|
916
|
+
if (openBraceIndex === -1) {
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
addFunction(
|
|
920
|
+
match[2],
|
|
921
|
+
match.index,
|
|
922
|
+
findMatchingBrace(content, openBraceIndex),
|
|
923
|
+
parameterCountsFromParen(content, openParenIndex),
|
|
924
|
+
match[1] ? 'public' : 'private',
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const arrowFunctionRegex = /\b(export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(\{)?/g;
|
|
929
|
+
while ((match = arrowFunctionRegex.exec(content)) !== null) {
|
|
930
|
+
const hasBlockBody = match[4] === '{';
|
|
931
|
+
const openBraceIndex = hasBlockBody ? content.indexOf('{', match.index) : match.index;
|
|
932
|
+
const endIndex = hasBlockBody ? findMatchingBrace(content, openBraceIndex) : match.index;
|
|
933
|
+
addFunction(
|
|
934
|
+
match[2],
|
|
935
|
+
match.index,
|
|
936
|
+
endIndex,
|
|
937
|
+
parameterCounts(match[3].replace(/^\(|\)$/g, '')),
|
|
938
|
+
match[1] ? 'public' : 'private',
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return functions;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function methodVisibilityFromTokens(tokens, name) {
|
|
946
|
+
if (name.startsWith('#')) {
|
|
947
|
+
return 'private';
|
|
948
|
+
}
|
|
949
|
+
if (/\bprivate\b/.test(tokens)) {
|
|
950
|
+
return 'private';
|
|
951
|
+
}
|
|
952
|
+
if (/\bprotected\b/.test(tokens)) {
|
|
953
|
+
return 'protected';
|
|
954
|
+
}
|
|
955
|
+
return 'public';
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function visibilityIntent(name, visibility) {
|
|
959
|
+
if (name.startsWith('#') || (name.startsWith('__') && !name.endsWith('__'))) {
|
|
960
|
+
return 'private';
|
|
961
|
+
}
|
|
962
|
+
if (name.startsWith('_') && !name.endsWith('__')) {
|
|
963
|
+
return 'protected';
|
|
964
|
+
}
|
|
965
|
+
return visibility;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function extractFile(filePath, sourcesRoot) {
|
|
969
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
970
|
+
const lineStarts = buildLineStarts(content);
|
|
971
|
+
const { classes, classRanges } = collectClasses(content, lineStarts);
|
|
972
|
+
const interfaces = collectInterfaces(content, lineStarts);
|
|
973
|
+
const types = collectTypes(content, lineStarts);
|
|
974
|
+
const abstractClasses = collectAbstractClasses(content, lineStarts);
|
|
975
|
+
const functions = collectFunctions(content, lineStarts, classRanges);
|
|
976
|
+
|
|
977
|
+
return {
|
|
978
|
+
imports: collectImports(content, filePath, sourcesRoot),
|
|
979
|
+
classes,
|
|
980
|
+
interfaces,
|
|
981
|
+
types,
|
|
982
|
+
abstract_classes: abstractClasses,
|
|
983
|
+
functions,
|
|
984
|
+
line_count: content.split('\n').length,
|
|
985
|
+
code_line_count: codeLineCount(content),
|
|
986
|
+
public_symbol_count: publicSymbolCount(content),
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function publicSymbolCount(content) {
|
|
991
|
+
const symbols = new Set();
|
|
992
|
+
let match;
|
|
993
|
+
|
|
994
|
+
const classRegex = /\bexport\s+(?:default\s+)?class\s+([A-Za-z_$][\w$]*)/g;
|
|
995
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
996
|
+
symbols.add(match[1]);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const abstractClassRegex = /\bexport\s+(?:default\s+)?abstract\s+class\s+([A-Za-z_$][\w$]*)/g;
|
|
1000
|
+
while ((match = abstractClassRegex.exec(content)) !== null) {
|
|
1001
|
+
symbols.add(match[1]);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const interfaceRegex = /\bexport\s+interface\s+([A-Za-z_$][\w$]*)/g;
|
|
1005
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
1006
|
+
symbols.add(match[1]);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const typeRegex = /\bexport\s+type\s+([A-Za-z_$][\w$]*)/g;
|
|
1010
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
1011
|
+
symbols.add(match[1]);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const functionRegex = /\bexport\s+(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/g;
|
|
1015
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
1016
|
+
symbols.add(match[1]);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const arrowFunctionRegex = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g;
|
|
1020
|
+
while ((match = arrowFunctionRegex.exec(content)) !== null) {
|
|
1021
|
+
symbols.add(match[1]);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return symbols.size;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
console.log(JSON.stringify(extractFile(
|
|
1028
|
+
path.resolve(process.argv[2]),
|
|
1029
|
+
process.argv[3] ? path.resolve(process.argv[3]) : process.cwd(),
|
|
1030
|
+
)));
|