@filipebraida/adonis-function-points 0.7.0 → 0.9.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 +132 -3
- package/README.md +1 -1
- package/build/commands/main.js +6 -6
- package/build/{fp_calibrate-3TxGdS1b.js → fp_calibrate-DJspMFHc.js} +1 -1
- package/build/{fp_count-arGLnVlY.js → fp_count-Bjviq8dz.js} +1 -1
- package/build/{fp_diff-DBBvzq5x.js → fp_diff-BSK1kKLQ.js} +1 -1
- package/build/{fp_explain-aNFApwiT.js → fp_explain-CrFcMeXU.js} +1 -1
- package/build/{fp_inventory-DIjKIC9t.js → fp_inventory-B4Pv8Thz.js} +1 -1
- package/build/{fp_metrics-BpU61waG.js → fp_metrics-DoS5riGr.js} +1 -1
- package/build/index.js +2 -2
- package/build/{pipeline-DO2301fV.js → pipeline-DqPrDZfD.js} +1135 -73
- package/build/{resolvers-DaU4uAqT.js → resolvers-CneCj3sT.js} +59 -11
- package/build/{runners-Dm7cWGa-.js → runners-BuZNr-FE.js} +14 -4
- package/build/src/albrecht/counter.d.ts +15 -3
- package/build/src/cli.js +2 -2
- package/build/src/inventory/graph/call_graph.d.ts +7 -0
- package/build/src/inventory/graph/deliveries.d.ts +13 -3
- package/build/src/inventory/graph/output_fields.d.ts +2 -2
- package/build/src/inventory/graph/pages.d.ts +44 -0
- package/build/src/inventory/resolvers/index.js +1 -1
- package/build/src/inventory/resolvers/local_function.d.ts +3 -3
- package/build/src/inventory/sources/event_bindings.d.ts +3 -0
- package/build/src/pipeline.js +1 -1
- package/build/src/reporters/table.d.ts +3 -1
- package/build/src/types.d.ts +18 -0
- package/package.json +1 -1
|
@@ -380,11 +380,26 @@ function classOfReceiver(receiver) {
|
|
|
380
380
|
return target.isKind(SyntaxKind.Identifier) ? target.getText() : null;
|
|
381
381
|
}
|
|
382
382
|
if (receiver.isKind(SyntaxKind.Identifier)) {
|
|
383
|
-
|
|
383
|
+
let init = (receiver.getSymbol()?.getDeclarations().find((d) => d.isKind(SyntaxKind.VariableDeclaration)))?.asKind(SyntaxKind.VariableDeclaration)?.getInitializer();
|
|
384
|
+
while (init?.isKind(SyntaxKind.AwaitExpression) || init?.isKind(SyntaxKind.ParenthesizedExpression)) init = init.getExpression();
|
|
384
385
|
if (init?.isKind(SyntaxKind.NewExpression)) {
|
|
385
386
|
const target = init.getExpression();
|
|
386
387
|
return target.isKind(SyntaxKind.Identifier) ? target.getText() : null;
|
|
387
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* `const svc = await app.container.make(AssignmentService)`: the container hands
|
|
391
|
+
* back an instance of the class named — the same binding as `new`, written the
|
|
392
|
+
* way a controller writes it when the service has dependencies of its own. On a
|
|
393
|
+
* reviewed application this shape carried the write of `POST /assignments`
|
|
394
|
+
* and 31 more sites, and none was followed (plan 0.8 §B).
|
|
395
|
+
*/
|
|
396
|
+
if (init?.isKind(SyntaxKind.CallExpression)) {
|
|
397
|
+
const callee = init.getExpression();
|
|
398
|
+
if (callee.isKind(SyntaxKind.PropertyAccessExpression) && callee.getName() === "make" && callee.getExpression().isKind(SyntaxKind.PropertyAccessExpression) && callee.getExpression().asKind(SyntaxKind.PropertyAccessExpression).getName() === "container") {
|
|
399
|
+
const made = init.getArguments()[0];
|
|
400
|
+
return made?.isKind(SyntaxKind.Identifier) ? made.getText() : null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
388
403
|
}
|
|
389
404
|
return null;
|
|
390
405
|
}
|
|
@@ -399,6 +414,7 @@ function collectEventBindings(app) {
|
|
|
399
414
|
compilerOptions: { allowJs: false }
|
|
400
415
|
});
|
|
401
416
|
for (const root of app.scanRoots) project.addSourceFilesAtPaths(`${root}/**/*.ts`);
|
|
417
|
+
project.addSourceFilesAtPaths(`${toPosix(app.root)}/start/**/*.ts`);
|
|
402
418
|
const bindings = /* @__PURE__ */ new Map();
|
|
403
419
|
for (const file of project.getSourceFiles()) for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
404
420
|
const expression = call.getExpression();
|
|
@@ -406,11 +422,16 @@ function collectEventBindings(app) {
|
|
|
406
422
|
if (expression.getName() !== "on") continue;
|
|
407
423
|
const [event, handlers] = call.getArguments();
|
|
408
424
|
if (!event || !handlers) continue;
|
|
409
|
-
|
|
410
|
-
|
|
425
|
+
/**
|
|
426
|
+
* `emitter.on('order:closed', …)`: a STRING event, keyed by its name, which
|
|
427
|
+
* `emitter.emit('order:closed', payload)` reaches. An application that binds every
|
|
428
|
+
* listener this way had none of them followed (plan 0.9 §C).
|
|
429
|
+
*/
|
|
430
|
+
const key = Node.isStringLiteral(event) ? eventKey(event.getLiteralValue()) : resolveEventClass(event, file, app);
|
|
431
|
+
if (!key) continue;
|
|
411
432
|
const refs = listenersOf(handlers, file, app);
|
|
412
433
|
if (refs.length === 0) continue;
|
|
413
|
-
bindings.set(
|
|
434
|
+
bindings.set(key, [...bindings.get(key) ?? [], ...refs]);
|
|
414
435
|
}
|
|
415
436
|
return bindings;
|
|
416
437
|
}
|
|
@@ -436,10 +457,23 @@ function resolveEventClass(expression, from, app) {
|
|
|
436
457
|
return registryEntry(registry, expression.getName(), from.getProject(), app);
|
|
437
458
|
}
|
|
438
459
|
/** listener bodies named by the second argument of `emitter.on` */
|
|
460
|
+
/** the binding key of a string event: `emitter.on('order:closed', …)` */
|
|
461
|
+
const eventKey = (name) => `event:${name}`;
|
|
439
462
|
function listenersOf(handlers, from, app) {
|
|
440
463
|
const entries = handlers.isKind(SyntaxKind.ArrayLiteralExpression) ? handlers.getElements() : [handlers];
|
|
441
464
|
const refs = [];
|
|
442
465
|
for (const entry of entries) {
|
|
466
|
+
/**
|
|
467
|
+
* `emitter.on(event, async function (payload) { … })`: the listener IS the body,
|
|
468
|
+
* located by its line — the way a route's inline closure already is a handler.
|
|
469
|
+
*/
|
|
470
|
+
if (Node.isArrowFunction(entry) || Node.isFunctionExpression(entry)) {
|
|
471
|
+
refs.push({
|
|
472
|
+
file: toPosix(from.getFilePath()),
|
|
473
|
+
line: entry.getStartLineNumber()
|
|
474
|
+
});
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
443
477
|
/**
|
|
444
478
|
* `[SomeListener, 'method']`: AdonisJS lets the binding name the method,
|
|
445
479
|
* and taking `handle` on faith there would look for a body that is not
|
|
@@ -513,6 +547,8 @@ function registryEntry(registryFile, key, project, app) {
|
|
|
513
547
|
}
|
|
514
548
|
//#endregion
|
|
515
549
|
//#region src/inventory/resolvers/event_dispatch.ts
|
|
550
|
+
/** the emitter's ways of firing a string event */
|
|
551
|
+
const EMITS = new Set(["emit", "emitSerial"]);
|
|
516
552
|
/**
|
|
517
553
|
* "Event" pattern: the handler announces, and listeners act.
|
|
518
554
|
*
|
|
@@ -535,6 +571,18 @@ const eventDispatchResolver = {
|
|
|
535
571
|
if (ctx.eventBindings.size === 0) return [];
|
|
536
572
|
const expression = call.getExpression();
|
|
537
573
|
if (!expression.isKind(SyntaxKind.PropertyAccessExpression)) return [];
|
|
574
|
+
/**
|
|
575
|
+
* `emitter.emit('order:closed', payload)`: a string event reaches the listeners bound
|
|
576
|
+
* to that name — the same decision as `Event.dispatch()`. A name built at runtime
|
|
577
|
+
* binds nothing.
|
|
578
|
+
*/
|
|
579
|
+
if (EMITS.has(expression.getName())) {
|
|
580
|
+
const emitterNode = expression.getExpression();
|
|
581
|
+
const isEmitter = Node.isIdentifier(emitterNode) ? emitterNode.getText() === "emitter" : Node.isPropertyAccessExpression(emitterNode) && emitterNode.getName() === "emitter";
|
|
582
|
+
const name = call.getArguments()[0];
|
|
583
|
+
if (!isEmitter || !name || !Node.isStringLiteral(name)) return [];
|
|
584
|
+
return ctx.eventBindings.get(eventKey(name.getLiteralValue())) ?? [];
|
|
585
|
+
}
|
|
538
586
|
if (expression.getName() !== "dispatch") return [];
|
|
539
587
|
const receiver = expression.getExpression();
|
|
540
588
|
if (!Node.isIdentifier(receiver) && !Node.isPropertyAccessExpression(receiver)) return [];
|
|
@@ -624,7 +672,7 @@ const jobDispatchResolver = {
|
|
|
624
672
|
* "Local function" pattern: a helper declared in the same file, not imported.
|
|
625
673
|
*
|
|
626
674
|
* const lista = await proximos(id) // function proximos() { … }
|
|
627
|
-
* return rows.map(
|
|
675
|
+
* return rows.map(toRow) // const toRow = (row) => …
|
|
628
676
|
*
|
|
629
677
|
* A query object that keeps its helpers beside it is common, and before this
|
|
630
678
|
* every such call was unresolved: the store a helper read was reached by nobody
|
|
@@ -648,7 +696,7 @@ const localFunctionResolver = {
|
|
|
648
696
|
}];
|
|
649
697
|
}
|
|
650
698
|
};
|
|
651
|
-
/** callbacks that apply a function to each element: `rows.map(
|
|
699
|
+
/** callbacks that apply a function to each element: `rows.map(toRow)` calls `toRow` */
|
|
652
700
|
const APPLIES_CALLBACK = new Set([
|
|
653
701
|
"map",
|
|
654
702
|
"flatMap",
|
|
@@ -661,8 +709,8 @@ const APPLIES_CALLBACK = new Set([
|
|
|
661
709
|
"reduce"
|
|
662
710
|
]);
|
|
663
711
|
/**
|
|
664
|
-
* The function a call names: `proximos(id)` names `proximos`; `rows.map(
|
|
665
|
-
* names `
|
|
712
|
+
* The function a call names: `proximos(id)` names `proximos`; `rows.map(toRow)`
|
|
713
|
+
* names `toRow`, called once per row — the body the graph must read is the
|
|
666
714
|
* same, whichever way it was reached.
|
|
667
715
|
*/
|
|
668
716
|
function calledFunctionOf(call) {
|
|
@@ -849,7 +897,7 @@ function transformerResourceOf(cls) {
|
|
|
849
897
|
* { autor: AutorTransformer.transform } 0 here; the nested body contributes
|
|
850
898
|
* { endereco: { rua, cidade } } leaves individually
|
|
851
899
|
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
852
|
-
* {
|
|
900
|
+
* { items: xs.map((i) => ({ a, b })) } the leaves, once
|
|
853
901
|
* ...this.pick(this.resource, [...]) the listed names
|
|
854
902
|
* ...this.toObject() 0 here; the followed body contributes
|
|
855
903
|
* ...anythingElse 1, opaque, reported
|
|
@@ -889,7 +937,7 @@ function outputFieldsIn(body, owner, stores, followed) {
|
|
|
889
937
|
* { autor: AutorTransformer.transform } 0 here; the followed body contributes
|
|
890
938
|
* { endereco: { rua, cidade } } leaves individually
|
|
891
939
|
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
892
|
-
* {
|
|
940
|
+
* { items: xs.map((i) => ({ a, b })) } the leaves, once
|
|
893
941
|
* ...this.pick(this.resource, [...]) the listed names
|
|
894
942
|
* ...this.toObject() 0 here; the followed body contributes
|
|
895
943
|
* ...anythingElse 1, opaque, reported
|
|
@@ -1226,7 +1274,7 @@ const BUILTIN_CALL_RESOLVERS = [
|
|
|
1226
1274
|
if (!found) return [];
|
|
1227
1275
|
const { file, owner } = found;
|
|
1228
1276
|
/**
|
|
1229
|
-
* `X.transform(p).useVariant('
|
|
1277
|
+
* `X.transform(p).useVariant('forSummary')`: the variant is a METHOD of the
|
|
1230
1278
|
* transformer, named after it, and it REPLACES `toObject()` — the shape that
|
|
1231
1279
|
* leaves is the variant's. The chain is visited call by call: the `useVariant`
|
|
1232
1280
|
* call resolves the variant's body, and the `transform` call before it resolves
|
|
@@ -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 { y as toPosix } from "./resolvers-
|
|
3
|
-
import { n as analyze } from "./pipeline-
|
|
2
|
+
import { y as toPosix } from "./resolvers-CneCj3sT.js";
|
|
3
|
+
import { n as analyze } from "./pipeline-DqPrDZfD.js";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
@@ -112,12 +112,21 @@ function renderCount(result) {
|
|
|
112
112
|
if (unresolvedCalls > 0 || entryPointsWithoutHandler > 0 || warnings.length > 0) {
|
|
113
113
|
lines.push("");
|
|
114
114
|
lines.push("Confidence:");
|
|
115
|
-
if (unresolvedCalls > 0)
|
|
115
|
+
if (unresolvedCalls > 0) {
|
|
116
|
+
lines.push(` ${unresolvedCalls} unresolved call(s) — one line per site, whatever the number of transactions reaching it:`);
|
|
117
|
+
const sites = result.confidence.unresolved ?? [];
|
|
118
|
+
for (const site of sites.slice(0, 25)) lines.push(` ${describeSite(site)}`);
|
|
119
|
+
if (sites.length > 25) lines.push(` … and ${sites.length - 25} more — fp:inventory lists them all`);
|
|
120
|
+
}
|
|
116
121
|
if (entryPointsWithoutHandler > 0) lines.push(` ${entryPointsWithoutHandler} entry points without a handler`);
|
|
117
122
|
for (const warning of warnings) lines.push(` ${warning}`);
|
|
118
123
|
}
|
|
119
124
|
return lines.join("\n");
|
|
120
125
|
}
|
|
126
|
+
/** one unresolved site, the way both reports print it: where, what, why, how many transactions */
|
|
127
|
+
function describeSite(site) {
|
|
128
|
+
return `${site.file}:${site.line} ${site.expression} — ${site.reason}` + (site.transactions > 1 ? ` (${site.transactions} transactions)` : "");
|
|
129
|
+
}
|
|
121
130
|
/** `fp:explain`: a function's provenance, which is what supports a dispute */
|
|
122
131
|
/**
|
|
123
132
|
* Structure and conformance, beside the count and never instead of it.
|
|
@@ -302,7 +311,8 @@ async function runInventory(options) {
|
|
|
302
311
|
output: [
|
|
303
312
|
`data stores: ${inventory.dataStores.length}`,
|
|
304
313
|
`entry points: ${coverage.entryPointsTotal}`,
|
|
305
|
-
`coverage: ${(coverage.ratio * 100).toFixed(1)}% (${coverage.unresolvedCalls} unresolved calls)
|
|
314
|
+
`coverage: ${(coverage.ratio * 100).toFixed(1)}% (${coverage.unresolvedCalls} unresolved calls)`,
|
|
315
|
+
...inventory.unresolved.map((site) => ` ${describeSite(site)}`)
|
|
306
316
|
].join("\n")
|
|
307
317
|
};
|
|
308
318
|
}
|
|
@@ -5,7 +5,7 @@ import type { Behavior } from '../inventory/graph/call_graph.js';
|
|
|
5
5
|
import type { DiscoveredSchema } from '../inventory/sources/json_schemas.js';
|
|
6
6
|
import type { CollectedJob } from '../inventory/sources/jobs.js';
|
|
7
7
|
import type { OpaqueDeclaration } from './opaque.js';
|
|
8
|
-
import type { Complexity, CountResult, FunctionType } from '../types.js';
|
|
8
|
+
import type { Complexity, CountResult, FunctionType, UnresolvedSite } from '../types.js';
|
|
9
9
|
import type { FunctionOverride } from '../define_config.js';
|
|
10
10
|
import type { ComplexityTable } from './tables.js';
|
|
11
11
|
import type { GroupingStrategy } from './data_functions.js';
|
|
@@ -40,13 +40,19 @@ export declare const RULESET = "afp";
|
|
|
40
40
|
* Three in 1.6.0: an output's DETs are what the transaction DELIVERS (the render
|
|
41
41
|
* props, the response payload, what a command prints) read back to their origin;
|
|
42
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.
|
|
43
|
+
* FTRs move; and ace commands are transactions, with flags as input. One in
|
|
44
|
+
* 1.7.0, wide: a write binds to what the variable IS — a destructured named
|
|
45
|
+
* interface, a followed method's return, a relation off a row, a loop over rows,
|
|
46
|
+
* the guard's user, a service the container made — and a write nobody can type is
|
|
47
|
+
* an unresolved call instead of silence. Two in 1.8.0: a method the model
|
|
48
|
+
* declares names the store its caller writes, and a listener written inline on a
|
|
49
|
+
* string event is followed like a listener class.
|
|
44
50
|
*
|
|
45
51
|
* Without the bump, a baseline saved by the previous version compares cleanly
|
|
46
52
|
* against this one and bills the tool's own improvement as work done. The guard
|
|
47
53
|
* exists for exactly that, and only this constant arms it.
|
|
48
54
|
*/
|
|
49
|
-
export declare const RULESET_VERSION = "1.
|
|
55
|
+
export declare const RULESET_VERSION = "1.8.0";
|
|
50
56
|
export type CountInput = {
|
|
51
57
|
app: AppContext;
|
|
52
58
|
stores: CollectedDataStore[];
|
|
@@ -57,6 +63,12 @@ export type CountInput = {
|
|
|
57
63
|
jsonSchemas?: Map<string, DiscoveredSchema>;
|
|
58
64
|
/** the queue jobs and who dispatches each — a job no transaction reaches is reported (plan 0.7 §D) */
|
|
59
65
|
jobs?: CollectedJob[];
|
|
66
|
+
/**
|
|
67
|
+
* The unresolved call sites as the inventory lists them — one per site, route and
|
|
68
|
+
* store problems included. Given, the count shows the same number and the same list;
|
|
69
|
+
* absent, it lists the sites the behaviors show, one each.
|
|
70
|
+
*/
|
|
71
|
+
unresolved?: UnresolvedSite[];
|
|
60
72
|
/**
|
|
61
73
|
* Stores written anywhere in the application's code, reachable from an entry
|
|
62
74
|
* 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-DqPrDZfD.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-BuZNr-FE.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -97,6 +97,13 @@ export type Behavior = {
|
|
|
97
97
|
opaqueFields: string[];
|
|
98
98
|
stores: string[];
|
|
99
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Of the stores delivered raw to a page, the columns the page reads off them
|
|
102
|
+
* (plan 0.8 §D). A store absent here leaves whole; one in `unreadablePages` leaves
|
|
103
|
+
* whole because the page could not be read, and says why.
|
|
104
|
+
*/
|
|
105
|
+
pageReads: Record<string, string[]>;
|
|
106
|
+
unreadablePages: Record<string, string>;
|
|
100
107
|
trace: TraceStep[];
|
|
101
108
|
/** bodies reached, for `fp:diff` */
|
|
102
109
|
scope: ScopeEntry[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Node } from 'ts-morph';
|
|
2
2
|
import type { CallExpression, SourceFile } from 'ts-morph';
|
|
3
3
|
import type { RelationMap, StoreSymbols } from '../detectors/lucid.js';
|
|
4
|
+
import type { PageRef } from './pages.js';
|
|
4
5
|
import type { HandlerRef } from '../../types.js';
|
|
5
6
|
/**
|
|
6
7
|
* What a transaction DELIVERS — counting-decisions §6, plan 0.7 §A′.
|
|
@@ -18,7 +19,11 @@ import type { HandlerRef } from '../../types.js';
|
|
|
18
19
|
* body returns — the graph does that in `run()`, once the bodies are known; this
|
|
19
20
|
* module only says WHAT was delivered and where it came from.
|
|
20
21
|
*/
|
|
21
|
-
export type Delivery =
|
|
22
|
+
export type Delivery = DeliveredValue & {
|
|
23
|
+
/** the page or template this value was handed to, when it was a render — what the page shows of it is read there */
|
|
24
|
+
via?: PageRef;
|
|
25
|
+
};
|
|
26
|
+
type DeliveredValue =
|
|
22
27
|
/** a variable bound to a store: its columns leave */
|
|
23
28
|
{
|
|
24
29
|
kind: 'store';
|
|
@@ -38,7 +43,7 @@ export type Delivery =
|
|
|
38
43
|
args: Delivery[];
|
|
39
44
|
/**
|
|
40
45
|
* One key of what the call returns — `const { data } = await q.handle()`,
|
|
41
|
-
* `
|
|
46
|
+
* `report.rows` — rather than the whole result. Resolved against the
|
|
42
47
|
* body's classified return, keeping only that key.
|
|
43
48
|
*/
|
|
44
49
|
pick?: string;
|
|
@@ -59,7 +64,11 @@ export type Delivery =
|
|
|
59
64
|
path: string;
|
|
60
65
|
expression: string;
|
|
61
66
|
};
|
|
62
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Keys under which a paginator or a wrapper hands its rows on: picking one of these
|
|
69
|
+
* is not picking one value. Framework and JavaScript conventions only — a key named
|
|
70
|
+
* in one application's language is that application's, not a rule.
|
|
71
|
+
*/
|
|
63
72
|
export declare const PASSES_ROWS: Set<string>;
|
|
64
73
|
export type DeliveryContext = {
|
|
65
74
|
body: Node;
|
|
@@ -86,3 +95,4 @@ export type BodyDeliveries = {
|
|
|
86
95
|
* response method, and what a `return` hands back that is not one of those calls.
|
|
87
96
|
*/
|
|
88
97
|
export declare function deliveriesIn(ctx: DeliveryContext): BodyDeliveries;
|
|
98
|
+
export {};
|
|
@@ -68,7 +68,7 @@ export declare function transformerResourceOf(cls: ClassDeclaration): string | n
|
|
|
68
68
|
* { autor: AutorTransformer.transform } 0 here; the nested body contributes
|
|
69
69
|
* { endereco: { rua, cidade } } leaves individually
|
|
70
70
|
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
71
|
-
* {
|
|
71
|
+
* { items: xs.map((i) => ({ a, b })) } the leaves, once
|
|
72
72
|
* ...this.pick(this.resource, [...]) the listed names
|
|
73
73
|
* ...this.toObject() 0 here; the followed body contributes
|
|
74
74
|
* ...anythingElse 1, opaque, reported
|
|
@@ -101,7 +101,7 @@ type LeafOptions = {
|
|
|
101
101
|
* { autor: AutorTransformer.transform } 0 here; the followed body contributes
|
|
102
102
|
* { endereco: { rua, cidade } } leaves individually
|
|
103
103
|
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
104
|
-
* {
|
|
104
|
+
* { items: xs.map((i) => ({ a, b })) } the leaves, once
|
|
105
105
|
* ...this.pick(this.resource, [...]) the listed names
|
|
106
106
|
* ...this.toObject() 0 here; the followed body contributes
|
|
107
107
|
* ...anythingElse 1, opaque, reported
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Project } from 'ts-morph';
|
|
2
|
+
import type { RelationMap } from '../detectors/lucid.js';
|
|
3
|
+
import type { CollectedDataStore } from '../sources/data_stores.js';
|
|
4
|
+
/**
|
|
5
|
+
* What the page SHOWS of a store handed to it raw — plan 0.7 §B / 0.8 §D,
|
|
6
|
+
* counting-decisions §6.
|
|
7
|
+
*
|
|
8
|
+
* The delivery rule (§A′) says which store leaves; a transformer or a `.select()`
|
|
9
|
+
* says which columns. With neither, the store left whole — and the CPM defines an
|
|
10
|
+
* output's DETs by what the user sees. The page is TypeScript (`.tsx`) or Edge,
|
|
11
|
+
* and the same reader that opened the controller can open it: from the
|
|
12
|
+
* component's props to every member read off a row, one child component deep.
|
|
13
|
+
*
|
|
14
|
+
* Where it cannot read — a second level of components, a spread, a package's
|
|
15
|
+
* component, two files answering to one page name — the store leaves whole and
|
|
16
|
+
* the transaction is REPORTED with the reason. Overestimating in the open, never a
|
|
17
|
+
* floor: a floor would undercount what the user sees.
|
|
18
|
+
*/
|
|
19
|
+
export type PageRef = {
|
|
20
|
+
engine: 'inertia' | 'edge';
|
|
21
|
+
page: string;
|
|
22
|
+
};
|
|
23
|
+
/** a store handed raw to a page, at a key path of its props (`vagas.data`) */
|
|
24
|
+
export type RawDelivery = PageRef & {
|
|
25
|
+
store: string;
|
|
26
|
+
path: string;
|
|
27
|
+
};
|
|
28
|
+
export type PageReading = {
|
|
29
|
+
/** store -> columns the page reads off its rows */
|
|
30
|
+
columns: Map<string, Set<string>>;
|
|
31
|
+
/** store -> why the page could not be read for it (the store then leaves whole) */
|
|
32
|
+
unreadable: Map<string, string>;
|
|
33
|
+
/** store -> members the page reads that are NOT its columns (`member.displayName`, computed on the way) */
|
|
34
|
+
unknownMembers: Map<string, Set<string>>;
|
|
35
|
+
};
|
|
36
|
+
export type PageEnvironment = {
|
|
37
|
+
root: string;
|
|
38
|
+
project: Project;
|
|
39
|
+
stores: Map<string, CollectedDataStore>;
|
|
40
|
+
relations: RelationMap;
|
|
41
|
+
/** the application's subpath imports (`#tecnologias/ui/components/x`), as the graph resolves them */
|
|
42
|
+
resolveSpecifier: (specifier: string) => string | null;
|
|
43
|
+
};
|
|
44
|
+
export declare function readPages(deliveries: RawDelivery[], env: PageEnvironment): PageReading;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as resolveCall, n as ignoreCalls, r as isTechnicalWrite, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-
|
|
1
|
+
import { i as resolveCall, n as ignoreCalls, r as isTechnicalWrite, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-CneCj3sT.js";
|
|
2
2
|
export { BUILTIN_CALL_RESOLVERS, ignoreCalls, isTechnicalWrite, resolveCall };
|
|
@@ -4,7 +4,7 @@ import type { CallResolver } from './types.js';
|
|
|
4
4
|
* "Local function" pattern: a helper declared in the same file, not imported.
|
|
5
5
|
*
|
|
6
6
|
* const lista = await proximos(id) // function proximos() { … }
|
|
7
|
-
* return rows.map(
|
|
7
|
+
* return rows.map(toRow) // const toRow = (row) => …
|
|
8
8
|
*
|
|
9
9
|
* A query object that keeps its helpers beside it is common, and before this
|
|
10
10
|
* every such call was unresolved: the store a helper read was reached by nobody
|
|
@@ -15,8 +15,8 @@ import type { CallResolver } from './types.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const localFunctionResolver: CallResolver;
|
|
17
17
|
/**
|
|
18
|
-
* The function a call names: `proximos(id)` names `proximos`; `rows.map(
|
|
19
|
-
* names `
|
|
18
|
+
* The function a call names: `proximos(id)` names `proximos`; `rows.map(toRow)`
|
|
19
|
+
* names `toRow`, called once per row — the body the graph must read is the
|
|
20
20
|
* same, whichever way it was reached.
|
|
21
21
|
*/
|
|
22
22
|
export declare function calledFunctionOf(call: CallExpression): import('ts-morph').Identifier | null;
|
|
@@ -41,4 +41,7 @@ export declare function collectEventBindings(app: AppContext): EventBindings;
|
|
|
41
41
|
* this" would drift.
|
|
42
42
|
*/
|
|
43
43
|
export declare function resolveEventClass(expression: Expression | Node, from: SourceFile, app: SpecifierResolver): string | null;
|
|
44
|
+
/** listener bodies named by the second argument of `emitter.on` */
|
|
45
|
+
/** the binding key of a string event: `emitter.on('order:closed', …)` */
|
|
46
|
+
export declare const eventKey: (name: string) => string;
|
|
44
47
|
export {};
|
package/build/src/pipeline.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as analyze, t as CoverageTooLowError } from "../pipeline-
|
|
1
|
+
import { n as analyze, t as CoverageTooLowError } from "../pipeline-DqPrDZfD.js";
|
|
2
2
|
export { CoverageTooLowError, analyze };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { CountResult, CountedFunction, Inventory } from '../types.js';
|
|
1
|
+
import type { CountResult, CountedFunction, Inventory, UnresolvedSite } from '../types.js';
|
|
2
2
|
import type { FunctionPointDiff } from '../albrecht/diff.js';
|
|
3
3
|
import type { Conformance, StructureMetrics } from '../metrics/structure.js';
|
|
4
4
|
export declare function renderCount(result: CountResult): string;
|
|
5
|
+
/** one unresolved site, the way both reports print it: where, what, why, how many transactions */
|
|
6
|
+
export declare function describeSite(site: UnresolvedSite): string;
|
|
5
7
|
/** `fp:explain`: a function's provenance, which is what supports a dispute */
|
|
6
8
|
/**
|
|
7
9
|
* Structure and conformance, beside the count and never instead of it.
|
package/build/src/types.d.ts
CHANGED
|
@@ -144,6 +144,10 @@ export type HandlerBehavior = {
|
|
|
144
144
|
opaqueFields: string[];
|
|
145
145
|
stores: string[];
|
|
146
146
|
};
|
|
147
|
+
/** of the stores delivered raw to a page, the columns the page reads off them (§6, plan 0.8 §D) */
|
|
148
|
+
pageReads?: Record<string, string[]>;
|
|
149
|
+
/** stores a page could not be read for, and why: they leave whole */
|
|
150
|
+
unreadablePages?: Record<string, string>;
|
|
147
151
|
/**
|
|
148
152
|
* How each store was read: rows whole, `.select()` columns, or one aggregate
|
|
149
153
|
* scalar; by its own chain (`direct`) or preloaded through another store (`via`).
|
|
@@ -186,6 +190,11 @@ export type UnresolvedCall = {
|
|
|
186
190
|
expression: string;
|
|
187
191
|
reason: string;
|
|
188
192
|
};
|
|
193
|
+
/** an unresolved call as the reports list it: one site, however many transactions reach it */
|
|
194
|
+
export type UnresolvedSite = UnresolvedCall & {
|
|
195
|
+
/** transactions whose walk reached this call; 0 for a route or store problem */
|
|
196
|
+
transactions: number;
|
|
197
|
+
};
|
|
189
198
|
export type Inventory = {
|
|
190
199
|
/** format version, for diffs across releases */
|
|
191
200
|
version: 1;
|
|
@@ -203,10 +212,16 @@ export type Inventory = {
|
|
|
203
212
|
coverage: {
|
|
204
213
|
entryPointsTotal: number;
|
|
205
214
|
entryPointsResolved: number;
|
|
215
|
+
/** distinct call sites nobody could follow, route and store problems included — the number the count shows too */
|
|
206
216
|
unresolvedCalls: number;
|
|
207
217
|
/** fraction of entry points whose handler was traced to completion */
|
|
208
218
|
ratio: number;
|
|
209
219
|
};
|
|
220
|
+
/**
|
|
221
|
+
* The unresolved calls, listed: what `fp:inventory` prints and what a person acts
|
|
222
|
+
* on. One entry per site — a body five routes reach is one gap, not five.
|
|
223
|
+
*/
|
|
224
|
+
unresolved: UnresolvedSite[];
|
|
210
225
|
};
|
|
211
226
|
export type FunctionType = 'ILF' | 'EIF' | 'EI' | 'EO' | 'EQ';
|
|
212
227
|
export type Complexity = 'low' | 'average' | 'high';
|
|
@@ -279,9 +294,12 @@ export type CountResult = {
|
|
|
279
294
|
};
|
|
280
295
|
/** flags when the count does not deserve confidence */
|
|
281
296
|
confidence: {
|
|
297
|
+
/** distinct call sites nobody could follow — the same number the inventory shows */
|
|
282
298
|
unresolvedCalls: number;
|
|
283
299
|
entryPointsWithoutHandler: number;
|
|
284
300
|
warnings: string[];
|
|
301
|
+
/** the sites themselves, so the report can list them; absent on a count built by hand */
|
|
302
|
+
unresolved?: UnresolvedSite[];
|
|
285
303
|
};
|
|
286
304
|
};
|
|
287
305
|
/** Maintenance type, for enhancement-project counting. */
|