@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 CHANGED
@@ -6,6 +6,135 @@ release moves the number for unchanged code, the rule set version moves with it
6
6
  otherwise the difference would measure the tool's change rather than the work, and
7
7
  that difference becomes an invoice.
8
8
 
9
+ ## 0.9.0
10
+
11
+ **Rule set `afp@1.8.0`.** Two rules from a team's second review of their own count, one
12
+ report fixed, and a rule about the library itself. A 0.8.0 baseline has to be recounted:
13
+ the totals move 0, −3 and +1 FP — a writing transaction leaves EO for EI, two EIs gain an
14
+ FTR — and the coverage line now means one thing in both reports.
15
+
16
+ ### Fixed
17
+
18
+ - **The unresolved calls are listed, once each, and the inventory and the count show one
19
+ number.** The 0.8 CHANGELOG said `fp:inventory` listed them; it printed a total — and the
20
+ count printed a different total, because it summed each transaction's unresolved calls
21
+ (a body five routes reach counted five times) while the inventory added the route and
22
+ store problems. Now `inventory.unresolved` and `count.confidence.unresolved` are the same
23
+ list — one entry per site (`file:line expression — reason`, how many transactions reach
24
+ it), route and store problems included — and `unresolvedCalls` is its length in both.
25
+ `fp:inventory` prints every site, `fp:count` up to 25, `--json` / `--out` carry them. The
26
+ number changes without any code changing: it counts places to look at now, which is what
27
+ a person acts on.
28
+ - **A method the model declares is application code.** `const items = await
29
+ order.pendingItems().forUpdate()`, then `item.save()` in a loop: the rows come from a
30
+ method declared on the model class, and the chain was read as an access to the model at
31
+ its root — the write went to the wrong store, or was reported unreadable. The method's
32
+ return annotation (`typeof Item`), or its returns when every one is `Item.query()…`, names
33
+ the store; a builder chain after it hands the same rows on, an aggregate a number; the
34
+ chain is read from its innermost call outwards. On the reviewing team's application the
35
+ one writing transaction still counted as EO after 0.8 turns EI (7 → 4 FP).
36
+
37
+ ### New
38
+
39
+ - **A listener written inline is a listener, and a string event is an event.**
40
+ `emitter.on('order:closed', async function ({ orderId }) { … })` binds a body to a
41
+ string, and the collector read only `emitter.on(EventClass, [ListenerClass])`: an
42
+ application that binds every listener this way had none followed, its jobs "reached by no
43
+ transaction", their writes nobody's FTR. The inline function or arrow is a handler located
44
+ by its line; the string is a binding key that `emitter.emit('…', payload)` / `emitSerial`
45
+ reach — the same decision as `Event.dispatch()`; a name built at runtime binds nothing;
46
+ `start/**` is scanned. Fixture `eventos_inline`: 27 FP where the previous rule set said 13.
47
+
48
+ ### The library itself
49
+
50
+ - **The library names no application.** Its rules are found by recounting real
51
+ applications, and their names had leaked in: two heuristics keyed on one team's result
52
+ keys and one front-end's page layout, forty comments quoting somebody's routes and models,
53
+ a few shipped-doc lines. A heuristic keyed on one application's vocabulary is not a rule.
54
+ The result-key list keeps only framework and language conventions (one list page of one
55
+ application goes from 76 back to 83 DET, same FP); a module-scoped `pages/` directory is
56
+ read structurally; comments illustrate with the library's own domain; docs cite the
57
+ measurement, not the domain. `tests/unit/no_project_literals.spec.ts` holds the words that
58
+ belong to the validated applications — in the test, nowhere else — and fails when one
59
+ appears in `src`, `docs`, README or CHANGELOG.
60
+
61
+ ### Documented
62
+
63
+ - counting-decisions §3 gains the model-method row of the bindings table; §9 gains "A
64
+ listener written inline is a listener; a string event is an event".
65
+ - Fixtures: `escritas_indiretas` grows to 97 FP (a model's own query method);
66
+ `eventos_inline` (27 FP) is new.
67
+
68
+ ## 0.8.0
69
+
70
+ **Rule set `afp@1.7.0`.** One rule, wide, found by a team reviewing a 0.6.0 count of
71
+ their own application: six transactions that write were counted as EOs, with the
72
+ coverage at 99.5% and three unresolved calls. The number was close and the count was
73
+ wrong in nine places, and only the coverage line could have said so — it said 100%.
74
+ Frozen in a fixture with a hand-written reference before the code, recounted on the
75
+ three validated applications. A 0.7.0 baseline has to be recounted: the totals move −1,
76
+ −8 and +2 FP; what moves is EO → EI (3 / 11 / 2 transactions), FTRs gained, and a
77
+ coverage that now falls where the analysis does not know.
78
+
79
+ ### Fixed
80
+
81
+ - **A write binds to what the variable IS, not to where it was born.** `document.save()`
82
+ was a write on `Document` only when the instance was born in the same body or arrived
83
+ as a parameter typed inline. The reviewed application's dominant shape — the
84
+ controller loads, the action alters — arrives it destructured from a **named**
85
+ interface (`handle({ document, name }: RenameDocumentInput)`), by `const { x } =
86
+ input`, as a followed method's **declared return type** (`Promise<Session | null>`;
87
+ unannotated, its `return`s when every one is a store, one level of calls down), as a
88
+ conditional or a default whose branches are the same store (`id ? await X.find(id) :
89
+ new X()`, `(await q.first()) ?? new X()`), as `auth.user` / `auth.getUserOrFail()` —
90
+ the model `config/auth.ts` names in its provider, read, never assumed — as a
91
+ **relation read off a loaded row** (`const pasta = documento.pasta`, which had been
92
+ billed to `Documento`), as a `for…of` or a callback parameter over rows or a relation,
93
+ as a `let x: Store` assigned later, as `Store[]` or `Store | null` parameters. Every
94
+ one is a reading of what the code declares; no type checker (the project resolves no
95
+ `#alias/…`, so `getType()` is `any`), and no guess. Counting-decisions §3.
96
+ - **A write on a receiver nobody can type is an UNRESOLVED call**, reason "write on a
97
+ receiver whose type the analysis cannot read": `x.save()` / `x.delete()` with no
98
+ arguments, `x.merge(…).save()`, `x.related('…').create|sync|attach(…)`. It lowers
99
+ coverage and is listed by `fp:inventory`; the transaction stays what the readable
100
+ code says. A local a package built (`await PDFDocument.create()`, then `pdf.save()`)
101
+ is not a store and is not reported. This is the line between a count that errs and a
102
+ count that lies, and it did not exist.
103
+ - **A service is what the container returns.** `const svc = await
104
+ app.container.make(X)` binds `svc` to X as `new X()` already did, so `svc.method()` is
105
+ followed. On the reviewed application this shape carried the write of
106
+ the assignment route and 31 more sites.
107
+ - A method chain on store rows handed to a delivery (`rows.map(f).join('\n')`) delivers
108
+ the rows, not one field.
109
+
110
+ ### New
111
+
112
+ - **What the page shows is what leaves**, for a store handed to the page raw — no
113
+ transformer, no `.select()`. `inertia.render('livros/index', …)` opens
114
+ `inertia/pages/livros/index.tsx` (or `app/<module>/ui/pages/…`, or any `pages/` directory
115
+ — exactly one match, or nothing is read) and `view.render('catalogo')` opens
116
+ `resources/views/catalogo.edge`; the columns the page reads off the rows are the DETs
117
+ (`page:Livro.titulo`), one child component deep, through tsconfig `paths`, subpath
118
+ imports and relative paths. Where it cannot read — a second level of components, a
119
+ spread, a function receiving the rows, a package's `<DataTable data={…} />`, two files
120
+ answering to one name, one prop carrying several stores, members that are not columns
121
+ — the store leaves whole and the count says why, by transaction. Measured on the three
122
+ applications: **0 FP moved** (one of them: three pages read, 22 stores reported; the other two
123
+ hand every raw store through a transformer). Fixture `inertia_pages` (25 FP): the DETs
124
+ change, the points do not.
125
+
126
+ ### Documented
127
+
128
+ - counting-decisions §3 gains "A write is attributed to what the variable IS", with the
129
+ eleven bindings as a table; the fixture `escritas_indiretas` (87 FP; `afp@1.6.0` said
130
+ 57 with full coverage — five EIs sold as EOs, two ILFs mistaken for EIFs, one ILF lost)
131
+ carries one transaction per shape, plus the unreadable receiver and the package object.
132
+ - counting-decisions §6 gains "What the page shows" — the reader, its reach, and the
133
+ measured zero.
134
+ - The three boundary questions the review left open — technical logs, a mirror of another
135
+ system's table, a requirement's data — are declarations (`boundary.infrastructure`,
136
+ `boundary.externallyMaintained`), not rules; plan 0.8 §C records them.
137
+
9
138
  ## 0.7.0
10
139
 
11
140
  **Rule set `afp@1.6.0`.** Three rules move the number for unchanged code, every one
@@ -32,10 +161,10 @@ and most of that is new elementary processes the count had never seen.
32
161
  attribute; a yes/no, a formatted value, a framework service's answer and an Inertia
33
162
  lazy prop are one value. Without a delivery point the output falls back to the
34
163
  stores read, as before. On the applications: a home page at 1 DET became 10; a
35
- petitions list at 8 DET became 60 — its nested transformers, which the unresolved
164
+ list page at 8 DET became 60 — its nested transformers, which the unresolved
36
165
  variant had hidden; every listing page stopped counting its filters twice.
37
166
  - **A function of the same file is followed** (`local-function`), and so is
38
- `rows.map(paraLinha)`. Not only DETs: the stores those helpers read are FTRs now, and
167
+ `rows.map(toRow)`. Not only DETs: the stores those helpers read are FTRs now, and
39
168
  two `POST`s that wrote through a local helper moved from EO to EI. Models and modules
40
169
  imported **inside** a body (`const { default: X } = await import('#…')`) bind like a
41
170
  static import — an importer that wrote four tables this way had touched nothing.
@@ -113,7 +242,7 @@ of it is EOs losing DETs they never showed and one data function that was four.
113
242
  benchmark depends on it); a child hanging off two parents stays apart and the report
114
243
  says why. Cascade delete was measured and rejected as the signal: on one application
115
244
  11 of 13 cascades pointed at the tenant table. On the three applications the rule
116
- folds four stores in all — the `Inpi*` mirror of an external registry becomes **one**
245
+ folds four stores in all — the four-table mirror of an external registry becomes **one**
117
246
  EIF with 4 RET, which is what the CPM says.
118
247
  - **A data function is identified by its table**, as counting-decisions §5 always said.
119
248
  Keyed by the class, renaming a model billed as a deletion plus an addition.
package/README.md CHANGED
@@ -13,7 +13,7 @@ node ace fp:count
13
13
 
14
14
  ```
15
15
  Unadjusted count: 46 FP
16
- Ruleset: afp@1.6.0
16
+ Ruleset: afp@1.8.0
17
17
 
18
18
  type n FP
19
19
  ILF 2 14
@@ -14,27 +14,27 @@
14
14
  const commands = [
15
15
  {
16
16
  commandName: "fp:inventory",
17
- importer: () => import("../fp_inventory-DIjKIC9t.js")
17
+ importer: () => import("../fp_inventory-B4Pv8Thz.js")
18
18
  },
19
19
  {
20
20
  commandName: "fp:metrics",
21
- importer: () => import("../fp_metrics-BpU61waG.js")
21
+ importer: () => import("../fp_metrics-DoS5riGr.js")
22
22
  },
23
23
  {
24
24
  commandName: "fp:count",
25
- importer: () => import("../fp_count-arGLnVlY.js")
25
+ importer: () => import("../fp_count-Bjviq8dz.js")
26
26
  },
27
27
  {
28
28
  commandName: "fp:explain",
29
- importer: () => import("../fp_explain-aNFApwiT.js")
29
+ importer: () => import("../fp_explain-CrFcMeXU.js")
30
30
  },
31
31
  {
32
32
  commandName: "fp:diff",
33
- importer: () => import("../fp_diff-DBBvzq5x.js")
33
+ importer: () => import("../fp_diff-BSK1kKLQ.js")
34
34
  },
35
35
  {
36
36
  commandName: "fp:calibrate",
37
- importer: () => import("../fp_calibrate-3TxGdS1b.js")
37
+ importer: () => import("../fp_calibrate-DJspMFHc.js")
38
38
  }
39
39
  ];
40
40
  let cache = null;
@@ -1,4 +1,4 @@
1
- import { s as printResult, t as runCalibrate } from "./runners-Dm7cWGa-.js";
1
+ import { s as printResult, t as runCalibrate } from "./runners-BuZNr-FE.js";
2
2
  import { n as printerFor, t as __decorate } from "./decorate-D6enDn9D.js";
3
3
  import { BaseCommand, args } from "@adonisjs/core/ace";
4
4
  //#region commands/fp_calibrate.ts
@@ -1,4 +1,4 @@
1
- import { n as runCount, s as printResult } from "./runners-Dm7cWGa-.js";
1
+ import { n as runCount, s as printResult } from "./runners-BuZNr-FE.js";
2
2
  import { n as printerFor, t as __decorate } from "./decorate-D6enDn9D.js";
3
3
  import { BaseCommand, flags } from "@adonisjs/core/ace";
4
4
  //#region commands/fp_count.ts
@@ -1,4 +1,4 @@
1
- import { r as runDiff, s as printResult } from "./runners-Dm7cWGa-.js";
1
+ import { r as runDiff, s as printResult } from "./runners-BuZNr-FE.js";
2
2
  import { n as printerFor, t as __decorate } from "./decorate-D6enDn9D.js";
3
3
  import { BaseCommand, args } from "@adonisjs/core/ace";
4
4
  //#region commands/fp_diff.ts
@@ -1,4 +1,4 @@
1
- import { i as runExplain, s as printResult } from "./runners-Dm7cWGa-.js";
1
+ import { i as runExplain, s as printResult } from "./runners-BuZNr-FE.js";
2
2
  import { n as printerFor, t as __decorate } from "./decorate-D6enDn9D.js";
3
3
  import { BaseCommand, args } from "@adonisjs/core/ace";
4
4
  //#region commands/fp_explain.ts
@@ -1,4 +1,4 @@
1
- import { a as runInventory, s as printResult } from "./runners-Dm7cWGa-.js";
1
+ import { a as runInventory, s as printResult } from "./runners-BuZNr-FE.js";
2
2
  import { n as printerFor, t as __decorate } from "./decorate-D6enDn9D.js";
3
3
  import { BaseCommand, flags } from "@adonisjs/core/ace";
4
4
  //#region commands/fp_inventory.ts
@@ -1,4 +1,4 @@
1
- import { o as runMetrics, s as printResult } from "./runners-Dm7cWGa-.js";
1
+ import { o as runMetrics, s as printResult } from "./runners-BuZNr-FE.js";
2
2
  import { n as printerFor, t as __decorate } from "./decorate-D6enDn9D.js";
3
3
  import { BaseCommand, flags } from "@adonisjs/core/ace";
4
4
  //#region commands/fp_metrics.ts
package/build/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { configure } from "./configure.js";
2
2
  import { a as AEP_FACTORS, c as IncomparableSourcesError, f as defineConfig, i as measureStructure, l as SISP_FACTORS, n as parseSamples, o as FACTOR_PRESETS, r as measureConformance, s as IncomparableRulesetsError, t as calibrate, u as diffCounts } from "./calibration-DVIf8hcE.js";
3
3
  import "./src/types.js";
4
- import { n as ignoreCalls, t as BUILTIN_CALL_RESOLVERS } from "./resolvers-DaU4uAqT.js";
5
- import { a as DEFAULT_TECHNICAL_PATTERNS, i as RULESET_VERSION, n as analyze, r as RULESET, t as CoverageTooLowError } from "./pipeline-DO2301fV.js";
4
+ import { n as ignoreCalls, t as BUILTIN_CALL_RESOLVERS } from "./resolvers-CneCj3sT.js";
5
+ import { a as DEFAULT_TECHNICAL_PATTERNS, i as RULESET_VERSION, n as analyze, r as RULESET, t as CoverageTooLowError } from "./pipeline-DqPrDZfD.js";
6
6
  export { AEP_FACTORS, BUILTIN_CALL_RESOLVERS, CoverageTooLowError, DEFAULT_TECHNICAL_PATTERNS, FACTOR_PRESETS, IncomparableRulesetsError, IncomparableSourcesError, RULESET, RULESET_VERSION, SISP_FACTORS, analyze, calibrate, configure, defineConfig, diffCounts, ignoreCalls, measureConformance, measureStructure, parseSamples };