@filipebraida/adonis-function-points 0.3.0 → 0.5.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 +113 -0
- package/README.md +17 -6
- package/build/commands/main.js +6 -6
- package/build/{fp_calibrate-iFAec0tA.js → fp_calibrate-DUbHiifm.js} +1 -1
- package/build/{fp_count-D21tQ_pv.js → fp_count-ChtblhZV.js} +1 -1
- package/build/{fp_diff-D0pHGMgi.js → fp_diff-Dt7J4IWu.js} +1 -1
- package/build/{fp_explain-BwFs-LW-.js → fp_explain-DZJ--0-S.js} +1 -1
- package/build/{fp_inventory-Bu6O1Nn0.js → fp_inventory-CPtmuuke.js} +1 -1
- package/build/{fp_metrics-MGDppfSa.js → fp_metrics-et8F1Wvt.js} +1 -1
- package/build/index.js +2 -2
- package/build/{pipeline-CIAydCcT.js → pipeline-CNTBhs6o.js} +442 -79
- package/build/{resolvers-CRB6lXoo.js → resolvers-PJwo2Z8R.js} +64 -1
- package/build/{runners-CmxNHuuq.js → runners-DIt1G85i.js} +19 -4
- package/build/src/albrecht/counter.d.ts +1 -1
- package/build/src/albrecht/transactional_functions.d.ts +9 -0
- package/build/src/cli.js +2 -2
- package/build/src/define_config.d.ts +26 -1
- package/build/src/inventory/graph/call_graph.d.ts +10 -0
- package/build/src/inventory/paths.d.ts +18 -0
- package/build/src/inventory/resolvers/index.d.ts +7 -0
- package/build/src/inventory/resolvers/index.js +2 -2
- package/build/src/inventory/resolvers/types.d.ts +19 -0
- package/build/src/inventory/source.d.ts +8 -1
- package/build/src/pipeline.js +1 -1
- package/build/src/types.d.ts +2 -0
- package/build/stubs/config.stub +7 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
2
3
|
//#region src/inventory/paths.ts
|
|
3
4
|
/**
|
|
@@ -20,8 +21,61 @@ import { Node, Project, SyntaxKind } from "ts-morph";
|
|
|
20
21
|
* it to each comparison costs vigilance forever.
|
|
21
22
|
*/
|
|
22
23
|
const toPosix = (value) => value.split("\\").join("/");
|
|
24
|
+
/**
|
|
25
|
+
* A path as it should appear in an EMITTED artefact: relative to the application.
|
|
26
|
+
*
|
|
27
|
+
* `CountSource.app` is documented as never being the absolute path, because that
|
|
28
|
+
* says where the machine keeps its files and travels with every count sent
|
|
29
|
+
* anywhere. One field below it, `config` shipped the absolute path — and so did
|
|
30
|
+
* every `trace[].file`, 858 times in a single production count. The rule was
|
|
31
|
+
* stated and then applied to one field.
|
|
32
|
+
*
|
|
33
|
+
* Internally the absolute path is the right thing: it is what ts-morph resolves
|
|
34
|
+
* and what the call graph keys its caches on. So this converts at the boundary
|
|
35
|
+
* where a path LEAVES, and nowhere else.
|
|
36
|
+
*
|
|
37
|
+
* A path outside the root keeps its `../` prefix, which describes where it is
|
|
38
|
+
* without naming the home directory.
|
|
39
|
+
*/
|
|
40
|
+
const relativeTo = (root, value) => toPosix(path.relative(toPosix(root), toPosix(value))) || ".";
|
|
23
41
|
/** Compares two paths that may have come from different sources. */
|
|
24
42
|
const samePath = (a, b) => a !== void 0 && b !== void 0 && toPosix(a) === toPosix(b);
|
|
43
|
+
/**
|
|
44
|
+
* Is this file the application's own code, as opposed to the scaffolding around it?
|
|
45
|
+
*
|
|
46
|
+
* The top-level filter on `scanRoots` already drops `tests/`, `database/` and the
|
|
47
|
+
* rest — but only at the ROOT. Applications organised by domain module put both
|
|
48
|
+
* inside `app/`:
|
|
49
|
+
*
|
|
50
|
+
* app/billing/tests/functional/invoice.spec.ts
|
|
51
|
+
* app/billing/seeders/plan_seeder.ts
|
|
52
|
+
*
|
|
53
|
+
* so they land in the project, and `writtenAnywhere()` read a seeder's inserts as
|
|
54
|
+
* the application maintaining the table. A reference table only the seed populates
|
|
55
|
+
* came out as an ILF — which the CPM does not allow: data maintained by the
|
|
56
|
+
* development team is at most an EIF, and code data is not counted at all.
|
|
57
|
+
*
|
|
58
|
+
* The segments are AdonisJS's own: `make:test` writes to a suite directory,
|
|
59
|
+
* `make:seeder` to `seeders`, `make:migration` to `migrations`, `make:factory` to
|
|
60
|
+
* `factories`. The `.spec`/`.test` suffixes come from the suite globs in
|
|
61
|
+
* `adonisrc.ts`.
|
|
62
|
+
*/
|
|
63
|
+
const SCAFFOLDING = new Set([
|
|
64
|
+
"tests",
|
|
65
|
+
"test",
|
|
66
|
+
"seeders",
|
|
67
|
+
"seeder",
|
|
68
|
+
"migrations",
|
|
69
|
+
"factories"
|
|
70
|
+
]);
|
|
71
|
+
function isApplicationCode(root, file) {
|
|
72
|
+
const relative = relativeTo(root, file);
|
|
73
|
+
if (relative.startsWith("..")) return false;
|
|
74
|
+
const parts = relative.split("/");
|
|
75
|
+
const name = parts.at(-1) ?? "";
|
|
76
|
+
if (/\.(spec|test)\.[jt]s$/.test(name)) return false;
|
|
77
|
+
return !parts.slice(0, -1).some((segment) => SCAFFOLDING.has(segment));
|
|
78
|
+
}
|
|
25
79
|
//#endregion
|
|
26
80
|
//#region src/inventory/sources/event_bindings.ts
|
|
27
81
|
/** the method a listener declares; AdonisJS calls `handle` unless told otherwise */
|
|
@@ -799,6 +853,15 @@ const BUILTIN_CALL_RESOLVERS = [
|
|
|
799
853
|
* them apart. Hence specific strategies declare a lower `order` than generic
|
|
800
854
|
* ones, and `module-function` comes last — it would match almost anything.
|
|
801
855
|
*/
|
|
856
|
+
/**
|
|
857
|
+
* Does any strategy call this a technical write?
|
|
858
|
+
*
|
|
859
|
+
* Asked separately from resolution, because the strategy that recognises the call as
|
|
860
|
+
* incidental is not necessarily the one that knows where it goes.
|
|
861
|
+
*/
|
|
862
|
+
function isTechnicalWrite(call, ctx, resolvers = BUILTIN_CALL_RESOLVERS) {
|
|
863
|
+
return resolvers.some((resolver) => resolver.technicalWrite?.(call, ctx) === true);
|
|
864
|
+
}
|
|
802
865
|
function resolveCall(call, ctx, resolvers = BUILTIN_CALL_RESOLVERS) {
|
|
803
866
|
for (const resolver of resolvers) {
|
|
804
867
|
/**
|
|
@@ -819,4 +882,4 @@ function resolveCall(call, ctx, resolvers = BUILTIN_CALL_RESOLVERS) {
|
|
|
819
882
|
return null;
|
|
820
883
|
}
|
|
821
884
|
//#endregion
|
|
822
|
-
export {
|
|
885
|
+
export { hooksFiredBy as a, isApplicationCode as c, toPosix as d, detectAccess as i, relativeTo as l, isTechnicalWrite as n, rootSymbolOf as o, resolveCall as r, collectEventBindings as s, BUILTIN_CALL_RESOLVERS as t, samePath as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as diffCounts, i as measureStructure, l as DEFAULTS, n as parseSamples, o as IncomparableRulesetsError, r as measureConformance, s as IncomparableSourcesError, t as calibrate, u as defineConfig } from "./calibration-8eV8CEix.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as analyze } from "./pipeline-
|
|
2
|
+
import { d as toPosix } from "./resolvers-PJwo2Z8R.js";
|
|
3
|
+
import { n as analyze } from "./pipeline-CNTBhs6o.js";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
@@ -91,7 +91,12 @@ function renderCount(result) {
|
|
|
91
91
|
* habit: if it grows, the count comes from a spreadsheet and the tool loses
|
|
92
92
|
* its reason to exist. Printing the share is what keeps that visible.
|
|
93
93
|
*/
|
|
94
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Only entries that DECLARED a number. A review records a decision and declares
|
|
96
|
+
* nothing, so counting it here would read as "35% of the total declared by
|
|
97
|
+
* override" about a count nobody touched.
|
|
98
|
+
*/
|
|
99
|
+
const overridden = result.functions.filter((fn) => fn.rationale.overrides?.some((o) => o.fields.length > 0));
|
|
95
100
|
if (overridden.length > 0) {
|
|
96
101
|
const points = overridden.reduce((total, fn) => total + fn.points, 0);
|
|
97
102
|
const share = (points / (result.totals.unadjusted || 1) * 100).toFixed(1);
|
|
@@ -359,7 +364,17 @@ async function runMetrics(options) {
|
|
|
359
364
|
async function runExplain(options) {
|
|
360
365
|
const { config, notes } = await configFor(options.root);
|
|
361
366
|
const { count } = await analyze(options.root, config);
|
|
362
|
-
|
|
367
|
+
/**
|
|
368
|
+
* An exact name wins outright; the substring search is the fallback.
|
|
369
|
+
*
|
|
370
|
+
* `fp:explain "POST /orders/:param/submit"` returned four functions, because
|
|
371
|
+
* `/submit`, `/submit-ready` and `/submit-ready/return` all contain it. Asking
|
|
372
|
+
* about a function by its exact name and being handed its neighbours makes the
|
|
373
|
+
* command useless for the thing it exists for — defending one number.
|
|
374
|
+
*/
|
|
375
|
+
const wanted = options.name.toLowerCase();
|
|
376
|
+
const exact = count.functions.filter((fn) => fn.name.toLowerCase() === wanted);
|
|
377
|
+
const matched = exact.length > 0 ? exact : count.functions.filter((fn) => fn.name.toLowerCase().includes(wanted));
|
|
363
378
|
if (matched.length === 0) return {
|
|
364
379
|
output: "",
|
|
365
380
|
notes,
|
|
@@ -34,7 +34,7 @@ export declare const RULESET = "afp";
|
|
|
34
34
|
* against this one and bills the tool's own improvement as work done. The guard
|
|
35
35
|
* exists for exactly that, and only this constant arms it.
|
|
36
36
|
*/
|
|
37
|
-
export declare const RULESET_VERSION = "1.
|
|
37
|
+
export declare const RULESET_VERSION = "1.4.0";
|
|
38
38
|
export type CountInput = {
|
|
39
39
|
app: AppContext;
|
|
40
40
|
stores: CollectedDataStore[];
|
|
@@ -31,5 +31,14 @@ export type TransactionOptions = {
|
|
|
31
31
|
messageDet: number;
|
|
32
32
|
tables: Record<FunctionType, ComplexityTable>;
|
|
33
33
|
weights: Record<FunctionType, Record<Complexity, number>>;
|
|
34
|
+
/**
|
|
35
|
+
* Application root, used only to relativise the paths that LEAVE in the trace.
|
|
36
|
+
*
|
|
37
|
+
* `CountSource.app` is documented as never being the absolute path, because it
|
|
38
|
+
* says where the machine keeps its files and travels with every count sent
|
|
39
|
+
* anywhere. The trace shipped the absolute path regardless — 858 times in a
|
|
40
|
+
* single production count, which is most of the artefact a ledger would store.
|
|
41
|
+
*/
|
|
42
|
+
root: string;
|
|
34
43
|
};
|
|
35
44
|
export declare function countTransactionalFunctions(entryPoints: CollectedEntryPoint[], behaviors: Map<string, Behavior>, options: TransactionOptions): CountedFunction[];
|
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-CNTBhs6o.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-DIt1G85i.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -161,9 +161,34 @@ export type FunctionOverride = {
|
|
|
161
161
|
*
|
|
162
162
|
* A name that matches no schema is a warning, never a silent fallback.
|
|
163
163
|
*/
|
|
164
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Name of a declared schema, or several whose fields are UNIONED.
|
|
166
|
+
*
|
|
167
|
+
* An ILF's DETs are the fields the user recognises in the file, and an
|
|
168
|
+
* application with one schema per template recognises the fields of all of them.
|
|
169
|
+
* Pointing at the largest and justifying it in `reason` gives the same answer
|
|
170
|
+
* only while they land in the same complexity band — which is a piece of
|
|
171
|
+
* reasoning the configuration should not have to carry.
|
|
172
|
+
*
|
|
173
|
+
* Unioned by leaf path, so a field two templates share counts once.
|
|
174
|
+
*/
|
|
175
|
+
detFromSchema?: string | string[];
|
|
165
176
|
/** declared RET (data function) or FTR (transaction) */
|
|
166
177
|
refs?: number;
|
|
178
|
+
/**
|
|
179
|
+
* Opaque DETs someone has looked at and decided are correct at 1.
|
|
180
|
+
*
|
|
181
|
+
* `fp:count` reports every opaque column and open input object, because 1 DET is
|
|
182
|
+
* a floor rather than a measurement. But some of them ARE one field — a copy, a
|
|
183
|
+
* checksum, a bag of metadata — and there was no way to say so, so the warning
|
|
184
|
+
* fired on every run forever. A warning that cannot be answered is a warning the
|
|
185
|
+
* team learns to scroll past, which costs more than the one it reports.
|
|
186
|
+
*
|
|
187
|
+
* It silences nothing else: the count does not move, and `fp:count` still says
|
|
188
|
+
* how many were reviewed. Names are matched bare (`schema`) or qualified
|
|
189
|
+
* (`Petition.schema`).
|
|
190
|
+
*/
|
|
191
|
+
opaqueReviewed?: string[];
|
|
167
192
|
/** why — required, and printed by `fp:explain` beside the number */
|
|
168
193
|
reason: string;
|
|
169
194
|
};
|
|
@@ -30,6 +30,16 @@ export type Behavior = {
|
|
|
30
30
|
writes: boolean;
|
|
31
31
|
/** data stores reached */
|
|
32
32
|
touches: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Of those, the ones this transaction WRITES.
|
|
35
|
+
*
|
|
36
|
+
* `writes` is a property of the transaction — it decides EI against EO — and was
|
|
37
|
+
* being read as a property of every store the transaction touched: a table merely
|
|
38
|
+
* read by a route that writes something else counted as maintained, so almost
|
|
39
|
+
* nothing could be an EIF. §6.5.4 asks who maintains THIS store, which is a
|
|
40
|
+
* question about the access, not about the request.
|
|
41
|
+
*/
|
|
42
|
+
writtenStores: string[];
|
|
33
43
|
/**
|
|
34
44
|
* Declared input fields: `request.validateUsing(x)` resolved down to the
|
|
35
45
|
* fields of the VineJS schema — counting-decisions §7.
|
|
@@ -18,5 +18,23 @@
|
|
|
18
18
|
* it to each comparison costs vigilance forever.
|
|
19
19
|
*/
|
|
20
20
|
export declare const toPosix: (value: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* A path as it should appear in an EMITTED artefact: relative to the application.
|
|
23
|
+
*
|
|
24
|
+
* `CountSource.app` is documented as never being the absolute path, because that
|
|
25
|
+
* says where the machine keeps its files and travels with every count sent
|
|
26
|
+
* anywhere. One field below it, `config` shipped the absolute path — and so did
|
|
27
|
+
* every `trace[].file`, 858 times in a single production count. The rule was
|
|
28
|
+
* stated and then applied to one field.
|
|
29
|
+
*
|
|
30
|
+
* Internally the absolute path is the right thing: it is what ts-morph resolves
|
|
31
|
+
* and what the call graph keys its caches on. So this converts at the boundary
|
|
32
|
+
* where a path LEAVES, and nowhere else.
|
|
33
|
+
*
|
|
34
|
+
* A path outside the root keeps its `../` prefix, which describes where it is
|
|
35
|
+
* without naming the home directory.
|
|
36
|
+
*/
|
|
37
|
+
export declare const relativeTo: (root: string, value: string) => string;
|
|
21
38
|
/** Compares two paths that may have come from different sources. */
|
|
22
39
|
export declare const samePath: (a: string | undefined, b: string | undefined) => boolean;
|
|
40
|
+
export declare function isApplicationCode(root: string, file: string): boolean;
|
|
@@ -17,6 +17,13 @@ export declare const BUILTIN_CALL_RESOLVERS: CallResolver[];
|
|
|
17
17
|
* them apart. Hence specific strategies declare a lower `order` than generic
|
|
18
18
|
* ones, and `module-function` comes last — it would match almost anything.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Does any strategy call this a technical write?
|
|
22
|
+
*
|
|
23
|
+
* Asked separately from resolution, because the strategy that recognises the call as
|
|
24
|
+
* incidental is not necessarily the one that knows where it goes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isTechnicalWrite(call: import('ts-morph').CallExpression, ctx: import('./types.js').ResolverContext, resolvers?: CallResolver[]): boolean;
|
|
20
27
|
export declare function resolveCall(call: import('ts-morph').CallExpression, ctx: import('./types.js').ResolverContext, resolvers?: CallResolver[]): {
|
|
21
28
|
by: string;
|
|
22
29
|
refs: import('../../types.js').HandlerRef[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as resolveCall, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-
|
|
2
|
-
export { BUILTIN_CALL_RESOLVERS, resolveCall };
|
|
1
|
+
import { n as isTechnicalWrite, r as resolveCall, t as BUILTIN_CALL_RESOLVERS } from "../../../resolvers-PJwo2Z8R.js";
|
|
2
|
+
export { BUILTIN_CALL_RESOLVERS, isTechnicalWrite, resolveCall };
|
|
@@ -97,4 +97,23 @@ export interface CallResolver {
|
|
|
97
97
|
* defect this package can have, whoever writes it.
|
|
98
98
|
*/
|
|
99
99
|
ignores?(call: CallExpression, ctx: ResolverContext): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* "This call writes, and the write is not what the transaction is FOR."
|
|
102
|
+
*
|
|
103
|
+
* AFP §6.5.3 decides EI against EO mechanically: a transaction that modifies a data
|
|
104
|
+
* store is an EI. That is deliberate — repeatability over CPM fidelity — and it
|
|
105
|
+
* misreads one shape: a screen that records a visit, a last-seen organisation, a
|
|
106
|
+
* view counter. The CPM asks what the elementary process is PRIMARILY for, and for a
|
|
107
|
+
* `GET` that shows a record while noting the visit, the answer is presentation.
|
|
108
|
+
*
|
|
109
|
+
* So the fact is declared about the CALL, not about each transaction that reaches it:
|
|
110
|
+
* `persistOrganizationVisit` is called from several screens and saying it once covers
|
|
111
|
+
* all of them.
|
|
112
|
+
*
|
|
113
|
+
* It does NOT hide the write. The store is still maintained by this application —
|
|
114
|
+
* still an ILF, still an FTR of the transaction — and only the transaction's
|
|
115
|
+
* classification changes. A resolver that wanted the write to disappear would use
|
|
116
|
+
* `ignores`, and would be wrong to.
|
|
117
|
+
*/
|
|
118
|
+
technicalWrite?(call: CallExpression, ctx: ResolverContext): boolean;
|
|
100
119
|
}
|
|
@@ -33,7 +33,14 @@ export type CountSource = {
|
|
|
33
33
|
*/
|
|
34
34
|
dirty?: boolean;
|
|
35
35
|
countedAt: string;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Configuration file that shaped the count, RELATIVE to the application root,
|
|
38
|
+
* or null for the defaults.
|
|
39
|
+
*
|
|
40
|
+
* Relative for the same reason `app` is a name: an absolute path says where the
|
|
41
|
+
* machine keeps its files, and this artefact is what goes into a ledger and to
|
|
42
|
+
* whoever receives the invoice.
|
|
43
|
+
*/
|
|
37
44
|
config: string | null;
|
|
38
45
|
};
|
|
39
46
|
export declare function describeSource(root: string, config: string | null): CountSource;
|
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-CNTBhs6o.js";
|
|
2
2
|
export { CoverageTooLowError, analyze };
|
package/build/src/types.d.ts
CHANGED
|
@@ -84,6 +84,8 @@ export type HandlerBehavior = {
|
|
|
84
84
|
writes: boolean;
|
|
85
85
|
/** DataStores reached (ids) */
|
|
86
86
|
touches: string[];
|
|
87
|
+
/** of those, the ones this transaction writes — §6.5.4 is per store, not per request */
|
|
88
|
+
writtenStores: string[];
|
|
87
89
|
/** declared input fields (validators) */
|
|
88
90
|
inputFields: Field[];
|
|
89
91
|
/**
|
package/build/stubs/config.stub
CHANGED
|
@@ -42,7 +42,13 @@ export default defineConfig({
|
|
|
42
42
|
* here.
|
|
43
43
|
*/
|
|
44
44
|
// overrides: {
|
|
45
|
-
//
|
|
45
|
+
// // one schema, or several whose fields are unioned by leaf path
|
|
46
|
+
// Form: { detFromSchema: ['intakeSchema', 'reviewSchema'], reason: 'one per template' },
|
|
47
|
+
//
|
|
48
|
+
// // 1 DET is a floor, and `fp:count` says so on every run. When 1 IS the right
|
|
49
|
+
// // answer, record that someone checked — otherwise the warning becomes noise
|
|
50
|
+
// // the team learns to scroll past. It moves no number.
|
|
51
|
+
// Petition: { opaqueReviewed: ['schema', 'uiSchema'], reason: 'metadata; one field each' },
|
|
46
52
|
// },
|
|
47
53
|
|
|
48
54
|
/**
|