@filipebraida/adonis-function-points 0.6.0 → 0.8.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +1 -1
  3. package/build/commands/main.js +6 -6
  4. package/build/{fp_calibrate-EAuAtdbq.js → fp_calibrate-B_oD9b02.js} +1 -1
  5. package/build/{fp_count-CZ0cUUBQ.js → fp_count-B_KPNKMO.js} +1 -1
  6. package/build/{fp_diff-BTg_LX0r.js → fp_diff-B3NhXzrb.js} +1 -1
  7. package/build/{fp_explain-D6QvDLKQ.js → fp_explain-C2s6Kpaj.js} +1 -1
  8. package/build/{fp_inventory-C43fU39x.js → fp_inventory-B322MvZT.js} +1 -1
  9. package/build/{fp_metrics-DEMPk4xC.js → fp_metrics-DjADb7F6.js} +1 -1
  10. package/build/index.js +2 -2
  11. package/build/{pipeline-Cq4dNTNE.js → pipeline-C6kHKB9-.js} +2363 -114
  12. package/build/{resolvers-DlKJOZnk.js → resolvers-DhJO-qvQ.js} +339 -151
  13. package/build/{runners-FYmPIPub.js → runners-BpBJsGMj.js} +2 -2
  14. package/build/src/albrecht/counter.d.ts +12 -1
  15. package/build/src/cli.js +2 -2
  16. package/build/src/inventory/graph/call_graph.d.ts +22 -0
  17. package/build/src/inventory/graph/deliveries.d.ts +94 -0
  18. package/build/src/inventory/graph/output_fields.d.ts +45 -1
  19. package/build/src/inventory/graph/pages.d.ts +44 -0
  20. package/build/src/inventory/resolvers/index.js +1 -1
  21. package/build/src/inventory/resolvers/job_dispatch.d.ts +20 -0
  22. package/build/src/inventory/resolvers/local_function.d.ts +24 -0
  23. package/build/src/inventory/resolvers/transformer.d.ts +0 -23
  24. package/build/src/inventory/sources/commands.d.ts +14 -0
  25. package/build/src/inventory/sources/jobs.d.ts +27 -0
  26. package/build/src/pipeline.js +1 -1
  27. package/build/src/types.d.ts +23 -0
  28. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ import { Node } from 'ts-morph';
2
+ import type { CallExpression, SourceFile } from 'ts-morph';
3
+ import type { RelationMap, StoreSymbols } from '../detectors/lucid.js';
4
+ import type { PageRef } from './pages.js';
5
+ import type { HandlerRef } from '../../types.js';
6
+ /**
7
+ * What a transaction DELIVERS — counting-decisions §6, plan 0.7 §A′.
8
+ *
9
+ * The DETs of an output are the fields that cross the boundary, and the place
10
+ * they cross is the delivery: the props handed to `inertia.render` /
11
+ * `inertia.modal` / `view.render`, the payload of `response.json|ok|created|send`,
12
+ * a literal the handler returns. Before this, "what leaves" was every column of
13
+ * every store the transaction touched — a `findOrFail` made to authorise handed
14
+ * the whole table to the screen, and a derived `total` handed nothing.
15
+ *
16
+ * Measured on three applications first: no delivered collection is a variable
17
+ * bound to a store; they come out of query objects the graph already follows.
18
+ * So a delivered value that comes from a followed call is resolved to what that
19
+ * body returns — the graph does that in `run()`, once the bodies are known; this
20
+ * module only says WHAT was delivered and where it came from.
21
+ */
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 =
27
+ /** a variable bound to a store: its columns leave */
28
+ {
29
+ kind: 'store';
30
+ store: string;
31
+ path: string;
32
+ }
33
+ /**
34
+ * The result of a call a strategy followed: what that body returns leaves.
35
+ * `args` are the call's arguments, classified — a body that returns no literal
36
+ * and reads no store (a CSV builder) delivers what was handed INTO it.
37
+ */
38
+ | {
39
+ kind: 'call';
40
+ refs: HandlerRef[];
41
+ path: string;
42
+ expression: string;
43
+ args: Delivery[];
44
+ /**
45
+ * One key of what the call returns — `const { data } = await q.handle()`,
46
+ * `relatorio.linhas` — rather than the whole result. Resolved against the
47
+ * body's classified return, keeping only that key.
48
+ */
49
+ pick?: string;
50
+ }
51
+ /** a scalar, a property, an expression: one DET */
52
+ | {
53
+ kind: 'scalar';
54
+ path: string;
55
+ }
56
+ /** an input echoed back — the validated payload, a field read off the request: counts once, on entry */
57
+ | {
58
+ kind: 'echo';
59
+ path: string;
60
+ }
61
+ /** something the classifier cannot read: one DET as a floor, reported */
62
+ | {
63
+ kind: 'opaque';
64
+ path: string;
65
+ expression: string;
66
+ };
67
+ /** keys of a result that carry its rows: picking one of these is not picking one value */
68
+ export declare const PASSES_ROWS: Set<string>;
69
+ export type DeliveryContext = {
70
+ body: Node;
71
+ file: SourceFile;
72
+ symbols: StoreSymbols;
73
+ relations: RelationMap;
74
+ followed: Map<CallExpression, HandlerRef[]>;
75
+ };
76
+ export type BodyDeliveries = {
77
+ /** props handed to a renderer or a response method, anywhere in the body */
78
+ calls: Delivery[];
79
+ /** a delivery call was found, even with nothing readable in it */
80
+ anyCall: boolean;
81
+ /**
82
+ * What a `return` hands back that is not one of those calls — meaningful for
83
+ * the ENTRY body only, where returning a value is delivering it; a followed
84
+ * body's return is read by `returnedLeavesOf` instead.
85
+ */
86
+ returns: Delivery[];
87
+ anyReturn: boolean;
88
+ };
89
+ /**
90
+ * The deliveries of a body: every props argument handed to a renderer or a
91
+ * response method, and what a `return` hands back that is not one of those calls.
92
+ */
93
+ export declare function deliveriesIn(ctx: DeliveryContext): BodyDeliveries;
94
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { Node } from 'ts-morph';
2
- import type { CallExpression, ClassDeclaration } from 'ts-morph';
2
+ import type { CallExpression, ClassDeclaration, Expression, ObjectLiteralExpression } from 'ts-morph';
3
3
  import type { CollectedDataStore } from '../sources/data_stores.js';
4
4
  /**
5
5
  * What an output transaction actually EMITS — counting-decisions §6.
@@ -77,6 +77,49 @@ export declare function transformerResourceOf(cls: ClassDeclaration): string | n
77
77
  * the same reason `isPrimary` is not one on the data function.
78
78
  */
79
79
  export declare function outputFieldsIn(body: Node, owner: ClassDeclaration | undefined, stores: Map<string, CollectedDataStore>, followed: (call: CallExpression) => boolean): OutputFacts;
80
+ /**
81
+ * The leaves of the object literal ANY body returns — a query object building a
82
+ * summary, a module function assembling a view model. Same walker as the
83
+ * transformer's, unqualified: the caller prefixes the path the value was
84
+ * delivered under. `null` when the body returns no literal.
85
+ */
86
+ export declare function returnedLeavesOf(body: Node, followed: (call: CallExpression) => boolean): {
87
+ leaves: string[];
88
+ opaque: string[];
89
+ } | null;
90
+ type LeafOptions = {
91
+ /** prefixed to every leaf, with a dot; empty for an unqualified walk */
92
+ qualifier: string;
93
+ /** top-level names that are not DETs (the resource's key, its stamps) */
94
+ excluded: Set<string>;
95
+ followed: (call: CallExpression) => boolean;
96
+ };
97
+ /**
98
+ * The leaves of object literals, by the §7 rules:
99
+ *
100
+ * { titulo: l.titulo } 1 — `titulo`
101
+ * { autor: AutorTransformer.transform } 0 here; the followed body contributes
102
+ * { endereco: { rua, cidade } } leaves individually
103
+ * { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
104
+ * { itens: xs.map((i) => ({ a, b })) } the leaves, once
105
+ * ...this.pick(this.resource, [...]) the listed names
106
+ * ...this.toObject() 0 here; the followed body contributes
107
+ * ...anythingElse 1, opaque, reported
108
+ */
109
+ export declare function collectLeaves(literals: ObjectLiteralExpression[], options: LeafOptions): {
110
+ leaves: string[];
111
+ opaque: string[];
112
+ };
113
+ /**
114
+ * Object literals the body itself returns — not the ones returned by arrow
115
+ * functions inside it, which belong to `.map()` callbacks and are read as
116
+ * repeating groups where they occur.
117
+ */
118
+ export declare function returnedLiteralsOf(body: Node): ObjectLiteralExpression[];
119
+ /** `xs.map((x) => ({ a, b }))` -> the literal; null for a scalar map or anything else */
120
+ export declare function mappedLiteralOf(value: Expression): ObjectLiteralExpression | null;
121
+ /** strips parentheses, `as`, `satisfies` and non-null assertions */
122
+ export declare function unwrap(node: Node | undefined | null): Expression | null;
80
123
  /**
81
124
  * The shape of the chain an access belongs to — read from the WHOLE chain, root
82
125
  * to end, because every call on it is detected as an access and each must reach
@@ -97,3 +140,4 @@ export type ChainShape = {
97
140
  }[];
98
141
  };
99
142
  export declare function chainShapeOf(access: CallExpression): ChainShape;
143
+ export {};
@@ -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 (`inventor.nomeCompleto`, 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-DlKJOZnk.js";
1
+ import { i as resolveCall, n as ignoreCalls, r as isTechnicalWrite, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-DhJO-qvQ.js";
2
2
  export { BUILTIN_CALL_RESOLVERS, ignoreCalls, isTechnicalWrite, resolveCall };
@@ -1,4 +1,24 @@
1
1
  import type { CallResolver } from './types.js';
2
+ export declare const DISPATCH_METHODS: Set<string>;
3
+ /**
4
+ * The method that actually runs the job, by queue package.
5
+ *
6
+ * There is no single name, and this list grew twice by measurement rather than by
7
+ * reasoning. `@rlanz/bull-queue` uses `handle`; `@nemoventures/adonis-jobs` calls
8
+ * it `process`; `@adonisjs/queue` — the official package — generates
9
+ * `async execute()` in its own `make:job` stub. Each omission cost the same: the
10
+ * file resolved, no body was found, the dispatch was reported as an unknown, and
11
+ * every write inside the job went uncounted.
12
+ *
13
+ * `execute` surfaced only once event dispatch started being followed, because the
14
+ * listener was what enqueued the job and that path had never been walked. Which is
15
+ * the argument for adding a name when a real application shows it: a list written
16
+ * from imagination would have missed this one too.
17
+ *
18
+ * Ordered: a class declaring more than one is answering the dispatcher with the
19
+ * first, and `handle` is the most common.
20
+ */
21
+ export declare const EXECUTION_METHODS: readonly ["handle", "execute", "process", "run", "perform"];
2
22
  /**
3
23
  * "Job" pattern: the write happens asynchronously.
4
24
  *
@@ -0,0 +1,24 @@
1
+ import type { CallExpression } from 'ts-morph';
2
+ import type { CallResolver } from './types.js';
3
+ /**
4
+ * "Local function" pattern: a helper declared in the same file, not imported.
5
+ *
6
+ * const lista = await proximos(id) // function proximos() { … }
7
+ * return rows.map(paraLinha) // const paraLinha = (row) => …
8
+ *
9
+ * A query object that keeps its helpers beside it is common, and before this
10
+ * every such call was unresolved: the store a helper read was reached by nobody
11
+ * from that route, and a value built by it left as a whole table handed in.
12
+ *
13
+ * Only module-level declarations: a closure declared inside a body is part of
14
+ * that body, and the graph already walks it.
15
+ */
16
+ export declare const localFunctionResolver: CallResolver;
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
20
+ * same, whichever way it was reached.
21
+ */
22
+ export declare function calledFunctionOf(call: CallExpression): import('ts-morph').Identifier | null;
23
+ /** `function name() {}` or `const name = () => {}` / `function () {}` at module level */
24
+ export declare function declaresFunction(file: import('ts-morph').SourceFile, name: string): boolean;
@@ -1,25 +1,2 @@
1
1
  import type { CallResolver } from './types.js';
2
- /**
3
- * "Transformer" pattern: the package supplies the API, the application
4
- * supplies the body.
5
- *
6
- * class InviteTransformer extends BaseTransformer<Invite> {
7
- * toObject() { … }
8
- * }
9
- *
10
- * InviteTransformer.transform(invite)
11
- *
12
- * `transform()` and `paginate()` live in `@adonisjs/core`, so resolving the
13
- * symbol lands on the application file and finds no body there. The naive
14
- * reading is that the tracer must step into node_modules; it does not. Those
15
- * methods call BACK into `toObject()`, which the application writes, so the
16
- * body worth analysing was in the application all along.
17
- *
18
- * It is the same shape as `job-dispatch`, where `dispatch` enqueues and
19
- * `handle` executes.
20
- *
21
- * COUNTING DECISION: the write a transformer performs belongs to the
22
- * transaction that serialised through it. Without this, a table written only
23
- * inside `toObject()` is reached by nobody and drops out under AFP §6.5.4.
24
- */
25
2
  export declare const transformerResolver: CallResolver;
@@ -0,0 +1,14 @@
1
+ import type { ClassDeclaration } from 'ts-morph';
2
+ import type { AppContext } from '../app_context.js';
3
+ import type { CollectedEntryPoint } from './routes_ast.js';
4
+ export declare const GENERATES_DATA_HINT = "imports @faker-js/faker: generates data, probably a development tool";
5
+ export declare function collectCommands(app: AppContext): CollectedEntryPoint[];
6
+ /**
7
+ * The input DETs of a command: its `@flags.*` and `@args.*`, named as the
8
+ * operator types them — `flagName` / `argumentName` when given, the property
9
+ * otherwise. Returned as `flags.<name>` / `args.<name>` so the counter can say
10
+ * which is which.
11
+ */
12
+ export declare function commandFieldsOf(cls: ClassDeclaration): string[];
13
+ /** is this class an ace command? — the graph asks, to read its flags as input */
14
+ export declare function isCommandClass(cls: ClassDeclaration): boolean;
@@ -0,0 +1,27 @@
1
+ import type { AppContext } from '../app_context.js';
2
+ /**
3
+ * The application's queue jobs, and who dispatches each — plan 0.7 §D.
4
+ *
5
+ * A job dispatched by a handler is part of that handler's transaction (§9): the
6
+ * user clicks, the effect happens, asynchronously or not. A job that NO
7
+ * transaction reaches is one of two things, and the code cannot say which:
8
+ *
9
+ * scheduled `PodarAuditoriaJob.schedule({}).cron('0 3 * * *')` in a start
10
+ * file — an elementary process nobody is counting
11
+ * dead code dispatched by nothing at all
12
+ *
13
+ * Reported, not counted: inventing an elementary process is the error this
14
+ * package exists to avoid. When a scheduler appears on a real application, it
15
+ * becomes an entry point `job:<Class>` with the identity §5 already decided.
16
+ */
17
+ export type CollectedJob = {
18
+ /** the class name */
19
+ name: string;
20
+ /** absolute, posix */
21
+ file: string;
22
+ /** files that dispatch it (`X.dispatch(…)`), absolute posix — inside a transaction or not */
23
+ dispatchedFrom: string[];
24
+ /** files that schedule it (`X.schedule({}).cron(…)`): a scheduler, which no transaction is */
25
+ scheduledFrom: string[];
26
+ };
27
+ export declare function collectJobs(app: AppContext): CollectedJob[];
@@ -1,2 +1,2 @@
1
- import { n as analyze, t as CoverageTooLowError } from "../pipeline-Cq4dNTNE.js";
1
+ import { n as analyze, t as CoverageTooLowError } from "../pipeline-C6kHKB9-.js";
2
2
  export { CoverageTooLowError, analyze };
@@ -74,6 +74,12 @@ export type EntryPoint = {
74
74
  /** route name when present; not the identity used across versions */
75
75
  name?: string;
76
76
  handler: HandlerRef | null;
77
+ /**
78
+ * What the collector noticed and the counter cannot decide: an ace command that
79
+ * imports a data generator is probably a development tool. Printed beside the
80
+ * function, so a person excludes it with `boundary.ignoreEntryPoints`.
81
+ */
82
+ hints?: string[];
77
83
  provenance: Provenance;
78
84
  };
79
85
  export type HandlerRef = {
@@ -114,6 +120,12 @@ export type HandlerBehavior = {
114
120
  * user-recognisable field crossing the boundary, which is §7.2's definition.
115
121
  */
116
122
  requestFields: Field[];
123
+ /**
124
+ * The `@flags.*` / `@args.*` an ace command declares — its input DETs, exact.
125
+ * Named `flags.<name>` / `args.<name>`, as the operator types them. Absent on
126
+ * an HTTP transaction.
127
+ */
128
+ commandFields?: Field[];
117
129
  /** the transaction reads the request in a way that enumerates nothing */
118
130
  opaqueRequest: boolean;
119
131
  /**
@@ -125,6 +137,17 @@ export type HandlerBehavior = {
125
137
  opaqueOutputFields: Field[];
126
138
  /** stores a transformer on the path is for: their keys leave, not their columns */
127
139
  transformedStores: string[];
140
+ /** what the transaction delivers: derived fields, raw stores, values nobody could read */
141
+ delivered: {
142
+ any: boolean;
143
+ fields: string[];
144
+ opaqueFields: string[];
145
+ stores: string[];
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>;
128
151
  /**
129
152
  * How each store was read: rows whole, `.select()` columns, or one aggregate
130
153
  * scalar; by its own chain (`direct`) or preloaded through another store (`via`).
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.6.0",
4
+ "version": "0.8.0",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },