@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.
@@ -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
- const init = (receiver.getSymbol()?.getDeclarations().find((d) => d.isKind(SyntaxKind.VariableDeclaration)))?.asKind(SyntaxKind.VariableDeclaration)?.getInitializer();
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
- const eventFile = resolveEventClass(event, file, app);
410
- if (!eventFile) continue;
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(eventFile, [...bindings.get(eventFile) ?? [], ...refs]);
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(paraLinha) // const paraLinha = (row) => …
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(paraLinha)` calls `paraLinha` */
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(paraLinha)`
665
- * names `paraLinha`, called once per row — the body the graph must read is the
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
- * { itens: xs.map((i) => ({ a, b })) } the leaves, once
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
- * { itens: xs.map((i) => ({ a, b })) } the leaves, once
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('forEgresso')`: the variant is a METHOD of the
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-DaU4uAqT.js";
3
- import { n as analyze } from "./pipeline-DO2301fV.js";
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) lines.push(` ${unresolvedCalls} unresolved calls`);
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.6.0";
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-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";
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
- * `relatorio.linhas` — rather than the whole result. Resolved against the
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
- /** keys of a result that carry its rows: picking one of these is not picking one value */
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
- * { itens: xs.map((i) => ({ a, b })) } the leaves, once
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
- * { itens: xs.map((i) => ({ a, b })) } the leaves, once
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-DaU4uAqT.js";
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(paraLinha) // const paraLinha = (row) => …
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(paraLinha)`
19
- * names `paraLinha`, called once per row — the body the graph must read is the
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 {};
@@ -1,2 +1,2 @@
1
- import { n as analyze, t as CoverageTooLowError } from "../pipeline-DO2301fV.js";
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.
@@ -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. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@filipebraida/adonis-function-points",
3
3
  "description": "Automated function point counting and code metrics for AdonisJS applications.",
4
- "version": "0.7.0",
4
+ "version": "0.9.0",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },