@filipebraida/adonis-function-points 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/README.md +1 -1
- package/build/commands/main.js +6 -6
- package/build/{fp_calibrate-EAuAtdbq.js → fp_calibrate-3TxGdS1b.js} +1 -1
- package/build/{fp_count-CZ0cUUBQ.js → fp_count-arGLnVlY.js} +1 -1
- package/build/{fp_diff-BTg_LX0r.js → fp_diff-DBBvzq5x.js} +1 -1
- package/build/{fp_explain-D6QvDLKQ.js → fp_explain-aNFApwiT.js} +1 -1
- package/build/{fp_inventory-C43fU39x.js → fp_inventory-DIjKIC9t.js} +1 -1
- package/build/{fp_metrics-DEMPk4xC.js → fp_metrics-BpU61waG.js} +1 -1
- package/build/index.js +2 -2
- package/build/{pipeline-Cq4dNTNE.js → pipeline-DO2301fV.js} +1414 -122
- package/build/{resolvers-DlKJOZnk.js → resolvers-DaU4uAqT.js} +323 -150
- package/build/{runners-FYmPIPub.js → runners-Dm7cWGa-.js} +2 -2
- package/build/src/albrecht/counter.d.ts +8 -1
- package/build/src/cli.js +2 -2
- package/build/src/inventory/graph/call_graph.d.ts +15 -0
- package/build/src/inventory/graph/deliveries.d.ts +88 -0
- package/build/src/inventory/graph/output_fields.d.ts +45 -1
- package/build/src/inventory/resolvers/index.js +1 -1
- package/build/src/inventory/resolvers/job_dispatch.d.ts +20 -0
- package/build/src/inventory/resolvers/local_function.d.ts +24 -0
- package/build/src/inventory/resolvers/transformer.d.ts +0 -23
- package/build/src/inventory/sources/commands.d.ts +14 -0
- package/build/src/inventory/sources/jobs.d.ts +27 -0
- package/build/src/pipeline.js +1 -1
- package/build/src/types.d.ts +19 -0
- package/package.json +1 -1
|
@@ -83,129 +83,6 @@ function isApplicationCode(root, file) {
|
|
|
83
83
|
return !parts.slice(0, -1).some((segment) => SCAFFOLDING.has(segment));
|
|
84
84
|
}
|
|
85
85
|
//#endregion
|
|
86
|
-
//#region src/inventory/sources/event_bindings.ts
|
|
87
|
-
/** the method a listener declares; AdonisJS calls `handle` unless told otherwise */
|
|
88
|
-
const LISTENER_METHOD = "handle";
|
|
89
|
-
function collectEventBindings(app) {
|
|
90
|
-
const project = new Project({
|
|
91
|
-
skipAddingFilesFromTsConfig: true,
|
|
92
|
-
skipFileDependencyResolution: true,
|
|
93
|
-
compilerOptions: { allowJs: false }
|
|
94
|
-
});
|
|
95
|
-
for (const root of app.scanRoots) project.addSourceFilesAtPaths(`${root}/**/*.ts`);
|
|
96
|
-
const bindings = /* @__PURE__ */ new Map();
|
|
97
|
-
for (const file of project.getSourceFiles()) for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
98
|
-
const expression = call.getExpression();
|
|
99
|
-
if (!Node.isPropertyAccessExpression(expression)) continue;
|
|
100
|
-
if (expression.getName() !== "on") continue;
|
|
101
|
-
const [event, handlers] = call.getArguments();
|
|
102
|
-
if (!event || !handlers) continue;
|
|
103
|
-
const eventFile = resolveEventClass(event, file, app);
|
|
104
|
-
if (!eventFile) continue;
|
|
105
|
-
const refs = listenersOf(handlers, file, app);
|
|
106
|
-
if (refs.length === 0) continue;
|
|
107
|
-
bindings.set(eventFile, [...bindings.get(eventFile) ?? [], ...refs]);
|
|
108
|
-
}
|
|
109
|
-
return bindings;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* The event class a dispatch or a binding names.
|
|
113
|
-
*
|
|
114
|
-
* Two shapes reach here: the class imported directly, and the generated
|
|
115
|
-
* registry (`events.OrderPlaced`), which is what `node ace make:event` produces
|
|
116
|
-
* and therefore the common one. Exported because the resolver has to ask the
|
|
117
|
-
* same question of a call site, and two implementations of "which event is
|
|
118
|
-
* this" would drift.
|
|
119
|
-
*/
|
|
120
|
-
function resolveEventClass(expression, from, app) {
|
|
121
|
-
if (Node.isIdentifier(expression)) {
|
|
122
|
-
const target = importedFrom(expression.getText(), from, app);
|
|
123
|
-
return target ? toPosix(target) : null;
|
|
124
|
-
}
|
|
125
|
-
if (!Node.isPropertyAccessExpression(expression)) return null;
|
|
126
|
-
const root = expression.getExpression();
|
|
127
|
-
if (!Node.isIdentifier(root)) return null;
|
|
128
|
-
const registry = importedFrom(root.getText(), from, app);
|
|
129
|
-
if (!registry) return null;
|
|
130
|
-
return registryEntry(registry, expression.getName(), from.getProject(), app);
|
|
131
|
-
}
|
|
132
|
-
/** listener bodies named by the second argument of `emitter.on` */
|
|
133
|
-
function listenersOf(handlers, from, app) {
|
|
134
|
-
const entries = handlers.isKind(SyntaxKind.ArrayLiteralExpression) ? handlers.getElements() : [handlers];
|
|
135
|
-
const refs = [];
|
|
136
|
-
for (const entry of entries) {
|
|
137
|
-
/**
|
|
138
|
-
* `[SomeListener, 'method']`: AdonisJS lets the binding name the method,
|
|
139
|
-
* and taking `handle` on faith there would look for a body that is not
|
|
140
|
-
* the one bound.
|
|
141
|
-
*/
|
|
142
|
-
if (entry.isKind(SyntaxKind.ArrayLiteralExpression)) {
|
|
143
|
-
const [target, member] = entry.getElements();
|
|
144
|
-
const file = target ? listenerFile(target, from, app) : null;
|
|
145
|
-
if (!file) continue;
|
|
146
|
-
const named = member?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
147
|
-
refs.push({
|
|
148
|
-
file,
|
|
149
|
-
member: named ?? LISTENER_METHOD
|
|
150
|
-
});
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const file = listenerFile(entry, from, app);
|
|
154
|
-
if (file) refs.push({
|
|
155
|
-
file,
|
|
156
|
-
member: LISTENER_METHOD
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
return refs;
|
|
160
|
-
}
|
|
161
|
-
function listenerFile(entry, from, app) {
|
|
162
|
-
if (Node.isPropertyAccessExpression(entry)) {
|
|
163
|
-
const root = entry.getExpression();
|
|
164
|
-
if (!Node.isIdentifier(root)) return null;
|
|
165
|
-
const registry = importedFrom(root.getText(), from, app);
|
|
166
|
-
return registry ? registryEntry(registry, entry.getName(), from.getProject(), app) : null;
|
|
167
|
-
}
|
|
168
|
-
if (Node.isIdentifier(entry)) {
|
|
169
|
-
const target = importedFrom(entry.getText(), from, app);
|
|
170
|
-
return target ? toPosix(target) : null;
|
|
171
|
-
}
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
/** where a local identifier was imported from, resolved through the alias map */
|
|
175
|
-
function importedFrom(local, from, app) {
|
|
176
|
-
for (const declaration of from.getImportDeclarations()) {
|
|
177
|
-
const named = declaration.getNamedImports().some((entry) => (entry.getAliasNode()?.getText() ?? entry.getName()) === local);
|
|
178
|
-
const isDefault = declaration.getDefaultImport()?.getText() === local;
|
|
179
|
-
if (!named && !isDefault) continue;
|
|
180
|
-
return app.resolveSpecifier(declaration.getModuleSpecifierValue());
|
|
181
|
-
}
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* The file a key of a generated registry points at.
|
|
186
|
-
*
|
|
187
|
-
* Both shapes the generators emit are handled: a direct reference to an
|
|
188
|
-
* imported class (`events.ts`) and a lazy importer (`listeners.ts`). They differ
|
|
189
|
-
* per artefact and per framework version, and reading only one of them silently
|
|
190
|
-
* lost half the graph.
|
|
191
|
-
*/
|
|
192
|
-
function registryEntry(registryFile, key, project, app) {
|
|
193
|
-
const file = project.getSourceFile(registryFile) ?? project.addSourceFileAtPathIfExists(registryFile);
|
|
194
|
-
if (!file) return null;
|
|
195
|
-
for (const declaration of file.getVariableDeclarations()) {
|
|
196
|
-
const value = ((declaration.getInitializer()?.asKind(SyntaxKind.ObjectLiteralExpression))?.getProperty(key)?.asKind(SyntaxKind.PropertyAssignment))?.getInitializer();
|
|
197
|
-
if (!value) continue;
|
|
198
|
-
if (Node.isIdentifier(value)) {
|
|
199
|
-
const target = importedFrom(value.getText(), file, app);
|
|
200
|
-
return target ? toPosix(target) : null;
|
|
201
|
-
}
|
|
202
|
-
const specifier = value.getFirstDescendantByKind(SyntaxKind.CallExpression)?.getArguments()[0]?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
203
|
-
const target = specifier ? app.resolveSpecifier(specifier) : null;
|
|
204
|
-
return target ? toPosix(target) : null;
|
|
205
|
-
}
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
//#endregion
|
|
209
86
|
//#region src/inventory/detectors/lucid.ts
|
|
210
87
|
const WRITE_METHODS = new Set([
|
|
211
88
|
"save",
|
|
@@ -512,6 +389,129 @@ function classOfReceiver(receiver) {
|
|
|
512
389
|
return null;
|
|
513
390
|
}
|
|
514
391
|
//#endregion
|
|
392
|
+
//#region src/inventory/sources/event_bindings.ts
|
|
393
|
+
/** the method a listener declares; AdonisJS calls `handle` unless told otherwise */
|
|
394
|
+
const LISTENER_METHOD = "handle";
|
|
395
|
+
function collectEventBindings(app) {
|
|
396
|
+
const project = new Project({
|
|
397
|
+
skipAddingFilesFromTsConfig: true,
|
|
398
|
+
skipFileDependencyResolution: true,
|
|
399
|
+
compilerOptions: { allowJs: false }
|
|
400
|
+
});
|
|
401
|
+
for (const root of app.scanRoots) project.addSourceFilesAtPaths(`${root}/**/*.ts`);
|
|
402
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
403
|
+
for (const file of project.getSourceFiles()) for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
404
|
+
const expression = call.getExpression();
|
|
405
|
+
if (!Node.isPropertyAccessExpression(expression)) continue;
|
|
406
|
+
if (expression.getName() !== "on") continue;
|
|
407
|
+
const [event, handlers] = call.getArguments();
|
|
408
|
+
if (!event || !handlers) continue;
|
|
409
|
+
const eventFile = resolveEventClass(event, file, app);
|
|
410
|
+
if (!eventFile) continue;
|
|
411
|
+
const refs = listenersOf(handlers, file, app);
|
|
412
|
+
if (refs.length === 0) continue;
|
|
413
|
+
bindings.set(eventFile, [...bindings.get(eventFile) ?? [], ...refs]);
|
|
414
|
+
}
|
|
415
|
+
return bindings;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* The event class a dispatch or a binding names.
|
|
419
|
+
*
|
|
420
|
+
* Two shapes reach here: the class imported directly, and the generated
|
|
421
|
+
* registry (`events.OrderPlaced`), which is what `node ace make:event` produces
|
|
422
|
+
* and therefore the common one. Exported because the resolver has to ask the
|
|
423
|
+
* same question of a call site, and two implementations of "which event is
|
|
424
|
+
* this" would drift.
|
|
425
|
+
*/
|
|
426
|
+
function resolveEventClass(expression, from, app) {
|
|
427
|
+
if (Node.isIdentifier(expression)) {
|
|
428
|
+
const target = importedFrom(expression.getText(), from, app);
|
|
429
|
+
return target ? toPosix(target) : null;
|
|
430
|
+
}
|
|
431
|
+
if (!Node.isPropertyAccessExpression(expression)) return null;
|
|
432
|
+
const root = expression.getExpression();
|
|
433
|
+
if (!Node.isIdentifier(root)) return null;
|
|
434
|
+
const registry = importedFrom(root.getText(), from, app);
|
|
435
|
+
if (!registry) return null;
|
|
436
|
+
return registryEntry(registry, expression.getName(), from.getProject(), app);
|
|
437
|
+
}
|
|
438
|
+
/** listener bodies named by the second argument of `emitter.on` */
|
|
439
|
+
function listenersOf(handlers, from, app) {
|
|
440
|
+
const entries = handlers.isKind(SyntaxKind.ArrayLiteralExpression) ? handlers.getElements() : [handlers];
|
|
441
|
+
const refs = [];
|
|
442
|
+
for (const entry of entries) {
|
|
443
|
+
/**
|
|
444
|
+
* `[SomeListener, 'method']`: AdonisJS lets the binding name the method,
|
|
445
|
+
* and taking `handle` on faith there would look for a body that is not
|
|
446
|
+
* the one bound.
|
|
447
|
+
*/
|
|
448
|
+
if (entry.isKind(SyntaxKind.ArrayLiteralExpression)) {
|
|
449
|
+
const [target, member] = entry.getElements();
|
|
450
|
+
const file = target ? listenerFile(target, from, app) : null;
|
|
451
|
+
if (!file) continue;
|
|
452
|
+
const named = member?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
453
|
+
refs.push({
|
|
454
|
+
file,
|
|
455
|
+
member: named ?? LISTENER_METHOD
|
|
456
|
+
});
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const file = listenerFile(entry, from, app);
|
|
460
|
+
if (file) refs.push({
|
|
461
|
+
file,
|
|
462
|
+
member: LISTENER_METHOD
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return refs;
|
|
466
|
+
}
|
|
467
|
+
function listenerFile(entry, from, app) {
|
|
468
|
+
if (Node.isPropertyAccessExpression(entry)) {
|
|
469
|
+
const root = entry.getExpression();
|
|
470
|
+
if (!Node.isIdentifier(root)) return null;
|
|
471
|
+
const registry = importedFrom(root.getText(), from, app);
|
|
472
|
+
return registry ? registryEntry(registry, entry.getName(), from.getProject(), app) : null;
|
|
473
|
+
}
|
|
474
|
+
if (Node.isIdentifier(entry)) {
|
|
475
|
+
const target = importedFrom(entry.getText(), from, app);
|
|
476
|
+
return target ? toPosix(target) : null;
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
/** where a local identifier was imported from, resolved through the alias map */
|
|
481
|
+
function importedFrom(local, from, app) {
|
|
482
|
+
for (const declaration of from.getImportDeclarations()) {
|
|
483
|
+
const named = declaration.getNamedImports().some((entry) => (entry.getAliasNode()?.getText() ?? entry.getName()) === local);
|
|
484
|
+
const isDefault = declaration.getDefaultImport()?.getText() === local;
|
|
485
|
+
if (!named && !isDefault) continue;
|
|
486
|
+
return app.resolveSpecifier(declaration.getModuleSpecifierValue());
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* The file a key of a generated registry points at.
|
|
492
|
+
*
|
|
493
|
+
* Both shapes the generators emit are handled: a direct reference to an
|
|
494
|
+
* imported class (`events.ts`) and a lazy importer (`listeners.ts`). They differ
|
|
495
|
+
* per artefact and per framework version, and reading only one of them silently
|
|
496
|
+
* lost half the graph.
|
|
497
|
+
*/
|
|
498
|
+
function registryEntry(registryFile, key, project, app) {
|
|
499
|
+
const file = project.getSourceFile(registryFile) ?? project.addSourceFileAtPathIfExists(registryFile);
|
|
500
|
+
if (!file) return null;
|
|
501
|
+
for (const declaration of file.getVariableDeclarations()) {
|
|
502
|
+
const value = ((declaration.getInitializer()?.asKind(SyntaxKind.ObjectLiteralExpression))?.getProperty(key)?.asKind(SyntaxKind.PropertyAssignment))?.getInitializer();
|
|
503
|
+
if (!value) continue;
|
|
504
|
+
if (Node.isIdentifier(value)) {
|
|
505
|
+
const target = importedFrom(value.getText(), file, app);
|
|
506
|
+
return target ? toPosix(target) : null;
|
|
507
|
+
}
|
|
508
|
+
const specifier = value.getFirstDescendantByKind(SyntaxKind.CallExpression)?.getArguments()[0]?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
509
|
+
const target = specifier ? app.resolveSpecifier(specifier) : null;
|
|
510
|
+
return target ? toPosix(target) : null;
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
515
|
//#region src/inventory/resolvers/event_dispatch.ts
|
|
516
516
|
/**
|
|
517
517
|
* "Event" pattern: the handler announces, and listeners act.
|
|
@@ -619,6 +619,68 @@ const jobDispatchResolver = {
|
|
|
619
619
|
}
|
|
620
620
|
};
|
|
621
621
|
//#endregion
|
|
622
|
+
//#region src/inventory/resolvers/local_function.ts
|
|
623
|
+
/**
|
|
624
|
+
* "Local function" pattern: a helper declared in the same file, not imported.
|
|
625
|
+
*
|
|
626
|
+
* const lista = await proximos(id) // function proximos() { … }
|
|
627
|
+
* return rows.map(paraLinha) // const paraLinha = (row) => …
|
|
628
|
+
*
|
|
629
|
+
* A query object that keeps its helpers beside it is common, and before this
|
|
630
|
+
* every such call was unresolved: the store a helper read was reached by nobody
|
|
631
|
+
* from that route, and a value built by it left as a whole table handed in.
|
|
632
|
+
*
|
|
633
|
+
* Only module-level declarations: a closure declared inside a body is part of
|
|
634
|
+
* that body, and the graph already walks it.
|
|
635
|
+
*/
|
|
636
|
+
const localFunctionResolver = {
|
|
637
|
+
name: "local-function",
|
|
638
|
+
order: 45,
|
|
639
|
+
resolve(call, ctx) {
|
|
640
|
+
const named = calledFunctionOf(call);
|
|
641
|
+
if (!named) return [];
|
|
642
|
+
const name = named.getText();
|
|
643
|
+
if (ctx.imports.has(name)) return [];
|
|
644
|
+
if (!declaresFunction(ctx.file, name)) return [];
|
|
645
|
+
return [{
|
|
646
|
+
file: ctx.file.getFilePath(),
|
|
647
|
+
member: name
|
|
648
|
+
}];
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
/** callbacks that apply a function to each element: `rows.map(paraLinha)` calls `paraLinha` */
|
|
652
|
+
const APPLIES_CALLBACK = new Set([
|
|
653
|
+
"map",
|
|
654
|
+
"flatMap",
|
|
655
|
+
"forEach",
|
|
656
|
+
"filter",
|
|
657
|
+
"find",
|
|
658
|
+
"some",
|
|
659
|
+
"every",
|
|
660
|
+
"sort",
|
|
661
|
+
"reduce"
|
|
662
|
+
]);
|
|
663
|
+
/**
|
|
664
|
+
* The function a call names: `proximos(id)` names `proximos`; `rows.map(paraLinha)`
|
|
665
|
+
* names `paraLinha`, called once per row — the body the graph must read is the
|
|
666
|
+
* same, whichever way it was reached.
|
|
667
|
+
*/
|
|
668
|
+
function calledFunctionOf(call) {
|
|
669
|
+
const expression = call.getExpression();
|
|
670
|
+
if (Node.isIdentifier(expression)) return expression;
|
|
671
|
+
if (Node.isPropertyAccessExpression(expression) && APPLIES_CALLBACK.has(expression.getName())) {
|
|
672
|
+
const callback = call.getArguments()[0];
|
|
673
|
+
if (callback && Node.isIdentifier(callback)) return callback;
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
/** `function name() {}` or `const name = () => {}` / `function () {}` at module level */
|
|
678
|
+
function declaresFunction(file, name) {
|
|
679
|
+
if (file.getFunction(name)) return true;
|
|
680
|
+
const initializer = file.getVariableDeclaration(name)?.getInitializer();
|
|
681
|
+
return !!initializer && (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer));
|
|
682
|
+
}
|
|
683
|
+
//#endregion
|
|
622
684
|
//#region src/inventory/resolvers/module_function.ts
|
|
623
685
|
/**
|
|
624
686
|
* "Module function" pattern: no class at all.
|
|
@@ -633,8 +695,8 @@ const moduleFunctionResolver = {
|
|
|
633
695
|
name: "module-function",
|
|
634
696
|
order: 50,
|
|
635
697
|
resolve(call, ctx) {
|
|
636
|
-
const expr = call
|
|
637
|
-
if (!expr
|
|
698
|
+
const expr = calledFunctionOf(call);
|
|
699
|
+
if (!expr) return [];
|
|
638
700
|
const local = expr.getText();
|
|
639
701
|
const file = ctx.imports.get(local);
|
|
640
702
|
if (!file) return [];
|
|
@@ -809,11 +871,37 @@ function outputFieldsIn(body, owner, stores, followed) {
|
|
|
809
871
|
* data function applies (§6).
|
|
810
872
|
*/
|
|
811
873
|
const excluded = new Set((resource ? stores.get(resource)?.attributes : void 0)?.filter((attribute) => attribute.isIdentifier || attribute.system).map((attribute) => attribute.name) ?? []);
|
|
874
|
+
const { leaves, opaque } = collectLeaves(returnedLiteralsOf(body), {
|
|
875
|
+
qualifier,
|
|
876
|
+
excluded,
|
|
877
|
+
followed
|
|
878
|
+
});
|
|
879
|
+
return {
|
|
880
|
+
outputs: leaves,
|
|
881
|
+
opaqueOutputs: opaque,
|
|
882
|
+
resource: resource && stores.has(resource) ? resource : null
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* The leaves of object literals, by the §7 rules:
|
|
887
|
+
*
|
|
888
|
+
* { titulo: l.titulo } 1 — `titulo`
|
|
889
|
+
* { autor: AutorTransformer.transform } 0 here; the followed body contributes
|
|
890
|
+
* { endereco: { rua, cidade } } leaves individually
|
|
891
|
+
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
892
|
+
* { itens: xs.map((i) => ({ a, b })) } the leaves, once
|
|
893
|
+
* ...this.pick(this.resource, [...]) the listed names
|
|
894
|
+
* ...this.toObject() 0 here; the followed body contributes
|
|
895
|
+
* ...anythingElse 1, opaque, reported
|
|
896
|
+
*/
|
|
897
|
+
function collectLeaves(literals, options) {
|
|
898
|
+
const { qualifier, excluded, followed } = options;
|
|
812
899
|
const outputs = /* @__PURE__ */ new Set();
|
|
813
900
|
const opaque = /* @__PURE__ */ new Set();
|
|
901
|
+
const q = qualifier ? `${qualifier}.` : "";
|
|
814
902
|
const leaf = (prefix, name) => {
|
|
815
903
|
if (prefix === "" && excluded.has(name)) return;
|
|
816
|
-
outputs.add(`${
|
|
904
|
+
outputs.add(`${q}${prefix ? `${prefix}.${name}` : name}`);
|
|
817
905
|
};
|
|
818
906
|
const walk = (literal, prefix) => {
|
|
819
907
|
for (const property of literal.getProperties()) {
|
|
@@ -830,7 +918,7 @@ function outputFieldsIn(body, owner, stores, followed) {
|
|
|
830
918
|
continue;
|
|
831
919
|
}
|
|
832
920
|
if (!Node.isPropertyAssignment(property)) continue;
|
|
833
|
-
const name = property.getName().replace(/^['"]|['"]$/g, "");
|
|
921
|
+
const name = Node.isComputedPropertyName(property.getNameNode()) ? "*" : property.getName().replace(/^['"]|['"]$/g, "");
|
|
834
922
|
const value = unwrap(property.getInitializer());
|
|
835
923
|
if (!value) {
|
|
836
924
|
leaf(prefix, name);
|
|
@@ -874,15 +962,14 @@ function outputFieldsIn(body, owner, stores, followed) {
|
|
|
874
962
|
* One DET as a floor, and reported — the placeholder carries the expression
|
|
875
963
|
* so the report can name what could not be read.
|
|
876
964
|
*/
|
|
877
|
-
const placeholder = `${
|
|
965
|
+
const placeholder = `${q}${prefix ? `${prefix}.` : ""}<${value.getText().replace(/\s+/g, "")}>`;
|
|
878
966
|
outputs.add(placeholder);
|
|
879
967
|
opaque.add(placeholder);
|
|
880
968
|
};
|
|
881
|
-
for (const literal of
|
|
969
|
+
for (const literal of literals) walk(literal, "");
|
|
882
970
|
return {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
resource: resource && stores.has(resource) ? resource : null
|
|
971
|
+
leaves: [...outputs],
|
|
972
|
+
opaque: [...opaque]
|
|
886
973
|
};
|
|
887
974
|
}
|
|
888
975
|
/**
|
|
@@ -1034,6 +1121,76 @@ const TRANSFORMER_METHODS = new Set([
|
|
|
1034
1121
|
]);
|
|
1035
1122
|
/** what the application-side body is called */
|
|
1036
1123
|
const APPLICATION_BODY = "toObject";
|
|
1124
|
+
/** the variant named in these is a method of the transformer, and part of what leaves */
|
|
1125
|
+
const VARIANT_METHODS = new Set(["useVariant", "withVariant"]);
|
|
1126
|
+
/**
|
|
1127
|
+
* "Transformer" pattern: the package supplies the API, the application
|
|
1128
|
+
* supplies the body.
|
|
1129
|
+
*
|
|
1130
|
+
* class InviteTransformer extends BaseTransformer<Invite> {
|
|
1131
|
+
* toObject() { … }
|
|
1132
|
+
* }
|
|
1133
|
+
*
|
|
1134
|
+
* InviteTransformer.transform(invite)
|
|
1135
|
+
*
|
|
1136
|
+
* `transform()` and `paginate()` live in `@adonisjs/core`, so resolving the
|
|
1137
|
+
* symbol lands on the application file and finds no body there. The naive
|
|
1138
|
+
* reading is that the tracer must step into node_modules; it does not. Those
|
|
1139
|
+
* methods call BACK into `toObject()`, which the application writes, so the
|
|
1140
|
+
* body worth analysing was in the application all along.
|
|
1141
|
+
*
|
|
1142
|
+
* It is the same shape as `job-dispatch`, where `dispatch` enqueues and
|
|
1143
|
+
* `handle` executes.
|
|
1144
|
+
*
|
|
1145
|
+
* COUNTING DECISION: the write a transformer performs belongs to the
|
|
1146
|
+
* transaction that serialised through it. Without this, a table written only
|
|
1147
|
+
* inside `toObject()` is reached by nobody and drops out under AFP §6.5.4.
|
|
1148
|
+
*/
|
|
1149
|
+
/** is `.useVariant('x')` / `.withVariant('x')` the next link of this call's chain? */
|
|
1150
|
+
function variantFollows(call) {
|
|
1151
|
+
let node = call;
|
|
1152
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
1153
|
+
const parent = node.getParent();
|
|
1154
|
+
if (!parent) return false;
|
|
1155
|
+
if (Node.isAwaitExpression(parent) || Node.isParenthesizedExpression(parent)) {
|
|
1156
|
+
node = parent;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (!Node.isPropertyAccessExpression(parent) || parent.getExpression() !== node) return false;
|
|
1160
|
+
const next = parent.getParent();
|
|
1161
|
+
if (!next || !Node.isCallExpression(next) || next.getExpression() !== parent) return false;
|
|
1162
|
+
if (VARIANT_METHODS.has(parent.getName())) return next.getArguments()[0]?.getKind() === SyntaxKind.StringLiteral;
|
|
1163
|
+
node = next;
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
/** the transformer class a chain call is on, if it is one: `X.transform(p)`, `X.transform(p).useVariant(v)` */
|
|
1168
|
+
function transformerOf(call, ctx) {
|
|
1169
|
+
const expression = call.getExpression();
|
|
1170
|
+
if (!Node.isPropertyAccessExpression(expression)) return null;
|
|
1171
|
+
if (!TRANSFORMER_METHODS.has(expression.getName())) return null;
|
|
1172
|
+
/**
|
|
1173
|
+
* The root of the chain, so `X.transform(p).useVariant(v)` resolves as
|
|
1174
|
+
* well as `X.transform(p)`. Analysing the same body twice is free: the
|
|
1175
|
+
* graph dedupes by file and member.
|
|
1176
|
+
*/
|
|
1177
|
+
const symbol = rootSymbolOf(expression.getExpression());
|
|
1178
|
+
if (!symbol) return null;
|
|
1179
|
+
const file = ctx.imports.get(symbol) ?? ctx.injected.get(symbol);
|
|
1180
|
+
if (!file) return null;
|
|
1181
|
+
/**
|
|
1182
|
+
* One definition of what a transformer is, shared with the output walker
|
|
1183
|
+
* (`graph/output_fields.ts`): the resolver must not follow a body whose keys
|
|
1184
|
+
* the walker would refuse to read, or the other way round.
|
|
1185
|
+
*/
|
|
1186
|
+
const declared = ctx.sourceFile(file);
|
|
1187
|
+
if (!declared || !declared.getClasses().some(isTransformerClass)) return null;
|
|
1188
|
+
const owner = declared.getClasses().find((cls) => cls.getMethod(APPLICATION_BODY));
|
|
1189
|
+
return owner ? {
|
|
1190
|
+
file,
|
|
1191
|
+
owner
|
|
1192
|
+
} : null;
|
|
1193
|
+
}
|
|
1037
1194
|
//#endregion
|
|
1038
1195
|
//#region src/inventory/resolvers/index.ts
|
|
1039
1196
|
/**
|
|
@@ -1051,34 +1208,50 @@ const BUILTIN_CALL_RESOLVERS = [
|
|
|
1051
1208
|
{
|
|
1052
1209
|
name: "transformer",
|
|
1053
1210
|
order: 18,
|
|
1211
|
+
/**
|
|
1212
|
+
* The `transform` before a `useVariant`: claimed and followed nowhere, or
|
|
1213
|
+
* `static-service` would take it next, look for a `transform` body in the
|
|
1214
|
+
* application file and report the package method as a gap.
|
|
1215
|
+
*/
|
|
1216
|
+
ignores(call, ctx) {
|
|
1217
|
+
const expression = call.getExpression();
|
|
1218
|
+
if (!Node.isPropertyAccessExpression(expression)) return false;
|
|
1219
|
+
if (VARIANT_METHODS.has(expression.getName()) || !variantFollows(call)) return false;
|
|
1220
|
+
return transformerOf(call, ctx) !== null;
|
|
1221
|
+
},
|
|
1054
1222
|
resolve(call, ctx) {
|
|
1055
1223
|
const expression = call.getExpression();
|
|
1056
1224
|
if (!Node.isPropertyAccessExpression(expression)) return [];
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
* well as `X.transform(p)`. Analysing the same body twice is free: the
|
|
1061
|
-
* graph dedupes by file and member.
|
|
1062
|
-
*/
|
|
1063
|
-
const symbol = rootSymbolOf(expression.getExpression());
|
|
1064
|
-
if (!symbol) return [];
|
|
1065
|
-
const file = ctx.imports.get(symbol) ?? ctx.injected.get(symbol);
|
|
1066
|
-
if (!file) return [];
|
|
1225
|
+
const found = transformerOf(call, ctx);
|
|
1226
|
+
if (!found) return [];
|
|
1227
|
+
const { file, owner } = found;
|
|
1067
1228
|
/**
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
* the
|
|
1229
|
+
* `X.transform(p).useVariant('forEgresso')`: the variant is a METHOD of the
|
|
1230
|
+
* transformer, named after it, and it REPLACES `toObject()` — the shape that
|
|
1231
|
+
* leaves is the variant's. The chain is visited call by call: the `useVariant`
|
|
1232
|
+
* call resolves the variant's body, and the `transform` call before it resolves
|
|
1233
|
+
* nothing, or both shapes would leave and the page would count twice.
|
|
1071
1234
|
*/
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1235
|
+
if (VARIANT_METHODS.has(expression.getName())) {
|
|
1236
|
+
const variant = call.getArguments()[0]?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
1237
|
+
return variant && owner.getMethod(variant) ? [{
|
|
1238
|
+
file,
|
|
1239
|
+
member: variant
|
|
1240
|
+
}] : [{
|
|
1241
|
+
file,
|
|
1242
|
+
member: APPLICATION_BODY
|
|
1243
|
+
}];
|
|
1244
|
+
}
|
|
1245
|
+
if (variantFollows(call)) return [];
|
|
1246
|
+
return [{
|
|
1075
1247
|
file,
|
|
1076
1248
|
member: APPLICATION_BODY
|
|
1077
|
-
}]
|
|
1249
|
+
}];
|
|
1078
1250
|
}
|
|
1079
1251
|
},
|
|
1080
1252
|
staticServiceResolver,
|
|
1081
1253
|
propertyServiceResolver,
|
|
1254
|
+
localFunctionResolver,
|
|
1082
1255
|
moduleFunctionResolver
|
|
1083
1256
|
];
|
|
1084
1257
|
/**
|
|
@@ -1147,4 +1320,4 @@ function ignoreCalls(options) {
|
|
|
1147
1320
|
};
|
|
1148
1321
|
}
|
|
1149
1322
|
//#endregion
|
|
1150
|
-
export { chainShapeOf as a,
|
|
1323
|
+
export { relativeTo as _, chainShapeOf as a, unwrap as c, collectEventBindings as d, detectAccess as f, isSeeder as g, isApplicationCode as h, resolveCall as i, DISPATCH_METHODS as l, rootSymbolOf as m, ignoreCalls as n, mappedLiteralOf as o, hooksFiredBy as p, isTechnicalWrite as r, outputFieldsIn as s, BUILTIN_CALL_RESOLVERS as t, EXECUTION_METHODS as u, samePath as v, toPosix as y };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as IncomparableSourcesError, d as DEFAULTS, f as defineConfig, i as measureStructure, n as parseSamples, o as FACTOR_PRESETS, r as measureConformance, s as IncomparableRulesetsError, t as calibrate, u as diffCounts } from "./calibration-DVIf8hcE.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as analyze } from "./pipeline-
|
|
2
|
+
import { y as toPosix } from "./resolvers-DaU4uAqT.js";
|
|
3
|
+
import { n as analyze } from "./pipeline-DO2301fV.js";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
@@ -3,6 +3,7 @@ import type { CollectedDataStore } from '../inventory/sources/data_stores.js';
|
|
|
3
3
|
import type { CollectedEntryPoint } from '../inventory/sources/routes_ast.js';
|
|
4
4
|
import type { Behavior } from '../inventory/graph/call_graph.js';
|
|
5
5
|
import type { DiscoveredSchema } from '../inventory/sources/json_schemas.js';
|
|
6
|
+
import type { CollectedJob } from '../inventory/sources/jobs.js';
|
|
6
7
|
import type { OpaqueDeclaration } from './opaque.js';
|
|
7
8
|
import type { Complexity, CountResult, FunctionType } from '../types.js';
|
|
8
9
|
import type { FunctionOverride } from '../define_config.js';
|
|
@@ -36,12 +37,16 @@ export declare const RULESET = "afp";
|
|
|
36
37
|
* column; system timestamps and `serializeAs: null` columns leaving the DETs;
|
|
37
38
|
* master-detail folded into one data function; identity by table; token tables
|
|
38
39
|
* technical; and opaque declarations reaching every function carrying the origin.
|
|
40
|
+
* Three in 1.6.0: an output's DETs are what the transaction DELIVERS (the render
|
|
41
|
+
* props, the response payload, what a command prints) read back to their origin;
|
|
42
|
+
* a function of the same file and a `.map(fn)` by reference are followed, so
|
|
43
|
+
* FTRs move; and ace commands are transactions, with flags as input.
|
|
39
44
|
*
|
|
40
45
|
* Without the bump, a baseline saved by the previous version compares cleanly
|
|
41
46
|
* against this one and bills the tool's own improvement as work done. The guard
|
|
42
47
|
* exists for exactly that, and only this constant arms it.
|
|
43
48
|
*/
|
|
44
|
-
export declare const RULESET_VERSION = "1.
|
|
49
|
+
export declare const RULESET_VERSION = "1.6.0";
|
|
45
50
|
export type CountInput = {
|
|
46
51
|
app: AppContext;
|
|
47
52
|
stores: CollectedDataStore[];
|
|
@@ -50,6 +55,8 @@ export type CountInput = {
|
|
|
50
55
|
behaviors: Map<string, Behavior>;
|
|
51
56
|
/** JSON Schema literals found in the code, for `detFromSchema` — §8 */
|
|
52
57
|
jsonSchemas?: Map<string, DiscoveredSchema>;
|
|
58
|
+
/** the queue jobs and who dispatches each — a job no transaction reaches is reported (plan 0.7 §D) */
|
|
59
|
+
jobs?: CollectedJob[];
|
|
53
60
|
/**
|
|
54
61
|
* Stores written anywhere in the application's code, reachable from an entry
|
|
55
62
|
* point or not — AFP §6.5.4 asks who MAINTAINS the store, and a job or a
|
package/build/src/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as CoverageTooLowError } from "../pipeline-
|
|
2
|
-
import { a as runInventory, c as ConfigLoadError, i as runExplain, n as runCount, o as runMetrics, r as runDiff, s as printResult, t as runCalibrate } from "../runners-
|
|
1
|
+
import { t as CoverageTooLowError } from "../pipeline-DO2301fV.js";
|
|
2
|
+
import { a as runInventory, c as ConfigLoadError, i as runExplain, n as runCount, o as runMetrics, r as runDiff, s as printResult, t as runCalibrate } from "../runners-Dm7cWGa-.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -53,6 +53,8 @@ export type Behavior = {
|
|
|
53
53
|
* not a validator.
|
|
54
54
|
*/
|
|
55
55
|
requestFields: string[];
|
|
56
|
+
/** an ace command's `@flags.*` / `@args.*`: `flags.limite`, `args.name` — its input DETs */
|
|
57
|
+
commandFields: string[];
|
|
56
58
|
/** the transaction reads the request in a way that enumerates nothing */
|
|
57
59
|
opaqueRequest: boolean;
|
|
58
60
|
/**
|
|
@@ -82,6 +84,19 @@ export type Behavior = {
|
|
|
82
84
|
/** stores it was preloaded through */
|
|
83
85
|
via: string[];
|
|
84
86
|
}>;
|
|
87
|
+
/**
|
|
88
|
+
* What the transaction DELIVERS — plan 0.7 §A′. `any` says a delivery point was
|
|
89
|
+
* found at all; without one the output falls back to the stores read. `fields`
|
|
90
|
+
* are derived values and the leaves of literals a followed body returned,
|
|
91
|
+
* `render:total`; `stores` are the ones whose rows were handed on, raw or through
|
|
92
|
+
* a query object; `opaqueFields` are values nobody could read, 1 DET each.
|
|
93
|
+
*/
|
|
94
|
+
delivered: {
|
|
95
|
+
any: boolean;
|
|
96
|
+
fields: string[];
|
|
97
|
+
opaqueFields: string[];
|
|
98
|
+
stores: string[];
|
|
99
|
+
};
|
|
85
100
|
trace: TraceStep[];
|
|
86
101
|
/** bodies reached, for `fp:diff` */
|
|
87
102
|
scope: ScopeEntry[];
|