@wtdlee/repomap 0.2.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 -290
- 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 +5 -3
- 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
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rails Routes Analyzer using tree-sitter
|
|
3
|
-
* tree-sitterを使用してroutes.rbを解析する
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { parseRubyFile, findNodes, getChildByType, getChildrenByType, getCallArguments, } from './ruby-parser.js';
|
|
8
|
-
export class RailsRoutesAnalyzer {
|
|
9
|
-
rootPath;
|
|
10
|
-
routesDir;
|
|
11
|
-
routes = [];
|
|
12
|
-
namespaces = [];
|
|
13
|
-
resources = [];
|
|
14
|
-
mountedEngines = [];
|
|
15
|
-
drawnFiles = [];
|
|
16
|
-
errors = [];
|
|
17
|
-
constructor(rootPath) {
|
|
18
|
-
this.rootPath = rootPath;
|
|
19
|
-
this.routesDir = path.join(rootPath, 'config', 'routes');
|
|
20
|
-
}
|
|
21
|
-
async analyze() {
|
|
22
|
-
const mainRoutesFile = path.join(this.rootPath, 'config', 'routes.rb');
|
|
23
|
-
if (!fs.existsSync(mainRoutesFile)) {
|
|
24
|
-
return {
|
|
25
|
-
routes: [],
|
|
26
|
-
namespaces: [],
|
|
27
|
-
resources: [],
|
|
28
|
-
mountedEngines: [],
|
|
29
|
-
drawnFiles: [],
|
|
30
|
-
errors: [`routes.rb not found at ${mainRoutesFile}`],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
await this.parseRoutesFile(mainRoutesFile, []);
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
this.errors.push(`Error parsing ${mainRoutesFile}: ${error}`);
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
routes: this.routes,
|
|
41
|
-
namespaces: [...new Set(this.namespaces)],
|
|
42
|
-
resources: this.resources,
|
|
43
|
-
mountedEngines: this.mountedEngines,
|
|
44
|
-
drawnFiles: this.drawnFiles,
|
|
45
|
-
errors: this.errors,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
async parseRoutesFile(filePath, currentNamespaces) {
|
|
49
|
-
const tree = await parseRubyFile(filePath);
|
|
50
|
-
const rootNode = tree.rootNode;
|
|
51
|
-
// Find all method calls
|
|
52
|
-
const calls = findNodes(rootNode, 'call');
|
|
53
|
-
for (const call of calls) {
|
|
54
|
-
const methodNode = call.childForFieldName('method');
|
|
55
|
-
if (!methodNode)
|
|
56
|
-
continue;
|
|
57
|
-
const methodName = methodNode.text;
|
|
58
|
-
const line = call.startPosition.row + 1;
|
|
59
|
-
switch (methodName) {
|
|
60
|
-
case 'get':
|
|
61
|
-
case 'post':
|
|
62
|
-
case 'put':
|
|
63
|
-
case 'patch':
|
|
64
|
-
case 'delete':
|
|
65
|
-
case 'match':
|
|
66
|
-
this.parseHttpRoute(call, methodName, currentNamespaces, line);
|
|
67
|
-
break;
|
|
68
|
-
case 'resources':
|
|
69
|
-
case 'resource':
|
|
70
|
-
await this.parseResources(call, currentNamespaces, line, methodName === 'resource');
|
|
71
|
-
break;
|
|
72
|
-
case 'namespace':
|
|
73
|
-
await this.parseNamespace(call, currentNamespaces, filePath);
|
|
74
|
-
break;
|
|
75
|
-
case 'mount':
|
|
76
|
-
this.parseMount(call, line);
|
|
77
|
-
break;
|
|
78
|
-
case 'draw':
|
|
79
|
-
await this.parseDraw(call, currentNamespaces);
|
|
80
|
-
break;
|
|
81
|
-
case 'devise_for':
|
|
82
|
-
this.parseDeviseFor(call, currentNamespaces, line);
|
|
83
|
-
break;
|
|
84
|
-
case 'root':
|
|
85
|
-
this.parseRoot(call, currentNamespaces, line);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
parseHttpRoute(call, method, namespaces, line) {
|
|
91
|
-
const args = getCallArguments(call);
|
|
92
|
-
if (args.length === 0)
|
|
93
|
-
return;
|
|
94
|
-
// First argument is the path
|
|
95
|
-
const pathArg = args[0];
|
|
96
|
-
const routePath = this.extractStringValue(pathArg);
|
|
97
|
-
if (!routePath)
|
|
98
|
-
return;
|
|
99
|
-
// Find controller#action from 'to:' option or second argument
|
|
100
|
-
let controller = '';
|
|
101
|
-
let action = '';
|
|
102
|
-
// Look for hash/pair arguments
|
|
103
|
-
for (const arg of args) {
|
|
104
|
-
if (arg.type === 'hash' || arg.type === 'pair') {
|
|
105
|
-
const pairs = arg.type === 'hash' ? getChildrenByType(arg, 'pair') : [arg];
|
|
106
|
-
for (const pair of pairs) {
|
|
107
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
108
|
-
const value = pair.child(2);
|
|
109
|
-
if (key === 'to' && value) {
|
|
110
|
-
const toValue = this.extractStringValue(value);
|
|
111
|
-
if (toValue && toValue.includes('#')) {
|
|
112
|
-
[controller, action] = toValue.split('#');
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else if (arg.type === 'string' || arg.type === 'string_content') {
|
|
118
|
-
const strValue = this.extractStringValue(arg);
|
|
119
|
-
if (strValue && strValue.includes('#') && !controller) {
|
|
120
|
-
[controller, action] = strValue.split('#');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
// If no explicit controller#action, try to infer from path
|
|
125
|
-
if (!controller && !action) {
|
|
126
|
-
const pathParts = routePath.replace(/^\//, '').split('/');
|
|
127
|
-
controller = pathParts[0] || '';
|
|
128
|
-
action = pathParts[1] || 'index';
|
|
129
|
-
}
|
|
130
|
-
// Apply namespace prefix
|
|
131
|
-
if (namespaces.length > 0 && controller && !controller.includes('/')) {
|
|
132
|
-
controller = `${namespaces.join('/')}/${controller}`;
|
|
133
|
-
}
|
|
134
|
-
// Build full path with namespace
|
|
135
|
-
const fullPath = this.buildPath(namespaces, routePath);
|
|
136
|
-
this.routes.push({
|
|
137
|
-
method: method === 'match' ? 'ALL' : method.toUpperCase(),
|
|
138
|
-
path: fullPath,
|
|
139
|
-
controller,
|
|
140
|
-
action,
|
|
141
|
-
namespace: namespaces.join('/') || undefined,
|
|
142
|
-
line,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
async parseResources(call, namespaces, line, singular) {
|
|
146
|
-
const args = getCallArguments(call);
|
|
147
|
-
if (args.length === 0)
|
|
148
|
-
return;
|
|
149
|
-
// First argument is the resource name (symbol)
|
|
150
|
-
const nameArg = args[0];
|
|
151
|
-
const resourceName = nameArg.text.replace(/^:/, '');
|
|
152
|
-
const resource = {
|
|
153
|
-
name: resourceName,
|
|
154
|
-
controller: namespaces.length > 0 ? `${namespaces.join('/')}/${resourceName}` : resourceName,
|
|
155
|
-
nested: [],
|
|
156
|
-
memberRoutes: [],
|
|
157
|
-
collectionRoutes: [],
|
|
158
|
-
line,
|
|
159
|
-
};
|
|
160
|
-
// Parse options (only:, except:, etc.)
|
|
161
|
-
for (const arg of args) {
|
|
162
|
-
if (arg.type === 'hash') {
|
|
163
|
-
const pairs = getChildrenByType(arg, 'pair');
|
|
164
|
-
for (const pair of pairs) {
|
|
165
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
166
|
-
const value = pair.child(2);
|
|
167
|
-
if (key === 'only' && value) {
|
|
168
|
-
resource.only = this.extractArrayValues(value);
|
|
169
|
-
}
|
|
170
|
-
else if (key === 'except' && value) {
|
|
171
|
-
resource.except = this.extractArrayValues(value);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Generate RESTful routes
|
|
177
|
-
this.generateResourceRoutes(resource, namespaces, singular);
|
|
178
|
-
this.resources.push(resource);
|
|
179
|
-
// Parse nested block if exists
|
|
180
|
-
const block = call.childForFieldName('block');
|
|
181
|
-
if (block) {
|
|
182
|
-
// Look for member/collection blocks and nested resources
|
|
183
|
-
const nestedCalls = findNodes(block, 'call');
|
|
184
|
-
for (const nestedCall of nestedCalls) {
|
|
185
|
-
const nestedMethod = nestedCall.childForFieldName('method')?.text;
|
|
186
|
-
if (nestedMethod === 'member') {
|
|
187
|
-
// Parse member routes
|
|
188
|
-
const memberBlock = nestedCall.childForFieldName('block');
|
|
189
|
-
if (memberBlock) {
|
|
190
|
-
this.parseMemberCollectionRoutes(memberBlock, resource, namespaces, 'member');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else if (nestedMethod === 'collection') {
|
|
194
|
-
// Parse collection routes
|
|
195
|
-
const collectionBlock = nestedCall.childForFieldName('block');
|
|
196
|
-
if (collectionBlock) {
|
|
197
|
-
this.parseMemberCollectionRoutes(collectionBlock, resource, namespaces, 'collection');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
parseMemberCollectionRoutes(block, resource, namespaces, type) {
|
|
204
|
-
const calls = findNodes(block, 'call');
|
|
205
|
-
for (const call of calls) {
|
|
206
|
-
const methodNode = call.childForFieldName('method');
|
|
207
|
-
if (!methodNode)
|
|
208
|
-
continue;
|
|
209
|
-
const method = methodNode.text;
|
|
210
|
-
if (!['get', 'post', 'put', 'patch', 'delete'].includes(method))
|
|
211
|
-
continue;
|
|
212
|
-
const args = getCallArguments(call);
|
|
213
|
-
if (args.length === 0)
|
|
214
|
-
continue;
|
|
215
|
-
const actionName = args[0].text.replace(/^:/, '');
|
|
216
|
-
const basePath = type === 'member'
|
|
217
|
-
? `/${resource.name}/:id/${actionName}`
|
|
218
|
-
: `/${resource.name}/${actionName}`;
|
|
219
|
-
const route = {
|
|
220
|
-
method: method.toUpperCase(),
|
|
221
|
-
path: this.buildPath(namespaces, basePath),
|
|
222
|
-
controller: resource.controller,
|
|
223
|
-
action: actionName,
|
|
224
|
-
namespace: namespaces.join('/') || undefined,
|
|
225
|
-
line: call.startPosition.row + 1,
|
|
226
|
-
};
|
|
227
|
-
if (type === 'member') {
|
|
228
|
-
resource.memberRoutes.push(route);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
resource.collectionRoutes.push(route);
|
|
232
|
-
}
|
|
233
|
-
this.routes.push(route);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async parseNamespace(call, currentNamespaces, _currentFile) {
|
|
237
|
-
const args = getCallArguments(call);
|
|
238
|
-
if (args.length === 0)
|
|
239
|
-
return;
|
|
240
|
-
const nsName = args[0].text.replace(/^:/, '');
|
|
241
|
-
this.namespaces.push(nsName);
|
|
242
|
-
const newNamespaces = [...currentNamespaces, nsName];
|
|
243
|
-
// Parse the namespace block
|
|
244
|
-
const block = call.childForFieldName('block');
|
|
245
|
-
if (block) {
|
|
246
|
-
// Look for draw calls or nested route definitions
|
|
247
|
-
const nestedCalls = findNodes(block, 'call');
|
|
248
|
-
for (const nestedCall of nestedCalls) {
|
|
249
|
-
const methodNode = nestedCall.childForFieldName('method');
|
|
250
|
-
if (!methodNode)
|
|
251
|
-
continue;
|
|
252
|
-
const methodName = methodNode.text;
|
|
253
|
-
const line = nestedCall.startPosition.row + 1;
|
|
254
|
-
switch (methodName) {
|
|
255
|
-
case 'get':
|
|
256
|
-
case 'post':
|
|
257
|
-
case 'put':
|
|
258
|
-
case 'patch':
|
|
259
|
-
case 'delete':
|
|
260
|
-
case 'match':
|
|
261
|
-
this.parseHttpRoute(nestedCall, methodName, newNamespaces, line);
|
|
262
|
-
break;
|
|
263
|
-
case 'resources':
|
|
264
|
-
case 'resource':
|
|
265
|
-
await this.parseResources(nestedCall, newNamespaces, line, methodName === 'resource');
|
|
266
|
-
break;
|
|
267
|
-
case 'draw':
|
|
268
|
-
await this.parseDraw(nestedCall, newNamespaces);
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
parseMount(call, line) {
|
|
275
|
-
const args = getCallArguments(call);
|
|
276
|
-
if (args.length === 0)
|
|
277
|
-
return;
|
|
278
|
-
// First arg is the engine
|
|
279
|
-
const engineArg = args[0];
|
|
280
|
-
const engine = engineArg.text;
|
|
281
|
-
// Find mount path from 'at:' option or '=>' syntax
|
|
282
|
-
let mountPath = '/';
|
|
283
|
-
for (const arg of args) {
|
|
284
|
-
if (arg.type === 'hash' || arg.type === 'pair') {
|
|
285
|
-
const pairs = arg.type === 'hash' ? getChildrenByType(arg, 'pair') : [arg];
|
|
286
|
-
for (const pair of pairs) {
|
|
287
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
288
|
-
const value = pair.child(2);
|
|
289
|
-
if (key === 'at' && value) {
|
|
290
|
-
mountPath = this.extractStringValue(value) || mountPath;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (arg.type === 'string') {
|
|
295
|
-
// Could be the path in "mount Engine => '/path'" syntax
|
|
296
|
-
const strValue = this.extractStringValue(arg);
|
|
297
|
-
if (strValue && strValue.startsWith('/')) {
|
|
298
|
-
mountPath = strValue;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
this.mountedEngines.push({
|
|
303
|
-
engine,
|
|
304
|
-
mountPath,
|
|
305
|
-
line,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
async parseDraw(call, namespaces) {
|
|
309
|
-
const args = getCallArguments(call);
|
|
310
|
-
if (args.length === 0)
|
|
311
|
-
return;
|
|
312
|
-
const drawName = args[0].text.replace(/^:/, '');
|
|
313
|
-
const drawFile = path.join(this.routesDir, `${drawName}.rb`);
|
|
314
|
-
if (fs.existsSync(drawFile)) {
|
|
315
|
-
this.drawnFiles.push(drawFile);
|
|
316
|
-
try {
|
|
317
|
-
await this.parseRoutesFile(drawFile, namespaces);
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
this.errors.push(`Error parsing drawn file ${drawFile}: ${error}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
parseDeviseFor(call, namespaces, line) {
|
|
325
|
-
const args = getCallArguments(call);
|
|
326
|
-
if (args.length === 0)
|
|
327
|
-
return;
|
|
328
|
-
const resource = args[0].text.replace(/^:/, '');
|
|
329
|
-
// Generate standard Devise routes
|
|
330
|
-
const deviseRoutes = [
|
|
331
|
-
{ method: 'GET', path: `/${resource}/sign_in`, action: 'new', controller: 'devise/sessions' },
|
|
332
|
-
{
|
|
333
|
-
method: 'POST',
|
|
334
|
-
path: `/${resource}/sign_in`,
|
|
335
|
-
action: 'create',
|
|
336
|
-
controller: 'devise/sessions',
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
method: 'DELETE',
|
|
340
|
-
path: `/${resource}/sign_out`,
|
|
341
|
-
action: 'destroy',
|
|
342
|
-
controller: 'devise/sessions',
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
method: 'GET',
|
|
346
|
-
path: `/${resource}/password/new`,
|
|
347
|
-
action: 'new',
|
|
348
|
-
controller: 'devise/passwords',
|
|
349
|
-
},
|
|
350
|
-
{
|
|
351
|
-
method: 'POST',
|
|
352
|
-
path: `/${resource}/password`,
|
|
353
|
-
action: 'create',
|
|
354
|
-
controller: 'devise/passwords',
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
method: 'GET',
|
|
358
|
-
path: `/${resource}/sign_up`,
|
|
359
|
-
action: 'new',
|
|
360
|
-
controller: 'devise/registrations',
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
method: 'POST',
|
|
364
|
-
path: `/${resource}`,
|
|
365
|
-
action: 'create',
|
|
366
|
-
controller: 'devise/registrations',
|
|
367
|
-
},
|
|
368
|
-
];
|
|
369
|
-
for (const dr of deviseRoutes) {
|
|
370
|
-
this.routes.push({
|
|
371
|
-
method: dr.method,
|
|
372
|
-
path: this.buildPath(namespaces, dr.path),
|
|
373
|
-
controller: dr.controller,
|
|
374
|
-
action: dr.action,
|
|
375
|
-
namespace: namespaces.join('/') || undefined,
|
|
376
|
-
line,
|
|
377
|
-
authenticated: false,
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
parseRoot(call, namespaces, line) {
|
|
382
|
-
const args = getCallArguments(call);
|
|
383
|
-
let controller = '';
|
|
384
|
-
let action = 'index';
|
|
385
|
-
for (const arg of args) {
|
|
386
|
-
if (arg.type === 'string') {
|
|
387
|
-
const value = this.extractStringValue(arg);
|
|
388
|
-
if (value && value.includes('#')) {
|
|
389
|
-
[controller, action] = value.split('#');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
else if (arg.type === 'hash' || arg.type === 'pair') {
|
|
393
|
-
const pairs = arg.type === 'hash' ? getChildrenByType(arg, 'pair') : [arg];
|
|
394
|
-
for (const pair of pairs) {
|
|
395
|
-
const key = pair.child(0)?.text?.replace(/^:/, '');
|
|
396
|
-
const value = pair.child(2);
|
|
397
|
-
if (key === 'to' && value) {
|
|
398
|
-
const toValue = this.extractStringValue(value);
|
|
399
|
-
if (toValue && toValue.includes('#')) {
|
|
400
|
-
[controller, action] = toValue.split('#');
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (controller) {
|
|
407
|
-
this.routes.push({
|
|
408
|
-
method: 'GET',
|
|
409
|
-
path: this.buildPath(namespaces, '/'),
|
|
410
|
-
controller,
|
|
411
|
-
action,
|
|
412
|
-
namespace: namespaces.join('/') || undefined,
|
|
413
|
-
line,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
generateResourceRoutes(resource, namespaces, singular) {
|
|
418
|
-
const basePath = this.buildPath(namespaces, `/${resource.name}`);
|
|
419
|
-
const allActions = singular
|
|
420
|
-
? ['show', 'new', 'create', 'edit', 'update', 'destroy']
|
|
421
|
-
: ['index', 'show', 'new', 'create', 'edit', 'update', 'destroy'];
|
|
422
|
-
const actions = resource.only ||
|
|
423
|
-
(resource.except
|
|
424
|
-
? allActions.filter((a) => !resource.except.includes(a))
|
|
425
|
-
: allActions);
|
|
426
|
-
const restfulRoutes = [];
|
|
427
|
-
if (!singular) {
|
|
428
|
-
if (actions.includes('index')) {
|
|
429
|
-
restfulRoutes.push({ method: 'GET', path: basePath, action: 'index' });
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (actions.includes('new')) {
|
|
433
|
-
restfulRoutes.push({ method: 'GET', path: `${basePath}/new`, action: 'new' });
|
|
434
|
-
}
|
|
435
|
-
if (actions.includes('create')) {
|
|
436
|
-
restfulRoutes.push({ method: 'POST', path: basePath, action: 'create' });
|
|
437
|
-
}
|
|
438
|
-
const showPath = singular ? basePath : `${basePath}/:id`;
|
|
439
|
-
if (actions.includes('show')) {
|
|
440
|
-
restfulRoutes.push({ method: 'GET', path: showPath, action: 'show' });
|
|
441
|
-
}
|
|
442
|
-
if (actions.includes('edit')) {
|
|
443
|
-
restfulRoutes.push({ method: 'GET', path: `${showPath}/edit`, action: 'edit' });
|
|
444
|
-
}
|
|
445
|
-
if (actions.includes('update')) {
|
|
446
|
-
restfulRoutes.push({ method: 'PUT', path: showPath, action: 'update' });
|
|
447
|
-
restfulRoutes.push({ method: 'PATCH', path: showPath, action: 'update' });
|
|
448
|
-
}
|
|
449
|
-
if (actions.includes('destroy')) {
|
|
450
|
-
restfulRoutes.push({ method: 'DELETE', path: showPath, action: 'destroy' });
|
|
451
|
-
}
|
|
452
|
-
for (const route of restfulRoutes) {
|
|
453
|
-
this.routes.push({
|
|
454
|
-
method: route.method,
|
|
455
|
-
path: route.path,
|
|
456
|
-
controller: resource.controller,
|
|
457
|
-
action: route.action,
|
|
458
|
-
namespace: namespaces.join('/') || undefined,
|
|
459
|
-
line: resource.line,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
buildPath(namespaces, routePath) {
|
|
464
|
-
if (routePath.startsWith('/')) {
|
|
465
|
-
return routePath;
|
|
466
|
-
}
|
|
467
|
-
const nsPath = namespaces.length > 0 ? `/${namespaces.join('/')}` : '';
|
|
468
|
-
return `${nsPath}/${routePath}`;
|
|
469
|
-
}
|
|
470
|
-
extractStringValue(node) {
|
|
471
|
-
if (node.type === 'string') {
|
|
472
|
-
// String has quotes, extract content
|
|
473
|
-
const content = getChildByType(node, 'string_content');
|
|
474
|
-
return content ? content.text : node.text.replace(/^["']|["']$/g, '');
|
|
475
|
-
}
|
|
476
|
-
if (node.type === 'string_content') {
|
|
477
|
-
return node.text;
|
|
478
|
-
}
|
|
479
|
-
if (node.type === 'simple_symbol' || node.type === 'symbol') {
|
|
480
|
-
return node.text.replace(/^:/, '');
|
|
481
|
-
}
|
|
482
|
-
return node.text.replace(/^["']|["']$/g, '');
|
|
483
|
-
}
|
|
484
|
-
extractArrayValues(node) {
|
|
485
|
-
const values = [];
|
|
486
|
-
if (node.type === 'array') {
|
|
487
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
488
|
-
const child = node.child(i);
|
|
489
|
-
if (child && child.type !== '[' && child.type !== ']' && child.type !== ',') {
|
|
490
|
-
const value = this.extractStringValue(child);
|
|
491
|
-
if (value)
|
|
492
|
-
values.push(value);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
// Single value
|
|
498
|
-
const value = this.extractStringValue(node);
|
|
499
|
-
if (value)
|
|
500
|
-
values.push(value);
|
|
501
|
-
}
|
|
502
|
-
return values;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
// Standalone execution for testing
|
|
506
|
-
async function main() {
|
|
507
|
-
const targetPath = process.argv[2] || process.cwd();
|
|
508
|
-
console.log(`Analyzing routes in: ${targetPath}`);
|
|
509
|
-
const analyzer = new RailsRoutesAnalyzer(targetPath);
|
|
510
|
-
const result = await analyzer.analyze();
|
|
511
|
-
console.log('\n=== Rails Routes Analysis ===\n');
|
|
512
|
-
console.log(`Total routes: ${result.routes.length}`);
|
|
513
|
-
console.log(`Namespaces: ${result.namespaces.join(', ') || '(none)'}`);
|
|
514
|
-
console.log(`Resources: ${result.resources.length}`);
|
|
515
|
-
console.log(`Mounted engines: ${result.mountedEngines.length}`);
|
|
516
|
-
console.log(`External route files: ${result.drawnFiles.length}`);
|
|
517
|
-
if (result.errors.length > 0) {
|
|
518
|
-
console.log(`\n--- Errors ---`);
|
|
519
|
-
for (const error of result.errors) {
|
|
520
|
-
console.log(` ❌ ${error}`);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
console.log('\n--- Sample Routes (first 30) ---');
|
|
524
|
-
for (const route of result.routes.slice(0, 30)) {
|
|
525
|
-
console.log(` ${route.method.padEnd(7)} ${route.path.padEnd(50)} => ${route.controller}#${route.action}`);
|
|
526
|
-
}
|
|
527
|
-
console.log('\n--- Mounted Engines ---');
|
|
528
|
-
for (const engine of result.mountedEngines) {
|
|
529
|
-
console.log(` ${engine.engine} => ${engine.mountPath}`);
|
|
530
|
-
}
|
|
531
|
-
console.log('\n--- External Route Files ---');
|
|
532
|
-
for (const file of result.drawnFiles) {
|
|
533
|
-
console.log(` ${path.basename(file)}`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
// Run if executed directly
|
|
537
|
-
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
538
|
-
if (isMainModule) {
|
|
539
|
-
main().catch(console.error);
|
|
540
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export interface ReactComponentRef {
|
|
2
|
-
name: string;
|
|
3
|
-
propsVar?: string;
|
|
4
|
-
ssr?: boolean;
|
|
5
|
-
line?: number;
|
|
6
|
-
}
|
|
7
|
-
export interface RailsViewInfo {
|
|
8
|
-
name: string;
|
|
9
|
-
path: string;
|
|
10
|
-
controller: string;
|
|
11
|
-
action: string;
|
|
12
|
-
format: string;
|
|
13
|
-
template: 'haml' | 'erb' | 'yml' | 'other';
|
|
14
|
-
routePath?: string;
|
|
15
|
-
partials: string[];
|
|
16
|
-
helpers: string[];
|
|
17
|
-
instanceVars: string[];
|
|
18
|
-
reactComponents: ReactComponentRef[];
|
|
19
|
-
line?: number;
|
|
20
|
-
}
|
|
21
|
-
export interface RailsPageInfo {
|
|
22
|
-
route: string;
|
|
23
|
-
method: string;
|
|
24
|
-
controller: string;
|
|
25
|
-
action: string;
|
|
26
|
-
view?: RailsViewInfo;
|
|
27
|
-
apis: RailsApiCall[];
|
|
28
|
-
services: string[];
|
|
29
|
-
grpcCalls: string[];
|
|
30
|
-
modelAccess: string[];
|
|
31
|
-
}
|
|
32
|
-
export interface RailsApiCall {
|
|
33
|
-
type: 'grpc' | 'service' | 'http' | 'internal';
|
|
34
|
-
name: string;
|
|
35
|
-
method?: string;
|
|
36
|
-
source: string;
|
|
37
|
-
line?: number;
|
|
38
|
-
}
|
|
39
|
-
export interface RailsViewAnalysisResult {
|
|
40
|
-
views: RailsViewInfo[];
|
|
41
|
-
pages: RailsPageInfo[];
|
|
42
|
-
summary: {
|
|
43
|
-
totalViews: number;
|
|
44
|
-
totalPages: number;
|
|
45
|
-
byController: Record<string, number>;
|
|
46
|
-
byTemplate: Record<string, number>;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
export declare function analyzeRailsViews(rootPath: string): Promise<RailsViewAnalysisResult>;
|