@wtdlee/repomap 0.3.0 → 0.3.2
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/{server/doc-server.js → chunk-4K4MGTPV.js} +41 -329
- package/dist/chunk-6F4PWJZI.js +0 -0
- package/dist/chunk-J2CM7T7U.js +1 -0
- package/dist/{generators/page-map-generator.js → chunk-MOEA75XK.js} +278 -503
- package/dist/{generators/rails-map-generator.js → chunk-SL2GMDBN.js} +48 -129
- package/dist/chunk-UJT5KTVK.js +36 -0
- package/dist/chunk-VV3A3UE3.js +1 -0
- package/dist/chunk-XWZH2RDG.js +19 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +29 -499
- package/dist/dataflow-analyzer-BfAiqVUp.d.ts +180 -0
- package/dist/env-detector-BIWJ7OYF.js +1 -0
- package/dist/generators/assets/common.css +564 -23
- package/dist/generators/index.d.ts +431 -3
- package/dist/generators/index.js +1 -3
- package/dist/index.d.ts +53 -10
- package/dist/index.js +1 -11
- package/dist/page-map-generator-XNZ4TDJT.js +1 -0
- package/dist/rails-TJCDGBBF.js +1 -0
- package/dist/rails-map-generator-JL5PKHYP.js +1 -0
- package/dist/server/index.d.ts +33 -1
- package/dist/server/index.js +1 -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,478 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rails Controller 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, getMethodName, getMethodParameters, getChildrenByType, } from './ruby-parser.js';
|
|
9
|
-
export class RailsControllerAnalyzer {
|
|
10
|
-
rootPath;
|
|
11
|
-
controllersDir;
|
|
12
|
-
controllers = [];
|
|
13
|
-
errors = [];
|
|
14
|
-
constructor(rootPath) {
|
|
15
|
-
this.rootPath = rootPath;
|
|
16
|
-
this.controllersDir = path.join(rootPath, 'app', 'controllers');
|
|
17
|
-
}
|
|
18
|
-
async analyze() {
|
|
19
|
-
if (!fs.existsSync(this.controllersDir)) {
|
|
20
|
-
return {
|
|
21
|
-
controllers: [],
|
|
22
|
-
totalActions: 0,
|
|
23
|
-
namespaces: [],
|
|
24
|
-
concerns: [],
|
|
25
|
-
errors: [`Controllers directory not found at ${this.controllersDir}`],
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
const controllerFiles = await glob('**/*_controller.rb', {
|
|
29
|
-
cwd: this.controllersDir,
|
|
30
|
-
ignore: ['concerns/**'],
|
|
31
|
-
});
|
|
32
|
-
for (const file of controllerFiles) {
|
|
33
|
-
const fullPath = path.join(this.controllersDir, file);
|
|
34
|
-
try {
|
|
35
|
-
const controller = await this.parseControllerFile(fullPath, file);
|
|
36
|
-
if (controller) {
|
|
37
|
-
this.controllers.push(controller);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
this.errors.push(`Error parsing ${file}: ${error}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const namespaces = [
|
|
45
|
-
...new Set(this.controllers.filter((c) => c.namespace).map((c) => c.namespace)),
|
|
46
|
-
];
|
|
47
|
-
const concerns = [...new Set(this.controllers.flatMap((c) => c.concerns))];
|
|
48
|
-
const totalActions = this.controllers.reduce((sum, c) => sum + c.actions.length, 0);
|
|
49
|
-
return {
|
|
50
|
-
controllers: this.controllers,
|
|
51
|
-
totalActions,
|
|
52
|
-
namespaces,
|
|
53
|
-
concerns,
|
|
54
|
-
errors: this.errors,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
async parseControllerFile(filePath, relativePath) {
|
|
58
|
-
const tree = await parseRubyFile(filePath);
|
|
59
|
-
const rootNode = tree.rootNode;
|
|
60
|
-
// Extract namespace from path
|
|
61
|
-
const pathParts = relativePath.replace(/_controller\.rb$/, '').split('/');
|
|
62
|
-
const namespace = pathParts.length > 1 ? pathParts.slice(0, -1).join('/') : undefined;
|
|
63
|
-
const controllerName = pathParts[pathParts.length - 1];
|
|
64
|
-
// Find class definition
|
|
65
|
-
const classNodes = findNodes(rootNode, 'class');
|
|
66
|
-
if (classNodes.length === 0)
|
|
67
|
-
return null;
|
|
68
|
-
const classNode = classNodes[0];
|
|
69
|
-
const className = getClassName(classNode);
|
|
70
|
-
const parentClass = getSuperclass(classNode);
|
|
71
|
-
if (!className)
|
|
72
|
-
return null;
|
|
73
|
-
const controller = {
|
|
74
|
-
name: controllerName,
|
|
75
|
-
filePath: relativePath,
|
|
76
|
-
className,
|
|
77
|
-
parentClass: parentClass || 'ApplicationController',
|
|
78
|
-
namespace,
|
|
79
|
-
actions: [],
|
|
80
|
-
beforeActions: [],
|
|
81
|
-
afterActions: [],
|
|
82
|
-
aroundActions: [],
|
|
83
|
-
skipBeforeActions: [],
|
|
84
|
-
concerns: [],
|
|
85
|
-
helpers: [],
|
|
86
|
-
rescueFrom: [],
|
|
87
|
-
line: classNode.startPosition.row + 1,
|
|
88
|
-
};
|
|
89
|
-
// Find all method calls for filters, concerns, etc.
|
|
90
|
-
const calls = findNodes(classNode, 'call');
|
|
91
|
-
for (const call of calls) {
|
|
92
|
-
const methodNode = call.childForFieldName('method');
|
|
93
|
-
if (!methodNode)
|
|
94
|
-
continue;
|
|
95
|
-
const methodName = methodNode.text;
|
|
96
|
-
const line = call.startPosition.row + 1;
|
|
97
|
-
switch (methodName) {
|
|
98
|
-
case 'before_action':
|
|
99
|
-
case 'before_filter': // Legacy
|
|
100
|
-
this.parseFilter(call, controller.beforeActions, line);
|
|
101
|
-
break;
|
|
102
|
-
case 'after_action':
|
|
103
|
-
case 'after_filter':
|
|
104
|
-
this.parseFilter(call, controller.afterActions, line);
|
|
105
|
-
break;
|
|
106
|
-
case 'around_action':
|
|
107
|
-
case 'around_filter':
|
|
108
|
-
this.parseFilter(call, controller.aroundActions, line);
|
|
109
|
-
break;
|
|
110
|
-
case 'skip_before_action':
|
|
111
|
-
case 'skip_before_filter':
|
|
112
|
-
this.parseFilter(call, controller.skipBeforeActions, line);
|
|
113
|
-
break;
|
|
114
|
-
case 'include':
|
|
115
|
-
this.parseInclude(call, controller.concerns);
|
|
116
|
-
break;
|
|
117
|
-
case 'helper':
|
|
118
|
-
this.parseHelper(call, controller.helpers);
|
|
119
|
-
break;
|
|
120
|
-
case 'layout':
|
|
121
|
-
controller.layoutInfo = this.parseLayout(call);
|
|
122
|
-
break;
|
|
123
|
-
case 'rescue_from':
|
|
124
|
-
this.parseRescueFrom(call, controller.rescueFrom, line);
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Find all method definitions
|
|
129
|
-
const _methods = findNodes(classNode, 'method');
|
|
130
|
-
let currentVisibility = 'public';
|
|
131
|
-
// Track visibility changes through identifiers
|
|
132
|
-
const bodyStatement = classNode.childForFieldName('body');
|
|
133
|
-
if (bodyStatement) {
|
|
134
|
-
for (let i = 0; i < bodyStatement.childCount; i++) {
|
|
135
|
-
const child = bodyStatement.child(i);
|
|
136
|
-
if (!child)
|
|
137
|
-
continue;
|
|
138
|
-
if (child.type === 'identifier') {
|
|
139
|
-
const text = child.text;
|
|
140
|
-
if (text === 'private')
|
|
141
|
-
currentVisibility = 'private';
|
|
142
|
-
else if (text === 'protected')
|
|
143
|
-
currentVisibility = 'protected';
|
|
144
|
-
else if (text === 'public')
|
|
145
|
-
currentVisibility = 'public';
|
|
146
|
-
}
|
|
147
|
-
else if (child.type === 'method') {
|
|
148
|
-
const action = this.parseMethod(child, currentVisibility);
|
|
149
|
-
if (action) {
|
|
150
|
-
controller.actions.push(action);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return controller;
|
|
156
|
-
}
|
|
157
|
-
parseFilter(call, filters, line) {
|
|
158
|
-
const args = this.getCallArguments(call);
|
|
159
|
-
if (args.length === 0)
|
|
160
|
-
return;
|
|
161
|
-
// First argument is the filter name (symbol)
|
|
162
|
-
const nameArg = args[0];
|
|
163
|
-
const filterName = nameArg.text.replace(/^:/, '');
|
|
164
|
-
const filter = {
|
|
165
|
-
name: filterName,
|
|
166
|
-
line,
|
|
167
|
-
};
|
|
168
|
-
// Parse options
|
|
169
|
-
for (const arg of args.slice(1)) {
|
|
170
|
-
if (arg.type === 'hash') {
|
|
171
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
172
|
-
for (const pair of pairs) {
|
|
173
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
174
|
-
const value = pair.child(2);
|
|
175
|
-
if (!key || !value)
|
|
176
|
-
continue;
|
|
177
|
-
switch (key) {
|
|
178
|
-
case 'only':
|
|
179
|
-
filter.only = this.extractArrayValues(value);
|
|
180
|
-
break;
|
|
181
|
-
case 'except':
|
|
182
|
-
filter.except = this.extractArrayValues(value);
|
|
183
|
-
break;
|
|
184
|
-
case 'if':
|
|
185
|
-
filter.if = value.text;
|
|
186
|
-
break;
|
|
187
|
-
case 'unless':
|
|
188
|
-
filter.unless = value.text;
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
filters.push(filter);
|
|
195
|
-
}
|
|
196
|
-
parseInclude(call, concerns) {
|
|
197
|
-
const args = this.getCallArguments(call);
|
|
198
|
-
for (const arg of args) {
|
|
199
|
-
if (arg.type === 'constant' || arg.type === 'scope_resolution') {
|
|
200
|
-
concerns.push(arg.text);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
parseHelper(call, helpers) {
|
|
205
|
-
const args = this.getCallArguments(call);
|
|
206
|
-
for (const arg of args) {
|
|
207
|
-
const value = arg.text.replace(/^:/, '');
|
|
208
|
-
helpers.push(value);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
parseLayout(call) {
|
|
212
|
-
const args = this.getCallArguments(call);
|
|
213
|
-
if (args.length === 0)
|
|
214
|
-
return undefined;
|
|
215
|
-
const nameArg = args[0];
|
|
216
|
-
let layoutName = nameArg.text.replace(/^["']|["']$/g, '');
|
|
217
|
-
// Handle symbol
|
|
218
|
-
if (layoutName.startsWith(':')) {
|
|
219
|
-
layoutName = layoutName.substring(1);
|
|
220
|
-
}
|
|
221
|
-
// Handle proc/lambda (return false or dynamic)
|
|
222
|
-
if (nameArg.type === 'lambda' || nameArg.type === 'proc') {
|
|
223
|
-
layoutName = '(dynamic)';
|
|
224
|
-
}
|
|
225
|
-
const layout = { name: layoutName };
|
|
226
|
-
// Parse conditions
|
|
227
|
-
for (const arg of args.slice(1)) {
|
|
228
|
-
if (arg.type === 'hash') {
|
|
229
|
-
layout.conditions = arg.text;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return layout;
|
|
233
|
-
}
|
|
234
|
-
parseRescueFrom(call, rescues, line) {
|
|
235
|
-
const args = this.getCallArguments(call);
|
|
236
|
-
if (args.length === 0)
|
|
237
|
-
return;
|
|
238
|
-
const exception = args[0].text;
|
|
239
|
-
let handler = 'unknown';
|
|
240
|
-
// Look for with: option
|
|
241
|
-
for (const arg of args.slice(1)) {
|
|
242
|
-
if (arg.type === 'hash' || arg.type === 'pair') {
|
|
243
|
-
const pairs = arg.type === 'hash' ? getChildrenByType(arg, 'pair') : [arg];
|
|
244
|
-
for (const pair of pairs) {
|
|
245
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
246
|
-
const value = pair.child(2);
|
|
247
|
-
if (key === 'with' && value) {
|
|
248
|
-
handler = value.text.replace(/^:/, '');
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
rescues.push({ exception, handler, line });
|
|
254
|
-
}
|
|
255
|
-
parseMethod(methodNode, visibility) {
|
|
256
|
-
const name = getMethodName(methodNode);
|
|
257
|
-
if (!name)
|
|
258
|
-
return null;
|
|
259
|
-
// Skip singleton methods (def self.xxx)
|
|
260
|
-
if (methodNode.text.includes('def self.'))
|
|
261
|
-
return null;
|
|
262
|
-
const action = {
|
|
263
|
-
name,
|
|
264
|
-
line: methodNode.startPosition.row + 1,
|
|
265
|
-
visibility,
|
|
266
|
-
parameters: getMethodParameters(methodNode),
|
|
267
|
-
servicesCalled: [],
|
|
268
|
-
modelsCalled: [],
|
|
269
|
-
methodCalls: [],
|
|
270
|
-
instanceVarAssignments: [],
|
|
271
|
-
};
|
|
272
|
-
// Analyze method body
|
|
273
|
-
const bodyContent = methodNode.text;
|
|
274
|
-
// Extract instance variable assignments: @var = value
|
|
275
|
-
const ivarRegex = /@([a-z_][a-z0-9_]*)\s*=\s*([^\n]+)/gi;
|
|
276
|
-
let ivarMatch;
|
|
277
|
-
while ((ivarMatch = ivarRegex.exec(bodyContent)) !== null) {
|
|
278
|
-
const varName = ivarMatch[1];
|
|
279
|
-
const assignedValue = ivarMatch[2].trim().slice(0, 100); // Truncate long values
|
|
280
|
-
// Try to detect model type from assignment
|
|
281
|
-
let assignedType;
|
|
282
|
-
// Pattern: Model.find/where/new/create/etc
|
|
283
|
-
const modelMatch = assignedValue.match(/^([A-Z][a-zA-Z0-9]+)\.(find|find_by|find_by!|where|all|first|last|new|create|create!|build)/);
|
|
284
|
-
if (modelMatch) {
|
|
285
|
-
assignedType = modelMatch[1];
|
|
286
|
-
}
|
|
287
|
-
// Pattern: @parent.association (e.g., @company.users)
|
|
288
|
-
const assocMatch = assignedValue.match(/^@([a-z_]+)\.([a-z_]+)/);
|
|
289
|
-
if (assocMatch && !assignedType) {
|
|
290
|
-
assignedType = `${assocMatch[1]}.${assocMatch[2]}`;
|
|
291
|
-
}
|
|
292
|
-
// Pattern: current_user, current_company, etc.
|
|
293
|
-
const currentMatch = assignedValue.match(/^current_([a-z_]+)/);
|
|
294
|
-
if (currentMatch && !assignedType) {
|
|
295
|
-
assignedType = currentMatch[1].charAt(0).toUpperCase() + currentMatch[1].slice(1);
|
|
296
|
-
}
|
|
297
|
-
// Pattern: SomeService.call (service result)
|
|
298
|
-
const serviceMatch = assignedValue.match(/^([A-Z][a-zA-Z0-9]+Service)\.(call|new|perform)/);
|
|
299
|
-
if (serviceMatch && !assignedType) {
|
|
300
|
-
assignedType = `Service:${serviceMatch[1]}`;
|
|
301
|
-
}
|
|
302
|
-
if (action.instanceVarAssignments) {
|
|
303
|
-
action.instanceVarAssignments.push({
|
|
304
|
-
name: varName,
|
|
305
|
-
assignedType,
|
|
306
|
-
assignedValue: assignedValue.length > 60 ? assignedValue.slice(0, 57) + '...' : assignedValue,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
// Check render types
|
|
311
|
-
if (bodyContent.includes('render json:') || bodyContent.includes('render :json')) {
|
|
312
|
-
action.rendersJson = true;
|
|
313
|
-
}
|
|
314
|
-
if (bodyContent.includes('render') && !action.rendersJson) {
|
|
315
|
-
action.rendersHtml = true;
|
|
316
|
-
}
|
|
317
|
-
// Extract redirects
|
|
318
|
-
const redirectMatch = bodyContent.match(/redirect_to\s+([^,\n]+)/);
|
|
319
|
-
if (redirectMatch) {
|
|
320
|
-
action.redirectsTo = redirectMatch[1].trim();
|
|
321
|
-
}
|
|
322
|
-
// Check respond_to formats
|
|
323
|
-
if (bodyContent.includes('respond_to')) {
|
|
324
|
-
const formats = [];
|
|
325
|
-
if (bodyContent.includes('format.html'))
|
|
326
|
-
formats.push('html');
|
|
327
|
-
if (bodyContent.includes('format.json'))
|
|
328
|
-
formats.push('json');
|
|
329
|
-
if (bodyContent.includes('format.xml'))
|
|
330
|
-
formats.push('xml');
|
|
331
|
-
if (bodyContent.includes('format.js'))
|
|
332
|
-
formats.push('js');
|
|
333
|
-
if (bodyContent.includes('format.csv'))
|
|
334
|
-
formats.push('csv');
|
|
335
|
-
if (bodyContent.includes('format.pdf'))
|
|
336
|
-
formats.push('pdf');
|
|
337
|
-
if (formats.length > 0) {
|
|
338
|
-
action.respondsTo = formats;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// Find service calls
|
|
342
|
-
const serviceCalls = findNodes(methodNode, 'call');
|
|
343
|
-
for (const call of serviceCalls) {
|
|
344
|
-
const receiver = call.childForFieldName('receiver');
|
|
345
|
-
const method = call.childForFieldName('method');
|
|
346
|
-
if (receiver && method) {
|
|
347
|
-
const receiverText = receiver.text;
|
|
348
|
-
const methodText = method.text;
|
|
349
|
-
// Service pattern: SomeService.call/new/perform
|
|
350
|
-
if (receiverText.endsWith('Service') &&
|
|
351
|
-
['call', 'new', 'perform', 'execute'].includes(methodText)) {
|
|
352
|
-
if (!action.servicesCalled.includes(receiverText)) {
|
|
353
|
-
action.servicesCalled.push(receiverText);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// Model pattern: User.find/where/create etc.
|
|
357
|
-
const arMethods = [
|
|
358
|
-
'find',
|
|
359
|
-
'find_by',
|
|
360
|
-
'find_by!',
|
|
361
|
-
'where',
|
|
362
|
-
'all',
|
|
363
|
-
'first',
|
|
364
|
-
'last',
|
|
365
|
-
'create',
|
|
366
|
-
'create!',
|
|
367
|
-
'new',
|
|
368
|
-
'update',
|
|
369
|
-
'update!',
|
|
370
|
-
'destroy',
|
|
371
|
-
'delete',
|
|
372
|
-
];
|
|
373
|
-
if (/^[A-Z][a-zA-Z]+$/.test(receiverText) && arMethods.includes(methodText)) {
|
|
374
|
-
if (!['Rails', 'ActiveRecord', 'ActionController', 'ApplicationRecord'].includes(receiverText)) {
|
|
375
|
-
if (!action.modelsCalled.includes(receiverText)) {
|
|
376
|
-
action.modelsCalled.push(receiverText);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
// Track all method calls
|
|
381
|
-
action.methodCalls.push(`${receiverText}.${methodText}`);
|
|
382
|
-
}
|
|
383
|
-
else if (method && !receiver) {
|
|
384
|
-
// Method call without receiver
|
|
385
|
-
action.methodCalls.push(method.text);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return action;
|
|
389
|
-
}
|
|
390
|
-
getCallArguments(call) {
|
|
391
|
-
const args = call.childForFieldName('arguments');
|
|
392
|
-
if (!args) {
|
|
393
|
-
// Arguments might be direct children without parentheses
|
|
394
|
-
const results = [];
|
|
395
|
-
for (let i = 0; i < call.childCount; i++) {
|
|
396
|
-
const child = call.child(i);
|
|
397
|
-
if (child && !['identifier', '(', ')', ',', 'call'].includes(child.type)) {
|
|
398
|
-
// Skip the method name and receiver
|
|
399
|
-
if (child !== call.childForFieldName('method') &&
|
|
400
|
-
child !== call.childForFieldName('receiver')) {
|
|
401
|
-
results.push(child);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return results;
|
|
406
|
-
}
|
|
407
|
-
const results = [];
|
|
408
|
-
for (let i = 0; i < args.childCount; i++) {
|
|
409
|
-
const child = args.child(i);
|
|
410
|
-
if (child && child.type !== '(' && child.type !== ')' && child.type !== ',') {
|
|
411
|
-
results.push(child);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return results;
|
|
415
|
-
}
|
|
416
|
-
extractArrayValues(node) {
|
|
417
|
-
const values = [];
|
|
418
|
-
if (node.type === 'array') {
|
|
419
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
420
|
-
const child = node.child(i);
|
|
421
|
-
if (child && child.type !== '[' && child.type !== ']' && child.type !== ',') {
|
|
422
|
-
values.push(child.text.replace(/^:/, ''));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
values.push(node.text.replace(/^:/, ''));
|
|
428
|
-
}
|
|
429
|
-
return values;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
// Standalone execution for testing
|
|
433
|
-
async function main() {
|
|
434
|
-
const targetPath = process.argv[2] || process.cwd();
|
|
435
|
-
console.log(`Analyzing controllers in: ${targetPath}`);
|
|
436
|
-
const analyzer = new RailsControllerAnalyzer(targetPath);
|
|
437
|
-
const result = await analyzer.analyze();
|
|
438
|
-
console.log('\n=== Rails Controllers Analysis ===\n');
|
|
439
|
-
console.log(`Total controllers: ${result.controllers.length}`);
|
|
440
|
-
console.log(`Total actions: ${result.totalActions}`);
|
|
441
|
-
console.log(`Namespaces: ${result.namespaces.join(', ') || '(none)'}`);
|
|
442
|
-
console.log(`Shared concerns: ${result.concerns.length}`);
|
|
443
|
-
if (result.errors.length > 0) {
|
|
444
|
-
console.log(`\n--- Errors (${result.errors.length}) ---`);
|
|
445
|
-
for (const error of result.errors.slice(0, 5)) {
|
|
446
|
-
console.log(` ❌ ${error}`);
|
|
447
|
-
}
|
|
448
|
-
if (result.errors.length > 5) {
|
|
449
|
-
console.log(` ... and ${result.errors.length - 5} more`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
console.log('\n--- Sample Controllers (first 10) ---');
|
|
453
|
-
for (const controller of result.controllers.slice(0, 10)) {
|
|
454
|
-
console.log(`\n 📁 ${controller.className} (${controller.filePath})`);
|
|
455
|
-
console.log(` Parent: ${controller.parentClass}`);
|
|
456
|
-
console.log(` Actions (${controller.actions.length}): ${controller.actions
|
|
457
|
-
.map((a) => a.name)
|
|
458
|
-
.slice(0, 5)
|
|
459
|
-
.join(', ')}${controller.actions.length > 5 ? '...' : ''}`);
|
|
460
|
-
if (controller.beforeActions.length > 0) {
|
|
461
|
-
console.log(` Before: ${controller.beforeActions.map((f) => f.name).join(', ')}`);
|
|
462
|
-
}
|
|
463
|
-
if (controller.concerns.length > 0) {
|
|
464
|
-
console.log(` Concerns: ${controller.concerns.join(', ')}`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
// Summary of actions by visibility
|
|
468
|
-
const publicActions = result.controllers.flatMap((c) => c.actions.filter((a) => a.visibility === 'public'));
|
|
469
|
-
const privateActions = result.controllers.flatMap((c) => c.actions.filter((a) => a.visibility === 'private'));
|
|
470
|
-
console.log('\n--- Action Visibility Summary ---');
|
|
471
|
-
console.log(` Public: ${publicActions.length}`);
|
|
472
|
-
console.log(` Private: ${privateActions.length}`);
|
|
473
|
-
}
|
|
474
|
-
// Run if executed directly
|
|
475
|
-
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
476
|
-
if (isMainModule) {
|
|
477
|
-
main().catch(console.error);
|
|
478
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rails gRPC Service Analyzer using tree-sitter
|
|
3
|
-
* tree-sitterを使用してgRPCサービスを解析する
|
|
4
|
-
*/
|
|
5
|
-
export interface GrpcServiceInfo {
|
|
6
|
-
name: string;
|
|
7
|
-
filePath: string;
|
|
8
|
-
className: string;
|
|
9
|
-
parentClass: string;
|
|
10
|
-
namespace?: string;
|
|
11
|
-
protoService?: string;
|
|
12
|
-
rpcs: RpcMethodInfo[];
|
|
13
|
-
policies: string[];
|
|
14
|
-
serializers: string[];
|
|
15
|
-
concerns: string[];
|
|
16
|
-
line: number;
|
|
17
|
-
}
|
|
18
|
-
export interface RpcMethodInfo {
|
|
19
|
-
name: string;
|
|
20
|
-
requestType?: string;
|
|
21
|
-
responseType?: string;
|
|
22
|
-
streaming?: 'none' | 'server' | 'client' | 'bidirectional';
|
|
23
|
-
policyMethod?: string;
|
|
24
|
-
modelsUsed: string[];
|
|
25
|
-
servicesUsed: string[];
|
|
26
|
-
line: number;
|
|
27
|
-
}
|
|
28
|
-
export interface RailsGrpcResult {
|
|
29
|
-
services: GrpcServiceInfo[];
|
|
30
|
-
totalRpcs: number;
|
|
31
|
-
namespaces: string[];
|
|
32
|
-
errors: string[];
|
|
33
|
-
}
|
|
34
|
-
export declare class RailsGrpcAnalyzer {
|
|
35
|
-
private rootPath;
|
|
36
|
-
private grpcDir;
|
|
37
|
-
private services;
|
|
38
|
-
private errors;
|
|
39
|
-
constructor(rootPath: string);
|
|
40
|
-
analyze(): Promise<RailsGrpcResult>;
|
|
41
|
-
private parseServiceFile;
|
|
42
|
-
private parseRpcMethod;
|
|
43
|
-
private getCallArguments;
|
|
44
|
-
}
|