@wtdlee/repomap 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzers/index.d.ts +69 -5
- package/dist/analyzers/index.js +1 -5
- package/dist/chunk-3PWXDB7B.js +153 -0
- package/dist/{generators/page-map-generator.js → chunk-3YFXZAP7.js} +322 -358
- package/dist/chunk-6F4PWJZI.js +1 -0
- package/dist/{generators/rails-map-generator.js → chunk-E4WRODSI.js} +86 -94
- package/dist/chunk-GNBMJMET.js +2519 -0
- package/dist/{server/doc-server.js → chunk-M6YNU536.js} +702 -303
- package/dist/chunk-OWM6WNLE.js +2610 -0
- package/dist/chunk-SSU6QFTX.js +1058 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +348 -452
- package/dist/dataflow-analyzer-BfAiqVUp.d.ts +180 -0
- package/dist/env-detector-EEMVUEIA.js +1 -0
- package/dist/generators/index.d.ts +431 -3
- package/dist/generators/index.js +2 -3
- package/dist/index.d.ts +53 -10
- package/dist/index.js +8 -11
- package/dist/page-map-generator-6MJGPBVA.js +1 -0
- package/dist/rails-UWSDRS33.js +1 -0
- package/dist/rails-map-generator-D2URLMVJ.js +2 -0
- package/dist/server/index.d.ts +33 -1
- package/dist/server/index.js +7 -1
- package/dist/types.d.ts +39 -37
- package/dist/types.js +1 -5
- package/package.json +4 -2
- package/dist/analyzers/base-analyzer.d.ts +0 -45
- package/dist/analyzers/base-analyzer.js +0 -47
- package/dist/analyzers/dataflow-analyzer.d.ts +0 -29
- package/dist/analyzers/dataflow-analyzer.js +0 -425
- package/dist/analyzers/graphql-analyzer.d.ts +0 -22
- package/dist/analyzers/graphql-analyzer.js +0 -386
- package/dist/analyzers/pages-analyzer.d.ts +0 -84
- package/dist/analyzers/pages-analyzer.js +0 -1695
- package/dist/analyzers/rails/index.d.ts +0 -46
- package/dist/analyzers/rails/index.js +0 -145
- package/dist/analyzers/rails/rails-controller-analyzer.d.ts +0 -82
- package/dist/analyzers/rails/rails-controller-analyzer.js +0 -478
- package/dist/analyzers/rails/rails-grpc-analyzer.d.ts +0 -44
- package/dist/analyzers/rails/rails-grpc-analyzer.js +0 -262
- package/dist/analyzers/rails/rails-model-analyzer.d.ts +0 -88
- package/dist/analyzers/rails/rails-model-analyzer.js +0 -493
- package/dist/analyzers/rails/rails-react-analyzer.d.ts +0 -41
- package/dist/analyzers/rails/rails-react-analyzer.js +0 -529
- package/dist/analyzers/rails/rails-routes-analyzer.d.ts +0 -62
- package/dist/analyzers/rails/rails-routes-analyzer.js +0 -540
- package/dist/analyzers/rails/rails-view-analyzer.d.ts +0 -49
- package/dist/analyzers/rails/rails-view-analyzer.js +0 -386
- package/dist/analyzers/rails/ruby-parser.d.ts +0 -63
- package/dist/analyzers/rails/ruby-parser.js +0 -212
- package/dist/analyzers/rest-api-analyzer.d.ts +0 -65
- package/dist/analyzers/rest-api-analyzer.js +0 -479
- package/dist/core/cache.d.ts +0 -47
- package/dist/core/cache.js +0 -151
- package/dist/core/engine.d.ts +0 -46
- package/dist/core/engine.js +0 -319
- package/dist/core/index.d.ts +0 -2
- package/dist/core/index.js +0 -2
- package/dist/generators/markdown-generator.d.ts +0 -25
- package/dist/generators/markdown-generator.js +0 -782
- package/dist/generators/mermaid-generator.d.ts +0 -35
- package/dist/generators/mermaid-generator.js +0 -364
- package/dist/generators/page-map-generator.d.ts +0 -22
- package/dist/generators/rails-map-generator.d.ts +0 -21
- package/dist/server/doc-server.d.ts +0 -30
- package/dist/utils/env-detector.d.ts +0 -31
- package/dist/utils/env-detector.js +0 -188
- package/dist/utils/parallel.d.ts +0 -23
- package/dist/utils/parallel.js +0 -70
- package/dist/utils/port.d.ts +0 -15
- package/dist/utils/port.js +0 -41
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rails Model Analyzer using tree-sitter
|
|
3
|
-
* tree-sitterを使用してモデルファイルを解析する
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { glob } from 'glob';
|
|
8
|
-
import { parseRubyFile, findNodes, getClassName, getSuperclass, getChildrenByType, } from './ruby-parser.js';
|
|
9
|
-
export class RailsModelAnalyzer {
|
|
10
|
-
rootPath;
|
|
11
|
-
modelsDir;
|
|
12
|
-
models = [];
|
|
13
|
-
errors = [];
|
|
14
|
-
constructor(rootPath) {
|
|
15
|
-
this.rootPath = rootPath;
|
|
16
|
-
this.modelsDir = path.join(rootPath, 'app', 'models');
|
|
17
|
-
}
|
|
18
|
-
async analyze() {
|
|
19
|
-
if (!fs.existsSync(this.modelsDir)) {
|
|
20
|
-
return {
|
|
21
|
-
models: [],
|
|
22
|
-
totalAssociations: 0,
|
|
23
|
-
totalValidations: 0,
|
|
24
|
-
concerns: [],
|
|
25
|
-
namespaces: [],
|
|
26
|
-
errors: [`Models directory not found at ${this.modelsDir}`],
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
const modelFiles = await glob('**/*.rb', {
|
|
30
|
-
cwd: this.modelsDir,
|
|
31
|
-
ignore: ['concerns/**', 'application_record.rb'],
|
|
32
|
-
});
|
|
33
|
-
for (const file of modelFiles) {
|
|
34
|
-
const fullPath = path.join(this.modelsDir, file);
|
|
35
|
-
try {
|
|
36
|
-
const model = await this.parseModelFile(fullPath, file);
|
|
37
|
-
if (model) {
|
|
38
|
-
this.models.push(model);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
this.errors.push(`Error parsing ${file}: ${error}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const concerns = [...new Set(this.models.flatMap((m) => m.concerns))];
|
|
46
|
-
const namespaces = [
|
|
47
|
-
...new Set(this.models
|
|
48
|
-
.map((m) => {
|
|
49
|
-
const parts = m.filePath.split('/');
|
|
50
|
-
return parts.length > 1 ? parts.slice(0, -1).join('/') : null;
|
|
51
|
-
})
|
|
52
|
-
.filter((n) => n !== null)),
|
|
53
|
-
];
|
|
54
|
-
const totalAssociations = this.models.reduce((sum, m) => sum + m.associations.length, 0);
|
|
55
|
-
const totalValidations = this.models.reduce((sum, m) => sum + m.validations.length, 0);
|
|
56
|
-
return {
|
|
57
|
-
models: this.models,
|
|
58
|
-
totalAssociations,
|
|
59
|
-
totalValidations,
|
|
60
|
-
concerns,
|
|
61
|
-
namespaces,
|
|
62
|
-
errors: this.errors,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
async parseModelFile(filePath, relativePath) {
|
|
66
|
-
const tree = await parseRubyFile(filePath);
|
|
67
|
-
const rootNode = tree.rootNode;
|
|
68
|
-
// Find class definition
|
|
69
|
-
const classNodes = findNodes(rootNode, 'class');
|
|
70
|
-
if (classNodes.length === 0)
|
|
71
|
-
return null;
|
|
72
|
-
const classNode = classNodes[0];
|
|
73
|
-
const className = getClassName(classNode);
|
|
74
|
-
const parentClass = getSuperclass(classNode);
|
|
75
|
-
if (!className)
|
|
76
|
-
return null;
|
|
77
|
-
// Skip non-ActiveRecord models
|
|
78
|
-
if (parentClass && !this.isActiveRecordModel(parentClass)) {
|
|
79
|
-
// Still include it but mark as non-AR
|
|
80
|
-
}
|
|
81
|
-
const model = {
|
|
82
|
-
name: className.replace(/.*::/, ''), // Remove namespace prefix
|
|
83
|
-
filePath: relativePath,
|
|
84
|
-
className,
|
|
85
|
-
parentClass: parentClass || 'ApplicationRecord',
|
|
86
|
-
associations: [],
|
|
87
|
-
validations: [],
|
|
88
|
-
callbacks: [],
|
|
89
|
-
scopes: [],
|
|
90
|
-
concerns: [],
|
|
91
|
-
enums: [],
|
|
92
|
-
attributes: [],
|
|
93
|
-
classMethodsCount: 0,
|
|
94
|
-
instanceMethodsCount: 0,
|
|
95
|
-
line: classNode.startPosition.row + 1,
|
|
96
|
-
};
|
|
97
|
-
// Parse table name if explicitly set
|
|
98
|
-
model.tableName = this.parseTableName(classNode);
|
|
99
|
-
// Find all method calls for associations, validations, etc.
|
|
100
|
-
const calls = findNodes(classNode, 'call');
|
|
101
|
-
for (const call of calls) {
|
|
102
|
-
const methodNode = call.childForFieldName('method');
|
|
103
|
-
if (!methodNode)
|
|
104
|
-
continue;
|
|
105
|
-
const methodName = methodNode.text;
|
|
106
|
-
const line = call.startPosition.row + 1;
|
|
107
|
-
// Associations
|
|
108
|
-
if (['belongs_to', 'has_one', 'has_many', 'has_and_belongs_to_many'].includes(methodName)) {
|
|
109
|
-
this.parseAssociation(call, methodName, model.associations, line);
|
|
110
|
-
}
|
|
111
|
-
// Validations
|
|
112
|
-
else if (methodName.startsWith('validates') || methodName === 'validate') {
|
|
113
|
-
this.parseValidation(call, methodName, model.validations, line);
|
|
114
|
-
}
|
|
115
|
-
// Callbacks
|
|
116
|
-
else if (this.isCallback(methodName)) {
|
|
117
|
-
this.parseCallback(call, methodName, model.callbacks, line);
|
|
118
|
-
}
|
|
119
|
-
// Scopes
|
|
120
|
-
else if (methodName === 'scope') {
|
|
121
|
-
this.parseScope(call, model.scopes, line);
|
|
122
|
-
}
|
|
123
|
-
// Concerns
|
|
124
|
-
else if (methodName === 'include') {
|
|
125
|
-
this.parseInclude(call, model.concerns);
|
|
126
|
-
}
|
|
127
|
-
// Enums
|
|
128
|
-
else if (methodName === 'enum') {
|
|
129
|
-
this.parseEnum(call, model.enums, line);
|
|
130
|
-
}
|
|
131
|
-
// Attributes
|
|
132
|
-
else if (methodName === 'attribute') {
|
|
133
|
-
this.parseAttribute(call, model.attributes, line);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Count methods
|
|
137
|
-
const methods = findNodes(classNode, 'method');
|
|
138
|
-
const singletonMethods = findNodes(classNode, 'singleton_method');
|
|
139
|
-
model.instanceMethodsCount = methods.length;
|
|
140
|
-
model.classMethodsCount = singletonMethods.length;
|
|
141
|
-
return model;
|
|
142
|
-
}
|
|
143
|
-
isActiveRecordModel(parentClass) {
|
|
144
|
-
const arBases = ['ApplicationRecord', 'ActiveRecord::Base', 'ActiveRecord'];
|
|
145
|
-
return arBases.some((base) => parentClass.includes(base));
|
|
146
|
-
}
|
|
147
|
-
parseTableName(classNode) {
|
|
148
|
-
const calls = findNodes(classNode, 'call');
|
|
149
|
-
for (const call of calls) {
|
|
150
|
-
const methodNode = call.childForFieldName('method');
|
|
151
|
-
if (methodNode?.text === 'table_name=') {
|
|
152
|
-
const args = this.getCallArguments(call);
|
|
153
|
-
if (args.length > 0) {
|
|
154
|
-
return args[0].text.replace(/^["']|["']$/g, '');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Also check for self.table_name =
|
|
159
|
-
const assignments = findNodes(classNode, 'assignment');
|
|
160
|
-
for (const assignment of assignments) {
|
|
161
|
-
const left = assignment.child(0);
|
|
162
|
-
if (left?.text?.includes('table_name')) {
|
|
163
|
-
const right = assignment.child(2);
|
|
164
|
-
if (right) {
|
|
165
|
-
return right.text.replace(/^["']|["']$/g, '');
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
parseAssociation(call, type, associations, line) {
|
|
172
|
-
const args = this.getCallArguments(call);
|
|
173
|
-
if (args.length === 0)
|
|
174
|
-
return;
|
|
175
|
-
const nameArg = args[0];
|
|
176
|
-
const assocName = nameArg.text.replace(/^:/, '');
|
|
177
|
-
const association = {
|
|
178
|
-
type,
|
|
179
|
-
name: assocName,
|
|
180
|
-
line,
|
|
181
|
-
};
|
|
182
|
-
// Parse options
|
|
183
|
-
for (const arg of args.slice(1)) {
|
|
184
|
-
if (arg.type === 'hash') {
|
|
185
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
186
|
-
for (const pair of pairs) {
|
|
187
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
188
|
-
const value = pair.child(2);
|
|
189
|
-
if (!key || !value)
|
|
190
|
-
continue;
|
|
191
|
-
switch (key) {
|
|
192
|
-
case 'class_name':
|
|
193
|
-
association.className = value.text.replace(/^["']|["']$/g, '');
|
|
194
|
-
break;
|
|
195
|
-
case 'foreign_key':
|
|
196
|
-
association.foreignKey = value.text.replace(/^["']|["']$/g, '').replace(/^:/, '');
|
|
197
|
-
break;
|
|
198
|
-
case 'through':
|
|
199
|
-
association.through = value.text.replace(/^:/, '');
|
|
200
|
-
break;
|
|
201
|
-
case 'polymorphic':
|
|
202
|
-
association.polymorphic = value.text === 'true';
|
|
203
|
-
break;
|
|
204
|
-
case 'dependent':
|
|
205
|
-
association.dependent = value.text.replace(/^:/, '');
|
|
206
|
-
break;
|
|
207
|
-
case 'optional':
|
|
208
|
-
association.optional = value.text === 'true';
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
associations.push(association);
|
|
215
|
-
}
|
|
216
|
-
parseValidation(call, methodName, validations, line) {
|
|
217
|
-
const args = this.getCallArguments(call);
|
|
218
|
-
if (args.length === 0)
|
|
219
|
-
return;
|
|
220
|
-
// Extract attributes being validated
|
|
221
|
-
const attributes = [];
|
|
222
|
-
const options = {};
|
|
223
|
-
let validationType = methodName;
|
|
224
|
-
for (const arg of args) {
|
|
225
|
-
if (arg.type === 'simple_symbol' || arg.type === 'symbol') {
|
|
226
|
-
attributes.push(arg.text.replace(/^:/, ''));
|
|
227
|
-
}
|
|
228
|
-
else if (arg.type === 'hash') {
|
|
229
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
230
|
-
for (const pair of pairs) {
|
|
231
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
232
|
-
const value = pair.child(2);
|
|
233
|
-
if (key && value) {
|
|
234
|
-
// Validation type is usually the key (presence, uniqueness, etc.)
|
|
235
|
-
if ([
|
|
236
|
-
'presence',
|
|
237
|
-
'uniqueness',
|
|
238
|
-
'numericality',
|
|
239
|
-
'length',
|
|
240
|
-
'format',
|
|
241
|
-
'inclusion',
|
|
242
|
-
'exclusion',
|
|
243
|
-
'acceptance',
|
|
244
|
-
'confirmation',
|
|
245
|
-
].includes(key)) {
|
|
246
|
-
validationType = key;
|
|
247
|
-
options[key] = value.text;
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
options[key] = value.text;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (attributes.length > 0 || methodName === 'validate') {
|
|
257
|
-
validations.push({
|
|
258
|
-
type: validationType,
|
|
259
|
-
attributes,
|
|
260
|
-
options: Object.keys(options).length > 0 ? options : undefined,
|
|
261
|
-
line,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
isCallback(methodName) {
|
|
266
|
-
const callbacks = [
|
|
267
|
-
'before_validation',
|
|
268
|
-
'after_validation',
|
|
269
|
-
'before_save',
|
|
270
|
-
'around_save',
|
|
271
|
-
'after_save',
|
|
272
|
-
'before_create',
|
|
273
|
-
'around_create',
|
|
274
|
-
'after_create',
|
|
275
|
-
'before_update',
|
|
276
|
-
'around_update',
|
|
277
|
-
'after_update',
|
|
278
|
-
'before_destroy',
|
|
279
|
-
'around_destroy',
|
|
280
|
-
'after_destroy',
|
|
281
|
-
'after_commit',
|
|
282
|
-
'after_rollback',
|
|
283
|
-
'after_initialize',
|
|
284
|
-
'after_find',
|
|
285
|
-
'after_touch',
|
|
286
|
-
];
|
|
287
|
-
return callbacks.includes(methodName);
|
|
288
|
-
}
|
|
289
|
-
parseCallback(call, type, callbacks, line) {
|
|
290
|
-
const args = this.getCallArguments(call);
|
|
291
|
-
if (args.length === 0)
|
|
292
|
-
return;
|
|
293
|
-
const methodArg = args[0];
|
|
294
|
-
const methodName = methodArg.text.replace(/^:/, '');
|
|
295
|
-
const callback = {
|
|
296
|
-
type,
|
|
297
|
-
method: methodName,
|
|
298
|
-
line,
|
|
299
|
-
};
|
|
300
|
-
// Check for conditions
|
|
301
|
-
for (const arg of args.slice(1)) {
|
|
302
|
-
if (arg.type === 'hash') {
|
|
303
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
304
|
-
for (const pair of pairs) {
|
|
305
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
306
|
-
const value = pair.child(2);
|
|
307
|
-
if (key && value && ['if', 'unless'].includes(key)) {
|
|
308
|
-
callback.conditions = `${key}: ${value.text}`;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
callbacks.push(callback);
|
|
314
|
-
}
|
|
315
|
-
parseScope(call, scopes, line) {
|
|
316
|
-
const args = this.getCallArguments(call);
|
|
317
|
-
if (args.length === 0)
|
|
318
|
-
return;
|
|
319
|
-
const nameArg = args[0];
|
|
320
|
-
const scopeName = nameArg.text.replace(/^:/, '');
|
|
321
|
-
const isLambda = args.length > 1 && (args[1].type === 'lambda' || args[1].text.includes('->'));
|
|
322
|
-
scopes.push({
|
|
323
|
-
name: scopeName,
|
|
324
|
-
lambda: isLambda,
|
|
325
|
-
line,
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
parseInclude(call, concerns) {
|
|
329
|
-
const args = this.getCallArguments(call);
|
|
330
|
-
for (const arg of args) {
|
|
331
|
-
if (arg.type === 'constant' || arg.type === 'scope_resolution') {
|
|
332
|
-
concerns.push(arg.text);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
parseEnum(call, enums, line) {
|
|
337
|
-
const args = this.getCallArguments(call);
|
|
338
|
-
if (args.length === 0)
|
|
339
|
-
return;
|
|
340
|
-
// enum status: { draft: 0, published: 1 }
|
|
341
|
-
// or enum :status, { draft: 0, published: 1 }
|
|
342
|
-
for (const arg of args) {
|
|
343
|
-
if (arg.type === 'hash') {
|
|
344
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
345
|
-
for (const pair of pairs) {
|
|
346
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
347
|
-
const value = pair.child(2);
|
|
348
|
-
if (key && value && value.type === 'hash') {
|
|
349
|
-
const enumValues = [];
|
|
350
|
-
const valuePairs = getChildrenByType(value, 'pair');
|
|
351
|
-
for (const vp of valuePairs) {
|
|
352
|
-
const vKey = vp.child(0)?.text?.replace(/^:/, '');
|
|
353
|
-
if (vKey)
|
|
354
|
-
enumValues.push(vKey);
|
|
355
|
-
}
|
|
356
|
-
enums.push({
|
|
357
|
-
name: key,
|
|
358
|
-
values: enumValues,
|
|
359
|
-
line,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
else if (key && value && value.type === 'array') {
|
|
363
|
-
// enum status: [:draft, :published]
|
|
364
|
-
const enumValues = [];
|
|
365
|
-
for (let i = 0; i < value.childCount; i++) {
|
|
366
|
-
const child = value.child(i);
|
|
367
|
-
if (child && child.type !== '[' && child.type !== ']' && child.type !== ',') {
|
|
368
|
-
enumValues.push(child.text.replace(/^:/, ''));
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
enums.push({
|
|
372
|
-
name: key,
|
|
373
|
-
values: enumValues,
|
|
374
|
-
line,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
parseAttribute(call, attributes, line) {
|
|
382
|
-
const args = this.getCallArguments(call);
|
|
383
|
-
if (args.length === 0)
|
|
384
|
-
return;
|
|
385
|
-
const nameArg = args[0];
|
|
386
|
-
const attrName = nameArg.text.replace(/^:/, '');
|
|
387
|
-
const attr = {
|
|
388
|
-
name: attrName,
|
|
389
|
-
line,
|
|
390
|
-
};
|
|
391
|
-
// Parse type and default
|
|
392
|
-
if (args.length > 1) {
|
|
393
|
-
const typeArg = args[1];
|
|
394
|
-
attr.type = typeArg.text.replace(/^:/, '');
|
|
395
|
-
}
|
|
396
|
-
for (const arg of args) {
|
|
397
|
-
if (arg.type === 'hash') {
|
|
398
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
399
|
-
for (const pair of pairs) {
|
|
400
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
401
|
-
const value = pair.child(2);
|
|
402
|
-
if (key === 'default' && value) {
|
|
403
|
-
attr.default = value.text;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
attributes.push(attr);
|
|
409
|
-
}
|
|
410
|
-
getCallArguments(call) {
|
|
411
|
-
const args = call.childForFieldName('arguments');
|
|
412
|
-
if (!args) {
|
|
413
|
-
const results = [];
|
|
414
|
-
for (let i = 0; i < call.childCount; i++) {
|
|
415
|
-
const child = call.child(i);
|
|
416
|
-
if (child && !['identifier', '(', ')', ',', 'call'].includes(child.type)) {
|
|
417
|
-
if (child !== call.childForFieldName('method') &&
|
|
418
|
-
child !== call.childForFieldName('receiver')) {
|
|
419
|
-
results.push(child);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return results;
|
|
424
|
-
}
|
|
425
|
-
const results = [];
|
|
426
|
-
for (let i = 0; i < args.childCount; i++) {
|
|
427
|
-
const child = args.child(i);
|
|
428
|
-
if (child && child.type !== '(' && child.type !== ')' && child.type !== ',') {
|
|
429
|
-
results.push(child);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return results;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
// Standalone execution for testing
|
|
436
|
-
async function main() {
|
|
437
|
-
const targetPath = process.argv[2] || process.cwd();
|
|
438
|
-
console.log(`Analyzing models in: ${targetPath}`);
|
|
439
|
-
const analyzer = new RailsModelAnalyzer(targetPath);
|
|
440
|
-
const result = await analyzer.analyze();
|
|
441
|
-
console.log('\n=== Rails Models Analysis ===\n');
|
|
442
|
-
console.log(`Total models: ${result.models.length}`);
|
|
443
|
-
console.log(`Total associations: ${result.totalAssociations}`);
|
|
444
|
-
console.log(`Total validations: ${result.totalValidations}`);
|
|
445
|
-
console.log(`Shared concerns: ${result.concerns.length}`);
|
|
446
|
-
console.log(`Namespaces: ${result.namespaces.join(', ') || '(none)'}`);
|
|
447
|
-
if (result.errors.length > 0) {
|
|
448
|
-
console.log(`\n--- Errors (${result.errors.length}) ---`);
|
|
449
|
-
for (const error of result.errors.slice(0, 5)) {
|
|
450
|
-
console.log(` ❌ ${error}`);
|
|
451
|
-
}
|
|
452
|
-
if (result.errors.length > 5) {
|
|
453
|
-
console.log(` ... and ${result.errors.length - 5} more`);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
console.log('\n--- Sample Models (first 15) ---');
|
|
457
|
-
for (const model of result.models.slice(0, 15)) {
|
|
458
|
-
console.log(`\n 📦 ${model.className} (${model.filePath})`);
|
|
459
|
-
console.log(` Parent: ${model.parentClass}`);
|
|
460
|
-
if (model.associations.length > 0) {
|
|
461
|
-
const assocs = model.associations.slice(0, 3).map((a) => `${a.type} :${a.name}`);
|
|
462
|
-
console.log(` Associations: ${assocs.join(', ')}${model.associations.length > 3 ? '...' : ''}`);
|
|
463
|
-
}
|
|
464
|
-
if (model.validations.length > 0) {
|
|
465
|
-
console.log(` Validations: ${model.validations.length}`);
|
|
466
|
-
}
|
|
467
|
-
if (model.scopes.length > 0) {
|
|
468
|
-
const scopeNames = model.scopes.slice(0, 3).map((s) => s.name);
|
|
469
|
-
console.log(` Scopes: ${scopeNames.join(', ')}${model.scopes.length > 3 ? '...' : ''}`);
|
|
470
|
-
}
|
|
471
|
-
if (model.enums.length > 0) {
|
|
472
|
-
const enumInfo = model.enums.map((e) => `${e.name}(${e.values.length})`);
|
|
473
|
-
console.log(` Enums: ${enumInfo.join(', ')}`);
|
|
474
|
-
}
|
|
475
|
-
if (model.concerns.length > 0) {
|
|
476
|
-
console.log(` Concerns: ${model.concerns.slice(0, 3).join(', ')}${model.concerns.length > 3 ? '...' : ''}`);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
// Association type summary
|
|
480
|
-
const allAssociations = result.models.flatMap((m) => m.associations);
|
|
481
|
-
const belongsTo = allAssociations.filter((a) => a.type === 'belongs_to').length;
|
|
482
|
-
const hasMany = allAssociations.filter((a) => a.type === 'has_many').length;
|
|
483
|
-
const hasOne = allAssociations.filter((a) => a.type === 'has_one').length;
|
|
484
|
-
console.log('\n--- Association Summary ---');
|
|
485
|
-
console.log(` belongs_to: ${belongsTo}`);
|
|
486
|
-
console.log(` has_many: ${hasMany}`);
|
|
487
|
-
console.log(` has_one: ${hasOne}`);
|
|
488
|
-
}
|
|
489
|
-
// Run if executed directly
|
|
490
|
-
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
491
|
-
if (isMainModule) {
|
|
492
|
-
main().catch(console.error);
|
|
493
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
export interface ReactComponentMapping {
|
|
2
|
-
name: string;
|
|
3
|
-
entryFile?: string;
|
|
4
|
-
sourceFile?: string;
|
|
5
|
-
importPath?: string;
|
|
6
|
-
ssr: boolean;
|
|
7
|
-
usedIn: ReactComponentUsage[];
|
|
8
|
-
}
|
|
9
|
-
export interface ReactComponentUsage {
|
|
10
|
-
viewPath: string;
|
|
11
|
-
controller: string;
|
|
12
|
-
action: string;
|
|
13
|
-
propsVar?: string;
|
|
14
|
-
line?: number;
|
|
15
|
-
pattern: ReactMountPattern;
|
|
16
|
-
}
|
|
17
|
-
export type ReactMountPattern = 'data-react-component' | 'render_react_component' | 'react_component' | 'redux_store' | 'stimulus-reflex' | 'turbo-frame-react';
|
|
18
|
-
export interface ReactAnalysisResult {
|
|
19
|
-
components: ReactComponentMapping[];
|
|
20
|
-
entryPoints: EntryPointInfo[];
|
|
21
|
-
detectedPaths: DetectedPaths;
|
|
22
|
-
summary: {
|
|
23
|
-
totalComponents: number;
|
|
24
|
-
totalEntryPoints: number;
|
|
25
|
-
ssrComponents: number;
|
|
26
|
-
clientComponents: number;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
export interface EntryPointInfo {
|
|
30
|
-
file: string;
|
|
31
|
-
fullPath: string;
|
|
32
|
-
componentName: string;
|
|
33
|
-
imports: string[];
|
|
34
|
-
selector?: string;
|
|
35
|
-
}
|
|
36
|
-
export interface DetectedPaths {
|
|
37
|
-
entryDirs: string[];
|
|
38
|
-
componentDirs: string[];
|
|
39
|
-
integrationPattern: 'react-rails' | 'react_on_rails' | 'webpacker' | 'vite' | 'custom' | 'unknown';
|
|
40
|
-
}
|
|
41
|
-
export declare function analyzeReactComponents(rootPath: string): Promise<ReactAnalysisResult>;
|