@filipebraida/adonis-function-points 0.8.0 → 0.10.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 +104 -5
- package/README.md +1 -1
- package/build/commands/main.js +6 -6
- package/build/{fp_calibrate-B_oD9b02.js → fp_calibrate-DUPUXVaJ.js} +1 -1
- package/build/{fp_count-B_KPNKMO.js → fp_count-B5hxDyGJ.js} +1 -1
- package/build/{fp_diff-B3NhXzrb.js → fp_diff-7S8NcKIY.js} +1 -1
- package/build/{fp_explain-C2s6Kpaj.js → fp_explain-C4X_-Rp7.js} +1 -1
- package/build/{fp_inventory-B322MvZT.js → fp_inventory-BJlIoetL.js} +1 -1
- package/build/{fp_metrics-DjADb7F6.js → fp_metrics-BDKaNL0B.js} +1 -1
- package/build/index.js +2 -2
- package/build/{pipeline-C6kHKB9-.js → pipeline-4sZBYGKU.js} +241 -65
- package/build/{resolvers-DhJO-qvQ.js → resolvers-CneCj3sT.js} +45 -12
- package/build/{runners-BpBJsGMj.js → runners-b1tgLA1B.js} +14 -4
- package/build/src/albrecht/counter.d.ts +12 -3
- package/build/src/cli.js +2 -2
- package/build/src/inventory/graph/deliveries.d.ts +6 -2
- package/build/src/inventory/graph/output_fields.d.ts +2 -2
- package/build/src/inventory/graph/pages.d.ts +1 -1
- 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 +14 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,105 @@ 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.10.0
|
|
10
|
+
|
|
11
|
+
**Rule set `afp@1.9.0`.** One rule and two report fixes, from a team's third review of their
|
|
12
|
+
own count — the one they accepted as a baseline. The rule moves no point on the three
|
|
13
|
+
validated applications and no point on its fixture: it moves a gap from the wrong line to
|
|
14
|
+
the right one. A 0.9.0 baseline still has to be recounted, because the rule set says so.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **A warning names a transaction the way the table and the config keys do.** Four warnings
|
|
19
|
+
printed the route's own pattern (`GET /orders/:id`) where the table, `fp:explain` and the
|
|
20
|
+
keys of `overrides` / `boundary.ignoreEntryPoints` use the identity (`GET /orders/:param`,
|
|
21
|
+
§5). Copying from a warning into the configuration required a translation nobody was told
|
|
22
|
+
about. All of them print the identity now.
|
|
23
|
+
- **"leaves whole — page never reads X" contradicted itself.** The rule was right — a store
|
|
24
|
+
handed to a page that uses nothing of it the reader can see leaves whole, in the open, never
|
|
25
|
+
as a floor — and the sentence was not. It says now: "handed to page "x", and nothing the
|
|
26
|
+
reader can see uses X: every column counted, in the open".
|
|
27
|
+
|
|
28
|
+
### Counting
|
|
29
|
+
|
|
30
|
+
- **The value of a transaction callback is what it returns.** `const { row } = await
|
|
31
|
+
db.transaction(async (trx) => { … return { row: created } })`, then `row.save()`: the write
|
|
32
|
+
was an unreadable receiver. The callback is a body — its own locals are bound first, in
|
|
33
|
+
source order — and every `return` names the store the value holds, whole or under one key
|
|
34
|
+
of a returned literal; `Model.transaction` and `trx.transaction` are the same shape. A
|
|
35
|
+
returned number binds nothing; a raw-query row nobody can type stays reported. On the three
|
|
36
|
+
applications the value of a transaction callback is used 17 times. The 0.8 rule "a value a
|
|
37
|
+
package built is not a store" narrows to a call with no function argument: `db` is a
|
|
38
|
+
package import, and `db.transaction(cb)` hands back the application's value.
|
|
39
|
+
|
|
40
|
+
### Documented
|
|
41
|
+
|
|
42
|
+
- counting-decisions §3 gains the transaction-callback row of the bindings table.
|
|
43
|
+
- Fixtures: `transacao_valor` (18 FP — the same before and after; one unresolved call moves
|
|
44
|
+
from `row.save` to the raw-query row that deserves it); `inertia_pages` grows a page that
|
|
45
|
+
uses nothing of its rows (29 FP).
|
|
46
|
+
- Measured and set aside: a model's instance method that writes — one in 52 models on the
|
|
47
|
+
three applications, not a rule.
|
|
48
|
+
|
|
49
|
+
## 0.9.0
|
|
50
|
+
|
|
51
|
+
**Rule set `afp@1.8.0`.** Two rules from a team's second review of their own count, one
|
|
52
|
+
report fixed, and a rule about the library itself. A 0.8.0 baseline has to be recounted:
|
|
53
|
+
the totals move 0, −3 and +1 FP — a writing transaction leaves EO for EI, two EIs gain an
|
|
54
|
+
FTR — and the coverage line now means one thing in both reports.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **The unresolved calls are listed, once each, and the inventory and the count show one
|
|
59
|
+
number.** The 0.8 CHANGELOG said `fp:inventory` listed them; it printed a total — and the
|
|
60
|
+
count printed a different total, because it summed each transaction's unresolved calls
|
|
61
|
+
(a body five routes reach counted five times) while the inventory added the route and
|
|
62
|
+
store problems. Now `inventory.unresolved` and `count.confidence.unresolved` are the same
|
|
63
|
+
list — one entry per site (`file:line expression — reason`, how many transactions reach
|
|
64
|
+
it), route and store problems included — and `unresolvedCalls` is its length in both.
|
|
65
|
+
`fp:inventory` prints every site, `fp:count` up to 25, `--json` / `--out` carry them. The
|
|
66
|
+
number changes without any code changing: it counts places to look at now, which is what
|
|
67
|
+
a person acts on.
|
|
68
|
+
- **A method the model declares is application code.** `const items = await
|
|
69
|
+
order.pendingItems().forUpdate()`, then `item.save()` in a loop: the rows come from a
|
|
70
|
+
method declared on the model class, and the chain was read as an access to the model at
|
|
71
|
+
its root — the write went to the wrong store, or was reported unreadable. The method's
|
|
72
|
+
return annotation (`typeof Item`), or its returns when every one is `Item.query()…`, names
|
|
73
|
+
the store; a builder chain after it hands the same rows on, an aggregate a number; the
|
|
74
|
+
chain is read from its innermost call outwards. On the reviewing team's application the
|
|
75
|
+
one writing transaction still counted as EO after 0.8 turns EI (7 → 4 FP).
|
|
76
|
+
|
|
77
|
+
### New
|
|
78
|
+
|
|
79
|
+
- **A listener written inline is a listener, and a string event is an event.**
|
|
80
|
+
`emitter.on('order:closed', async function ({ orderId }) { … })` binds a body to a
|
|
81
|
+
string, and the collector read only `emitter.on(EventClass, [ListenerClass])`: an
|
|
82
|
+
application that binds every listener this way had none followed, its jobs "reached by no
|
|
83
|
+
transaction", their writes nobody's FTR. The inline function or arrow is a handler located
|
|
84
|
+
by its line; the string is a binding key that `emitter.emit('…', payload)` / `emitSerial`
|
|
85
|
+
reach — the same decision as `Event.dispatch()`; a name built at runtime binds nothing;
|
|
86
|
+
`start/**` is scanned. Fixture `eventos_inline`: 27 FP where the previous rule set said 13.
|
|
87
|
+
|
|
88
|
+
### The library itself
|
|
89
|
+
|
|
90
|
+
- **The library names no application.** Its rules are found by recounting real
|
|
91
|
+
applications, and their names had leaked in: two heuristics keyed on one team's result
|
|
92
|
+
keys and one front-end's page layout, forty comments quoting somebody's routes and models,
|
|
93
|
+
a few shipped-doc lines. A heuristic keyed on one application's vocabulary is not a rule.
|
|
94
|
+
The result-key list keeps only framework and language conventions (one list page of one
|
|
95
|
+
application goes from 76 back to 83 DET, same FP); a module-scoped `pages/` directory is
|
|
96
|
+
read structurally; comments illustrate with the library's own domain; docs cite the
|
|
97
|
+
measurement, not the domain. `tests/unit/no_project_literals.spec.ts` holds the words that
|
|
98
|
+
belong to the validated applications — in the test, nowhere else — and fails when one
|
|
99
|
+
appears in `src`, `docs`, README or CHANGELOG.
|
|
100
|
+
|
|
101
|
+
### Documented
|
|
102
|
+
|
|
103
|
+
- counting-decisions §3 gains the model-method row of the bindings table; §9 gains "A
|
|
104
|
+
listener written inline is a listener; a string event is an event".
|
|
105
|
+
- Fixtures: `escritas_indiretas` grows to 97 FP (a model's own query method);
|
|
106
|
+
`eventos_inline` (27 FP) is new.
|
|
107
|
+
|
|
9
108
|
## 0.8.0
|
|
10
109
|
|
|
11
110
|
**Rule set `afp@1.7.0`.** One rule, wide, found by a team reviewing a 0.6.0 count of
|
|
@@ -44,7 +143,7 @@ new X()`, `(await q.first()) ?? new X()`), as `auth.user` / `auth.getUserOrFail(
|
|
|
44
143
|
- **A service is what the container returns.** `const svc = await
|
|
45
144
|
app.container.make(X)` binds `svc` to X as `new X()` already did, so `svc.method()` is
|
|
46
145
|
followed. On the reviewed application this shape carried the write of
|
|
47
|
-
|
|
146
|
+
the assignment route and 31 more sites.
|
|
48
147
|
- A method chain on store rows handed to a delivery (`rows.map(f).join('\n')`) delivers
|
|
49
148
|
the rows, not one field.
|
|
50
149
|
|
|
@@ -60,7 +159,7 @@ app.container.make(X)` binds `svc` to X as `new X()` already did, so `svc.method
|
|
|
60
159
|
spread, a function receiving the rows, a package's `<DataTable data={…} />`, two files
|
|
61
160
|
answering to one name, one prop carrying several stores, members that are not columns
|
|
62
161
|
— the store leaves whole and the count says why, by transaction. Measured on the three
|
|
63
|
-
applications: **0 FP moved** (
|
|
162
|
+
applications: **0 FP moved** (one of them: three pages read, 22 stores reported; the other two
|
|
64
163
|
hand every raw store through a transformer). Fixture `inertia_pages` (25 FP): the DETs
|
|
65
164
|
change, the points do not.
|
|
66
165
|
|
|
@@ -102,10 +201,10 @@ and most of that is new elementary processes the count had never seen.
|
|
|
102
201
|
attribute; a yes/no, a formatted value, a framework service's answer and an Inertia
|
|
103
202
|
lazy prop are one value. Without a delivery point the output falls back to the
|
|
104
203
|
stores read, as before. On the applications: a home page at 1 DET became 10; a
|
|
105
|
-
|
|
204
|
+
list page at 8 DET became 60 — its nested transformers, which the unresolved
|
|
106
205
|
variant had hidden; every listing page stopped counting its filters twice.
|
|
107
206
|
- **A function of the same file is followed** (`local-function`), and so is
|
|
108
|
-
`rows.map(
|
|
207
|
+
`rows.map(toRow)`. Not only DETs: the stores those helpers read are FTRs now, and
|
|
109
208
|
two `POST`s that wrote through a local helper moved from EO to EI. Models and modules
|
|
110
209
|
imported **inside** a body (`const { default: X } = await import('#…')`) bind like a
|
|
111
210
|
static import — an importer that wrote four tables this way had touched nothing.
|
|
@@ -183,7 +282,7 @@ of it is EOs losing DETs they never showed and one data function that was four.
|
|
|
183
282
|
benchmark depends on it); a child hanging off two parents stays apart and the report
|
|
184
283
|
says why. Cascade delete was measured and rejected as the signal: on one application
|
|
185
284
|
11 of 13 cascades pointed at the tenant table. On the three applications the rule
|
|
186
|
-
folds four stores in all — the
|
|
285
|
+
folds four stores in all — the four-table mirror of an external registry becomes **one**
|
|
187
286
|
EIF with 4 RET, which is what the CPM says.
|
|
188
287
|
- **A data function is identified by its table**, as counting-decisions §5 always said.
|
|
189
288
|
Keyed by the class, renaming a model billed as a deletion plus an addition.
|
package/README.md
CHANGED
package/build/commands/main.js
CHANGED
|
@@ -14,27 +14,27 @@
|
|
|
14
14
|
const commands = [
|
|
15
15
|
{
|
|
16
16
|
commandName: "fp:inventory",
|
|
17
|
-
importer: () => import("../fp_inventory-
|
|
17
|
+
importer: () => import("../fp_inventory-BJlIoetL.js")
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
commandName: "fp:metrics",
|
|
21
|
-
importer: () => import("../fp_metrics-
|
|
21
|
+
importer: () => import("../fp_metrics-BDKaNL0B.js")
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
commandName: "fp:count",
|
|
25
|
-
importer: () => import("../fp_count-
|
|
25
|
+
importer: () => import("../fp_count-B5hxDyGJ.js")
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
commandName: "fp:explain",
|
|
29
|
-
importer: () => import("../fp_explain-
|
|
29
|
+
importer: () => import("../fp_explain-C4X_-Rp7.js")
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
commandName: "fp:diff",
|
|
33
|
-
importer: () => import("../fp_diff-
|
|
33
|
+
importer: () => import("../fp_diff-7S8NcKIY.js")
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
commandName: "fp:calibrate",
|
|
37
|
-
importer: () => import("../fp_calibrate-
|
|
37
|
+
importer: () => import("../fp_calibrate-DUPUXVaJ.js")
|
|
38
38
|
}
|
|
39
39
|
];
|
|
40
40
|
let cache = null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as printResult, t as runCalibrate } from "./runners-
|
|
1
|
+
import { s as printResult, t as runCalibrate } from "./runners-b1tgLA1B.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-
|
|
1
|
+
import { n as runCount, s as printResult } from "./runners-b1tgLA1B.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-
|
|
1
|
+
import { r as runDiff, s as printResult } from "./runners-b1tgLA1B.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-
|
|
1
|
+
import { i as runExplain, s as printResult } from "./runners-b1tgLA1B.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-
|
|
1
|
+
import { a as runInventory, s as printResult } from "./runners-b1tgLA1B.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-
|
|
1
|
+
import { o as runMetrics, s as printResult } from "./runners-b1tgLA1B.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-
|
|
5
|
-
import { a as DEFAULT_TECHNICAL_PATTERNS, i as RULESET_VERSION, n as analyze, r as RULESET, t as CoverageTooLowError } from "./pipeline-
|
|
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-4sZBYGKU.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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as relativeTo, a as chainShapeOf, c as unwrap$1, d as collectEventBindings, f as detectAccess, g as isSeeder, h as isApplicationCode, i as resolveCall, l as DISPATCH_METHODS, m as rootSymbolOf, o as mappedLiteralOf, p as hooksFiredBy, r as isTechnicalWrite, s as outputFieldsIn, t as BUILTIN_CALL_RESOLVERS, u as EXECUTION_METHODS, v as samePath, y as toPosix } from "./resolvers-
|
|
1
|
+
import { _ as relativeTo, a as chainShapeOf, c as unwrap$1, d as collectEventBindings, f as detectAccess, g as isSeeder, h as isApplicationCode, i as resolveCall, l as DISPATCH_METHODS, m as rootSymbolOf, o as mappedLiteralOf, p as hooksFiredBy, r as isTechnicalWrite, s as outputFieldsIn, t as BUILTIN_CALL_RESOLVERS, u as EXECUTION_METHODS, v as samePath, y as toPosix } from "./resolvers-CneCj3sT.js";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path, { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
@@ -969,7 +969,7 @@ const problemAt = (call, expression, reason) => ({
|
|
|
969
969
|
/**
|
|
970
970
|
* Ace commands as entry points — counting-decisions §5, plan 0.7 §C.
|
|
971
971
|
*
|
|
972
|
-
* IFPUG counts batch processes an operator starts. `node ace
|
|
972
|
+
* IFPUG counts batch processes an operator starts. `node ace articles:import`
|
|
973
973
|
* reads a feed and writes news: an EI exactly like a `POST`, and until this the
|
|
974
974
|
* collector emitted only `kind: 'http'`, although the type had `command` and §5
|
|
975
975
|
* had decided its identity since 0.1.0.
|
|
@@ -1036,7 +1036,7 @@ function baseCommandNameIn(file) {
|
|
|
1036
1036
|
}
|
|
1037
1037
|
return null;
|
|
1038
1038
|
}
|
|
1039
|
-
/** `static commandName = '
|
|
1039
|
+
/** `static commandName = 'articles:import'` — a literal; a computed name is nobody's identity */
|
|
1040
1040
|
function commandNameOf(cls) {
|
|
1041
1041
|
const property = cls.getStaticProperty("commandName");
|
|
1042
1042
|
if (!property || !Node.isPropertyDeclaration(property)) return null;
|
|
@@ -1190,7 +1190,8 @@ const FRAMEWORK_SERVICES = new Set([
|
|
|
1190
1190
|
"bouncer",
|
|
1191
1191
|
"ui",
|
|
1192
1192
|
"colors",
|
|
1193
|
-
"app"
|
|
1193
|
+
"app",
|
|
1194
|
+
"prompt"
|
|
1194
1195
|
]);
|
|
1195
1196
|
/** Is this call one that cannot reach a data store? */
|
|
1196
1197
|
function isNoise(call, owner) {
|
|
@@ -1295,15 +1296,17 @@ const PASSES_THROUGH$1 = new Set([
|
|
|
1295
1296
|
"toJSON",
|
|
1296
1297
|
"serialize"
|
|
1297
1298
|
]);
|
|
1298
|
-
/**
|
|
1299
|
+
/**
|
|
1300
|
+
* Keys under which a paginator or a wrapper hands its rows on: picking one of these
|
|
1301
|
+
* is not picking one value. Framework and JavaScript conventions only — a key named
|
|
1302
|
+
* in one application's language is that application's, not a rule.
|
|
1303
|
+
*/
|
|
1299
1304
|
const PASSES_ROWS = new Set([
|
|
1300
1305
|
"data",
|
|
1301
1306
|
"rows",
|
|
1302
1307
|
"items",
|
|
1303
1308
|
"results",
|
|
1304
|
-
"
|
|
1305
|
-
"linhas",
|
|
1306
|
-
"itens"
|
|
1309
|
+
"records"
|
|
1307
1310
|
]);
|
|
1308
1311
|
/** properties of a result that are one value, not its rows */
|
|
1309
1312
|
const SCALAR_PROPS = new Set([
|
|
@@ -1790,7 +1793,7 @@ function classifyCall(value, path, ctx, out, depth, pick) {
|
|
|
1790
1793
|
/**
|
|
1791
1794
|
* `rows.map(…).join('\n')` in a body whose parameter is `rows`: the whole input
|
|
1792
1795
|
* transformed, so the input says what leaves — the caller's arguments decide,
|
|
1793
|
-
* and the body itself says nothing. `
|
|
1796
|
+
* and the body itself says nothing. `article.publicadaEm?.toISO()` on that
|
|
1794
1797
|
* parameter is one FIELD of it, and stays one value below.
|
|
1795
1798
|
*/
|
|
1796
1799
|
if (Node.isPropertyAccessExpression(callee) && transformsParameter(callee, ctx.body)) {
|
|
@@ -1803,7 +1806,7 @@ function classifyCall(value, path, ctx, out, depth, pick) {
|
|
|
1803
1806
|
}
|
|
1804
1807
|
if (Node.isPropertyAccessExpression(callee)) {
|
|
1805
1808
|
/**
|
|
1806
|
-
* `
|
|
1809
|
+
* `notice.enviadoEm!.toISODate()`, `(a ?? b).toRFC2822()`: a FIELD read off
|
|
1807
1810
|
* the chain, or an expression, then a method — one value. A chain rooted at
|
|
1808
1811
|
* `this` (`this.service.find()`) is a call into a service, and not this.
|
|
1809
1812
|
*/
|
|
@@ -1838,7 +1841,7 @@ function classifyCall(value, path, ctx, out, depth, pick) {
|
|
|
1838
1841
|
}
|
|
1839
1842
|
/**
|
|
1840
1843
|
* `xs.map(cb)` with no literal in the callback: what the callback RETURNS,
|
|
1841
|
-
* once — a function passed by reference (`rows.map(
|
|
1844
|
+
* once — a function passed by reference (`rows.map(toRow)`) is a call to
|
|
1842
1845
|
* that function over the rows; an expression body (`(m) => new T(m).toObject()`)
|
|
1843
1846
|
* is classified as if it were the value; anything else is one repeating attribute.
|
|
1844
1847
|
*/
|
|
@@ -1877,7 +1880,7 @@ function callbackValueOf(callback) {
|
|
|
1877
1880
|
}
|
|
1878
1881
|
/**
|
|
1879
1882
|
* Does this chain read a property (not a method) or hold an expression before
|
|
1880
|
-
* the method is applied? `
|
|
1883
|
+
* the method is applied? `notice.enviadoEm.toISO()` does; `rows.map(f).join()`
|
|
1881
1884
|
* does not; a chain rooted at `this` is a service, and does not.
|
|
1882
1885
|
*/
|
|
1883
1886
|
function readsField(node) {
|
|
@@ -1906,7 +1909,7 @@ function readsField(node) {
|
|
|
1906
1909
|
}
|
|
1907
1910
|
/**
|
|
1908
1911
|
* Is this chain a method (or methods) applied to a plain parameter of the body,
|
|
1909
|
-
* with no field read in between? `rows.map(f).join(s)` is; `
|
|
1912
|
+
* with no field read in between? `rows.map(f).join(s)` is; `article.capa?.toISO()`
|
|
1910
1913
|
* reads a field first and is not.
|
|
1911
1914
|
*/
|
|
1912
1915
|
function transformsParameter(callee, body) {
|
|
@@ -1956,7 +1959,7 @@ function returnsPrimitive(call) {
|
|
|
1956
1959
|
}
|
|
1957
1960
|
}
|
|
1958
1961
|
/**
|
|
1959
|
-
* `x.data`, `
|
|
1962
|
+
* `x.data`, `result.rows`, `rows[0]`: a part of a value the body holds.
|
|
1960
1963
|
*
|
|
1961
1964
|
* on a variable bound to a followed call that key of what the call returns
|
|
1962
1965
|
* on a store-bound variable the store (`rows[0]`, `.data`) or a field (scalar)
|
|
@@ -1993,7 +1996,7 @@ function classifyAccess(value, path, ctx, out, depth) {
|
|
|
1993
1996
|
return;
|
|
1994
1997
|
}
|
|
1995
1998
|
/**
|
|
1996
|
-
* `
|
|
1999
|
+
* `result.rows`: one key of what the call returns; `meta.pagina` on a
|
|
1997
2000
|
* destructured `meta`: the key under the key; `rows[0]`, `.data`: the whole.
|
|
1998
2001
|
*/
|
|
1999
2002
|
const own = property && !PASSES_THROUGH$1.has(property) ? property : void 0;
|
|
@@ -2165,7 +2168,7 @@ function readPages(deliveries, env) {
|
|
|
2165
2168
|
for (const { store } of readable) {
|
|
2166
2169
|
if (reading.columns.has(store) || reading.unreadable.has(store)) continue;
|
|
2167
2170
|
const members = [...reading.unknownMembers.get(store) ?? []];
|
|
2168
|
-
markUnreadable(reading, store, members.length > 0 ? `page "${page}" reads ${members.slice(0, 4).map((m) => `\`${m}\``).join(", ")} off ${store}, none of them a column of it` : `page "${page}"
|
|
2171
|
+
markUnreadable(reading, store, members.length > 0 ? `page "${page}" reads ${members.slice(0, 4).map((m) => `\`${m}\``).join(", ")} off ${store}, none of them a column of it` : `handed to page "${page}", and nothing the reader can see uses ${store}: every column counted, in the open`);
|
|
2169
2172
|
}
|
|
2170
2173
|
}
|
|
2171
2174
|
return reading;
|
|
@@ -2183,33 +2186,37 @@ const SKIPPED_DIRS = new Set([
|
|
|
2183
2186
|
".adonisjs"
|
|
2184
2187
|
]);
|
|
2185
2188
|
/**
|
|
2186
|
-
* `inertia.render('livros/index')` → `inertia/pages/livros/index.tsx` (the default),
|
|
2187
|
-
* `
|
|
2188
|
-
* under the root holding that path. The `resolve` function of the front-end is not
|
|
2189
|
+
* `inertia.render('livros/index')` → `inertia/pages/livros/index.tsx` (the default), or any
|
|
2190
|
+
* `pages/` directory under the root holding that path — a module-scoped one included. The `resolve` function of the front-end is not
|
|
2189
2191
|
* run — a convention is read, a function is not.
|
|
2190
2192
|
*/
|
|
2191
2193
|
function inertiaPageFiles(root, page) {
|
|
2192
2194
|
const found = /* @__PURE__ */ new Set();
|
|
2195
|
+
/**
|
|
2196
|
+
* `orders/show` → `<…>/orders/<…>/pages/show.tsx`: a front-end that keeps one `pages/`
|
|
2197
|
+
* directory per module names the module in the page name's first segment. Read
|
|
2198
|
+
* structurally — a `pages/` directory whose path carries that segment — never as a
|
|
2199
|
+
* fixed layout: the default is `inertia/pages/<name>`, and anything else is one
|
|
2200
|
+
* application's convention.
|
|
2201
|
+
*/
|
|
2193
2202
|
const [first, ...rest] = page.split("/");
|
|
2194
|
-
|
|
2195
|
-
".
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
const index = join(dir, page, `index${extension}`);
|
|
2212
|
-
if (existsSync(index)) found.add(index);
|
|
2203
|
+
for (const dir of pagesDirectories(root)) {
|
|
2204
|
+
const scopedToModule = rest.length > 0 && toPosix(dir).split("/").includes(first);
|
|
2205
|
+
for (const extension of [
|
|
2206
|
+
".tsx",
|
|
2207
|
+
".jsx",
|
|
2208
|
+
".vue",
|
|
2209
|
+
".svelte"
|
|
2210
|
+
]) {
|
|
2211
|
+
const file = join(dir, `${page}${extension}`);
|
|
2212
|
+
if (existsSync(file)) found.add(file);
|
|
2213
|
+
const index = join(dir, page, `index${extension}`);
|
|
2214
|
+
if (existsSync(index)) found.add(index);
|
|
2215
|
+
if (scopedToModule) {
|
|
2216
|
+
const scoped = join(dir, `${rest.join("/")}${extension}`);
|
|
2217
|
+
if (existsSync(scoped)) found.add(scoped);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2213
2220
|
}
|
|
2214
2221
|
return [...found].sort();
|
|
2215
2222
|
}
|
|
@@ -3031,6 +3038,86 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3031
3038
|
}
|
|
3032
3039
|
return found.size === 1 ? [...found][0] : null;
|
|
3033
3040
|
};
|
|
3041
|
+
/**
|
|
3042
|
+
* `order.pendingItems({ client })`: a method declared on the model class, returning
|
|
3043
|
+
* `Item.query()…`. The model file is application code, and the method's annotation
|
|
3044
|
+
* (`typeof Item` names the store) or its returns say what it hands back (plan 0.9 §B).
|
|
3045
|
+
*/
|
|
3046
|
+
const modelMethodStore = (callee, scope) => {
|
|
3047
|
+
const root = rootSymbolOf(callee.getExpression());
|
|
3048
|
+
const store = root ? scope.get(root) : void 0;
|
|
3049
|
+
if (!store) return null;
|
|
3050
|
+
const declared = storesByName.get(store);
|
|
3051
|
+
const method = (declared ? sourceFile(declared.provenance.file) : null)?.getClasses().flatMap((c) => c.getMethods()).find((m) => m.getName() === callee.getName());
|
|
3052
|
+
if (!method) return null;
|
|
3053
|
+
const annotation = method.getReturnTypeNode()?.getText();
|
|
3054
|
+
if (annotation) {
|
|
3055
|
+
const named = annotation.match(/typeof\s+([A-Za-z_]\w*)/)?.[1];
|
|
3056
|
+
return storeNamedBy(named ?? annotation, storesByName);
|
|
3057
|
+
}
|
|
3058
|
+
const found = /* @__PURE__ */ new Set();
|
|
3059
|
+
for (const statement of method.getDescendantsOfKind(SyntaxKind.ReturnStatement)) {
|
|
3060
|
+
const value = statement.getExpression();
|
|
3061
|
+
if (!value) return null;
|
|
3062
|
+
const returnedRoot = rootSymbolOf(unwrapAwait(value));
|
|
3063
|
+
if (returnedRoot && storesByName.has(returnedRoot)) found.add(returnedRoot);
|
|
3064
|
+
else return null;
|
|
3065
|
+
}
|
|
3066
|
+
return found.size === 1 ? [...found][0] : null;
|
|
3067
|
+
};
|
|
3068
|
+
/**
|
|
3069
|
+
* `await db.transaction(async (trx) => { … return … })` — also `Model.transaction`
|
|
3070
|
+
* and `trx.transaction` (a savepoint): a body whose VALUE is what it returns. The
|
|
3071
|
+
* callback's own locals are bound first, in source order, so `return { row: created }`
|
|
3072
|
+
* can be read; then every return names one store, or nothing binds (plan 0.10 §B).
|
|
3073
|
+
*/
|
|
3074
|
+
const transactionCallbackOf = (call) => {
|
|
3075
|
+
const callee = call.getExpression();
|
|
3076
|
+
if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "transaction") return null;
|
|
3077
|
+
return call.getArguments().find((a) => Node.isArrowFunction(a) || Node.isFunctionExpression(a)) ?? null;
|
|
3078
|
+
};
|
|
3079
|
+
const returnsOf = (callback) => {
|
|
3080
|
+
if (!Node.isArrowFunction(callback) && !Node.isFunctionExpression(callback)) return [];
|
|
3081
|
+
const callbackBody = callback.getBody();
|
|
3082
|
+
if (!Node.isBlock(callbackBody)) return [callbackBody];
|
|
3083
|
+
const returned = [];
|
|
3084
|
+
for (const statement of callbackBody.getDescendantsOfKind(SyntaxKind.ReturnStatement)) {
|
|
3085
|
+
const owner = statement.getFirstAncestor((n) => Node.isArrowFunction(n) || Node.isFunctionExpression(n) || Node.isFunctionDeclaration(n) || Node.isMethodDeclaration(n));
|
|
3086
|
+
const expression = statement.getExpression();
|
|
3087
|
+
if (owner === callback && expression) returned.push(expression);
|
|
3088
|
+
}
|
|
3089
|
+
return returned;
|
|
3090
|
+
};
|
|
3091
|
+
const bindCallbackLocals = (callback) => {
|
|
3092
|
+
for (const declaration of callback.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
3093
|
+
const nameNode = declaration.getNameNode();
|
|
3094
|
+
const initializer = declaration.getInitializer();
|
|
3095
|
+
if (!initializer || !Node.isIdentifier(nameNode)) continue;
|
|
3096
|
+
const store = storeOfValue(initializer);
|
|
3097
|
+
if (store) symbols.set(nameNode.getText(), store);
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
/** the store every return of the callback names — whole, or under one key of a returned literal */
|
|
3101
|
+
const transactionValueStore = (callback, key) => {
|
|
3102
|
+
bindCallbackLocals(callback);
|
|
3103
|
+
const returned = returnsOf(callback);
|
|
3104
|
+
if (returned.length === 0) return null;
|
|
3105
|
+
const found = /* @__PURE__ */ new Set();
|
|
3106
|
+
for (const expression of returned) {
|
|
3107
|
+
let value = unwrapAwait(expression);
|
|
3108
|
+
if (key) {
|
|
3109
|
+
if (!Node.isObjectLiteralExpression(value)) return null;
|
|
3110
|
+
const property = value.getProperty(key);
|
|
3111
|
+
if (!property) return null;
|
|
3112
|
+
value = Node.isPropertyAssignment(property) ? property.getInitializer() : Node.isShorthandPropertyAssignment(property) ? property.getNameNode() : void 0;
|
|
3113
|
+
if (!value) return null;
|
|
3114
|
+
}
|
|
3115
|
+
const store = storeOfValue(value);
|
|
3116
|
+
if (!store) return null;
|
|
3117
|
+
found.add(store);
|
|
3118
|
+
}
|
|
3119
|
+
return found.size === 1 ? [...found][0] : null;
|
|
3120
|
+
};
|
|
3034
3121
|
const isAuthUser = (node) => {
|
|
3035
3122
|
if (!authUserStore) return false;
|
|
3036
3123
|
const chain = Node.isCallExpression(node) ? node.getExpression() : node;
|
|
@@ -3062,9 +3149,27 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3062
3149
|
if (isAuthUser(node)) return authUserStore;
|
|
3063
3150
|
if (Node.isPropertyAccessExpression(node)) return storeOfExpression(node, symbols, relationsByStore);
|
|
3064
3151
|
if (Node.isCallExpression(node)) {
|
|
3152
|
+
const callee = node.getExpression();
|
|
3153
|
+
const callback = transactionCallbackOf(node);
|
|
3154
|
+
if (callback) return transactionValueStore(callback);
|
|
3155
|
+
if (Node.isPropertyAccessExpression(callee)) {
|
|
3156
|
+
/**
|
|
3157
|
+
* `q.forUpdate()`, `q.where(…)`: a query-builder chain hands the same rows on —
|
|
3158
|
+
* read from the innermost call outwards, so a model's method at the root of
|
|
3159
|
+
* the chain is seen before Lucid's API is assumed for the whole chain. An
|
|
3160
|
+
* aggregate (`.count()`) hands back a number.
|
|
3161
|
+
*/
|
|
3162
|
+
const receiver = unwrapAwait(callee.getExpression());
|
|
3163
|
+
if (Node.isCallExpression(receiver)) {
|
|
3164
|
+
if (AGGREGATES.has(callee.getName())) return null;
|
|
3165
|
+
const inner = storeOfValue(receiver, depth + 1);
|
|
3166
|
+
if (inner) return inner;
|
|
3167
|
+
}
|
|
3168
|
+
const fromModel = modelMethodStore(callee, symbols);
|
|
3169
|
+
if (fromModel) return fromModel;
|
|
3170
|
+
}
|
|
3065
3171
|
const access = detectAccess(node, symbols, relationsByStore);
|
|
3066
3172
|
if (access) return access.method === "related" && access.viaRelation ? access.viaRelation : access.store;
|
|
3067
|
-
const callee = node.getExpression();
|
|
3068
3173
|
if (Node.isPropertyAccessExpression(callee) && ONE_OF_ROWS.has(callee.getName())) return storeOfValue(callee.getExpression(), depth + 1);
|
|
3069
3174
|
return returnedStoreOf(node);
|
|
3070
3175
|
}
|
|
@@ -3086,9 +3191,22 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3086
3191
|
const nameNode = node.getNameNode();
|
|
3087
3192
|
if (!initializer) return;
|
|
3088
3193
|
if (Node.isIdentifier(nameNode)) {
|
|
3089
|
-
|
|
3194
|
+
/**
|
|
3195
|
+
* Re-read even when the parameter pass already bound the name by the chain's
|
|
3196
|
+
* root: `const items = await order.pendingItems().forUpdate()` is rooted at
|
|
3197
|
+
* `order` and holds `Item` rows — what the model's method returns wins over
|
|
3198
|
+
* where the chain started.
|
|
3199
|
+
*/
|
|
3090
3200
|
const store = storeOfValue(initializer);
|
|
3091
3201
|
if (store) symbols.set(nameNode.getText(), store);
|
|
3202
|
+
} else if (Node.isObjectBindingPattern(nameNode)) {
|
|
3203
|
+
const call = unwrapAwait(initializer);
|
|
3204
|
+
const callback = Node.isCallExpression(call) ? transactionCallbackOf(call) : null;
|
|
3205
|
+
if (!callback) return;
|
|
3206
|
+
for (const element of nameNode.getElements()) {
|
|
3207
|
+
const store = transactionValueStore(callback, element.getPropertyNameNode()?.getText() ?? element.getName());
|
|
3208
|
+
if (store) symbols.set(element.getName(), store);
|
|
3209
|
+
}
|
|
3092
3210
|
}
|
|
3093
3211
|
return;
|
|
3094
3212
|
}
|
|
@@ -3096,7 +3214,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3096
3214
|
const declared = node.getInitializer();
|
|
3097
3215
|
if (!Node.isVariableDeclarationList(declared)) return;
|
|
3098
3216
|
const nameNode = declared.getDeclarations()[0]?.getNameNode();
|
|
3099
|
-
if (!nameNode || !Node.isIdentifier(nameNode)
|
|
3217
|
+
if (!nameNode || !Node.isIdentifier(nameNode)) return;
|
|
3100
3218
|
const store = storeOfValue(node.getExpression());
|
|
3101
3219
|
if (store) symbols.set(nameNode.getText(), store);
|
|
3102
3220
|
return;
|
|
@@ -3107,7 +3225,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3107
3225
|
const callback = node.getArguments()[0];
|
|
3108
3226
|
if (!callback || !(Node.isArrowFunction(callback) || Node.isFunctionExpression(callback))) return;
|
|
3109
3227
|
const parameter = callback.getParameters()[0]?.getNameNode();
|
|
3110
|
-
if (!parameter || !Node.isIdentifier(parameter)
|
|
3228
|
+
if (!parameter || !Node.isIdentifier(parameter)) return;
|
|
3111
3229
|
const store = storeOfValue(callee.getExpression());
|
|
3112
3230
|
if (store) symbols.set(parameter.getText(), store);
|
|
3113
3231
|
}
|
|
@@ -3176,8 +3294,8 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3176
3294
|
* A select list that is not literal is reported, and the store falls back
|
|
3177
3295
|
* to every column, which overestimates in the open.
|
|
3178
3296
|
*
|
|
3179
|
-
* `related('
|
|
3180
|
-
* parent only as a receiver; `preload('
|
|
3297
|
+
* `related('items').query().count()` reads the RELATION target, and the
|
|
3298
|
+
* parent only as a receiver; `preload('items')` reads the target whole.
|
|
3181
3299
|
*/
|
|
3182
3300
|
if (access.mode === "read") {
|
|
3183
3301
|
const chain = chainShapeOf(call);
|
|
@@ -3318,7 +3436,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3318
3436
|
* Both project-wide facts come from one pass, computed once: which stores the
|
|
3319
3437
|
* application WRITES (maintenance, §6.5.4) and which it ADDRESSES directly
|
|
3320
3438
|
* (grouping, counting-decisions §10). A store reached only through a relation
|
|
3321
|
-
* — `preload('
|
|
3439
|
+
* — `preload('items')`, `related('items').create()` — is read or written, but
|
|
3322
3440
|
* not addressed: the user never sees it outside its parent.
|
|
3323
3441
|
*/
|
|
3324
3442
|
let projectWide;
|
|
@@ -3465,8 +3583,8 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3465
3583
|
}
|
|
3466
3584
|
const returned = facts.deliveries.returns.filter((r) => !item.pick || r.path === item.pick || r.path.startsWith(`${item.pick}.`));
|
|
3467
3585
|
/**
|
|
3468
|
-
* `
|
|
3469
|
-
*
|
|
3586
|
+
* `member.curso` where the body returns the row itself (`return
|
|
3587
|
+
* Member.query()…first()`, path ''): the pick lands INSIDE a returned
|
|
3470
3588
|
* value — one field of a store is one value; one key of a returned call
|
|
3471
3589
|
* is that call picked deeper.
|
|
3472
3590
|
*/
|
|
@@ -3727,7 +3845,7 @@ function storeSymbolsFor(body, file, app, stores, relations = /* @__PURE__ */ ne
|
|
|
3727
3845
|
}
|
|
3728
3846
|
}
|
|
3729
3847
|
/**
|
|
3730
|
-
* `const { default:
|
|
3848
|
+
* `const { default: Article } = await import('#articles/models/article')`: a
|
|
3731
3849
|
* model imported INSIDE the body — an ace command does this to keep the app
|
|
3732
3850
|
* from booting for `--help`. The store is the same; only the binding moved.
|
|
3733
3851
|
*/
|
|
@@ -3808,7 +3926,7 @@ function storeSymbolsFor(body, file, app, stores, relations = /* @__PURE__ */ ne
|
|
|
3808
3926
|
}
|
|
3809
3927
|
}
|
|
3810
3928
|
/**
|
|
3811
|
-
* `const {
|
|
3929
|
+
* `const { order } = input` where `input.order` is a registered path:
|
|
3812
3930
|
* each element inherits the store of its path. Read AFTER the parameters, which
|
|
3813
3931
|
* is where the paths come from.
|
|
3814
3932
|
*/
|
|
@@ -3931,6 +4049,17 @@ const ONE_OF_ROWS = new Set([
|
|
|
3931
4049
|
"first",
|
|
3932
4050
|
"last"
|
|
3933
4051
|
]);
|
|
4052
|
+
/** query-builder terminals that hand back one number or one yes/no, never rows */
|
|
4053
|
+
const AGGREGATES = new Set([
|
|
4054
|
+
"count",
|
|
4055
|
+
"sum",
|
|
4056
|
+
"avg",
|
|
4057
|
+
"min",
|
|
4058
|
+
"max",
|
|
4059
|
+
"exists",
|
|
4060
|
+
"pojo",
|
|
4061
|
+
"pluck"
|
|
4062
|
+
]);
|
|
3934
4063
|
/** array methods whose callback receives one row of the receiver */
|
|
3935
4064
|
const ITERATES_ROWS = new Set([
|
|
3936
4065
|
"map",
|
|
@@ -4015,8 +4144,8 @@ function importMapsOf(file, app) {
|
|
|
4015
4144
|
}
|
|
4016
4145
|
}
|
|
4017
4146
|
/**
|
|
4018
|
-
* `const { default:
|
|
4019
|
-
* `const {
|
|
4147
|
+
* `const { default: SyncCatalog } = await import('#catalog/actions/sync_catalog')`
|
|
4148
|
+
* `const { runInProgress } = await import('#catalog/services/runs')`
|
|
4020
4149
|
*
|
|
4021
4150
|
* A module imported INSIDE a body — the shape ace commands use so `--help` does
|
|
4022
4151
|
* not boot the application. The binding moved; the body it names did not, and
|
|
@@ -4128,8 +4257,15 @@ function isUnreadableWrite(call, symbols, imports, packageImports, body) {
|
|
|
4128
4257
|
const nameNode = declaration.getNameNode();
|
|
4129
4258
|
if (!Node.isIdentifier(nameNode) || nameNode.getText() !== root) continue;
|
|
4130
4259
|
const initializer = declaration.getInitializer();
|
|
4131
|
-
const
|
|
4132
|
-
|
|
4260
|
+
const initialized = initializer ? unwrapAwait(initializer) : null;
|
|
4261
|
+
/**
|
|
4262
|
+
* `await db.transaction(async (trx) => …)` is rooted at a package too, but its value
|
|
4263
|
+
* is what the CALLBACK returns — the application's. Only a call with no function
|
|
4264
|
+
* argument is a package's own object.
|
|
4265
|
+
*/
|
|
4266
|
+
const handsBackACallback = initialized && Node.isCallExpression(initialized) && initialized.getArguments().some((a) => Node.isArrowFunction(a) || Node.isFunctionExpression(a));
|
|
4267
|
+
const origin = initialized ? rootSymbolOf(initialized) : null;
|
|
4268
|
+
if (origin && packageImports.has(origin) && !handsBackACallback) return false;
|
|
4133
4269
|
}
|
|
4134
4270
|
return true;
|
|
4135
4271
|
}
|
|
@@ -5074,13 +5210,16 @@ const RULESET = "afp";
|
|
|
5074
5210
|
* 1.7.0, wide: a write binds to what the variable IS — a destructured named
|
|
5075
5211
|
* interface, a followed method's return, a relation off a row, a loop over rows,
|
|
5076
5212
|
* the guard's user, a service the container made — and a write nobody can type is
|
|
5077
|
-
* an unresolved call instead of silence.
|
|
5213
|
+
* an unresolved call instead of silence. Two in 1.8.0: a method the model
|
|
5214
|
+
* declares names the store its caller writes, and a listener written inline on a
|
|
5215
|
+
* string event is followed like a listener class. One in 1.9.0: the value of a
|
|
5216
|
+
* transaction callback is what its returns name.
|
|
5078
5217
|
*
|
|
5079
5218
|
* Without the bump, a baseline saved by the previous version compares cleanly
|
|
5080
5219
|
* against this one and bills the tool's own improvement as work done. The guard
|
|
5081
5220
|
* exists for exactly that, and only this constant arms it.
|
|
5082
5221
|
*/
|
|
5083
|
-
const RULESET_VERSION = "1.
|
|
5222
|
+
const RULESET_VERSION = "1.9.0";
|
|
5084
5223
|
function count(input, options = {}) {
|
|
5085
5224
|
const warnings = [];
|
|
5086
5225
|
const usage = usageOf(input);
|
|
@@ -5228,7 +5367,7 @@ function unreadableDeliveryWarnings(input) {
|
|
|
5228
5367
|
if (blind.length === 0) return [];
|
|
5229
5368
|
return [
|
|
5230
5369
|
`${blind.length} transaction(s) deliver a value the analysis cannot read — a generated document, a call nobody followed — counted as 1 DET each, a FLOOR. This UNDERSTATES the output:`,
|
|
5231
|
-
...blind.slice(0, 10).map(({ entry, behavior }) => ` ${entry.
|
|
5370
|
+
...blind.slice(0, 10).map(({ entry, behavior }) => ` ${entry.identity}: ${behavior.delivered.opaqueFields.join(", ")}`),
|
|
5232
5371
|
...blind.length > 10 ? [` … and ${blind.length - 10} more`] : []
|
|
5233
5372
|
];
|
|
5234
5373
|
}
|
|
@@ -5244,7 +5383,7 @@ function unreadablePageWarnings(input) {
|
|
|
5244
5383
|
for (const entry of input.entryPoints) {
|
|
5245
5384
|
const behavior = input.behaviors.get(entry.id);
|
|
5246
5385
|
if (!behavior || behavior.writes) continue;
|
|
5247
|
-
for (const [store, reason] of Object.entries(behavior.unreadablePages ?? {})) lines.push(` ${entry.
|
|
5386
|
+
for (const [store, reason] of Object.entries(behavior.unreadablePages ?? {})) lines.push(` ${entry.identity}: ${store} leaves whole — ${reason}`);
|
|
5248
5387
|
}
|
|
5249
5388
|
if (lines.length === 0) return [];
|
|
5250
5389
|
return [
|
|
@@ -5378,7 +5517,7 @@ function unreadableInputWarnings(input) {
|
|
|
5378
5517
|
if (blind.length === 0) return [];
|
|
5379
5518
|
return [
|
|
5380
5519
|
`${blind.length} transaction(s) read the request without enumerating fields (\`all()\`, \`body()\`, \`except()\`), so their input DETs could not be counted and each sits at the floor of its band. This UNDERSTATES the total — the fix is a validator, not a configuration:`,
|
|
5381
|
-
...blind.slice(0, 10).map(({ entry }) => ` ${entry.
|
|
5520
|
+
...blind.slice(0, 10).map(({ entry }) => ` ${entry.identity}`),
|
|
5382
5521
|
...blind.length > 10 ? [` … and ${blind.length - 10} more`] : []
|
|
5383
5522
|
];
|
|
5384
5523
|
}
|
|
@@ -5399,7 +5538,7 @@ function unreadableOutputWarnings(input) {
|
|
|
5399
5538
|
if (blind.length === 0) return [];
|
|
5400
5539
|
return [
|
|
5401
5540
|
`${blind.length} transaction(s) pass through a transformer that spreads something the analysis cannot read, counted as 1 DET each — a FLOOR. This UNDERSTATES the output; the fix is in the transformer (\`this.pick(...)\` or named keys), not a configuration:`,
|
|
5402
|
-
...blind.slice(0, 10).map(({ entry, behavior }) => ` ${entry.
|
|
5541
|
+
...blind.slice(0, 10).map(({ entry, behavior }) => ` ${entry.identity}: ${behavior.opaqueOutputFields.join(", ")}`),
|
|
5403
5542
|
...blind.length > 10 ? [` … and ${blind.length - 10} more`] : []
|
|
5404
5543
|
];
|
|
5405
5544
|
}
|
|
@@ -5537,20 +5676,32 @@ function totalsOf(functions) {
|
|
|
5537
5676
|
* reader must see that without having to go looking.
|
|
5538
5677
|
*/
|
|
5539
5678
|
function confidenceOf(input, warnings) {
|
|
5540
|
-
let unresolvedCalls = 0;
|
|
5541
5679
|
let withoutHandler = 0;
|
|
5680
|
+
const sites = /* @__PURE__ */ new Map();
|
|
5681
|
+
for (const site of input.unresolved ?? []) sites.set(`${site.file}:${site.line}:${site.expression}`, { ...site });
|
|
5542
5682
|
for (const entry of input.entryPoints) {
|
|
5543
5683
|
const behavior = input.behaviors.get(entry.id);
|
|
5544
5684
|
if (!behavior) {
|
|
5545
5685
|
withoutHandler++;
|
|
5546
5686
|
continue;
|
|
5547
5687
|
}
|
|
5548
|
-
|
|
5688
|
+
if (input.unresolved) continue;
|
|
5689
|
+
for (const call of behavior.unresolved) {
|
|
5690
|
+
const key = `${call.file}:${call.line}:${call.expression}`;
|
|
5691
|
+
const site = sites.get(key);
|
|
5692
|
+
if (site) site.transactions++;
|
|
5693
|
+
else sites.set(key, {
|
|
5694
|
+
...call,
|
|
5695
|
+
transactions: 1
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5549
5698
|
}
|
|
5699
|
+
const unresolved = [...sites.values()].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
5550
5700
|
return {
|
|
5551
|
-
unresolvedCalls,
|
|
5701
|
+
unresolvedCalls: unresolved.length,
|
|
5552
5702
|
entryPointsWithoutHandler: withoutHandler,
|
|
5553
|
-
warnings
|
|
5703
|
+
warnings,
|
|
5704
|
+
unresolved
|
|
5554
5705
|
};
|
|
5555
5706
|
}
|
|
5556
5707
|
//#endregion
|
|
@@ -5627,7 +5778,27 @@ async function analyze(root, options = {}) {
|
|
|
5627
5778
|
});
|
|
5628
5779
|
const behaviors = new Map(entryPoints.filter((entry) => entry.handler).map((entry) => [entry.id, analyzer.analyze(entry.handler)]));
|
|
5629
5780
|
const resolved = [...behaviors.values()].filter((behavior) => behavior.unresolved.length === 0).length;
|
|
5630
|
-
|
|
5781
|
+
/**
|
|
5782
|
+
* One site, however many transactions reach it: a body five routes walk is one gap
|
|
5783
|
+
* to close, not five. The count shows the same number — a reviewing team found 10
|
|
5784
|
+
* in one report and 13 in the other and could act on neither.
|
|
5785
|
+
*/
|
|
5786
|
+
const sites = /* @__PURE__ */ new Map();
|
|
5787
|
+
for (const problem of [...storeProblems, ...routeProblems]) sites.set(`${problem.file}:${problem.line}:${problem.expression}`, {
|
|
5788
|
+
...problem,
|
|
5789
|
+
transactions: 0
|
|
5790
|
+
});
|
|
5791
|
+
for (const behavior of behaviors.values()) for (const call of behavior.unresolved) {
|
|
5792
|
+
const key = `${call.file}:${call.line}:${call.expression}`;
|
|
5793
|
+
const site = sites.get(key);
|
|
5794
|
+
if (site) site.transactions++;
|
|
5795
|
+
else sites.set(key, {
|
|
5796
|
+
...call,
|
|
5797
|
+
transactions: 1
|
|
5798
|
+
});
|
|
5799
|
+
}
|
|
5800
|
+
const unresolvedSites = [...sites.values()].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
5801
|
+
const unresolvedCalls = unresolvedSites.length;
|
|
5631
5802
|
/**
|
|
5632
5803
|
* Every path that LEAVES is relative to the application root.
|
|
5633
5804
|
*
|
|
@@ -5740,7 +5911,11 @@ async function analyze(root, options = {}) {
|
|
|
5740
5911
|
entryPointsResolved: resolved,
|
|
5741
5912
|
unresolvedCalls,
|
|
5742
5913
|
ratio: entryPoints.length === 0 ? 1 : resolved / entryPoints.length
|
|
5743
|
-
}
|
|
5914
|
+
},
|
|
5915
|
+
unresolved: unresolvedSites.map((site) => ({
|
|
5916
|
+
...site,
|
|
5917
|
+
file: emit(site.file)
|
|
5918
|
+
}))
|
|
5744
5919
|
};
|
|
5745
5920
|
const minimum = options.minCoverage ?? 0;
|
|
5746
5921
|
if (inventory.coverage.ratio < minimum) throw new CoverageTooLowError(inventory.coverage.ratio, minimum);
|
|
@@ -5754,6 +5929,7 @@ async function analyze(root, options = {}) {
|
|
|
5754
5929
|
behaviors,
|
|
5755
5930
|
jsonSchemas,
|
|
5756
5931
|
jobs: collectJobs(app),
|
|
5932
|
+
unresolved: inventory.unresolved,
|
|
5757
5933
|
writtenAnywhere: analyzer.writtenAnywhere(),
|
|
5758
5934
|
addressedAnywhere: analyzer.addressedAnywhere(),
|
|
5759
5935
|
seededAnywhere: analyzer.seededAnywhere()
|
|
@@ -387,10 +387,10 @@ function classOfReceiver(receiver) {
|
|
|
387
387
|
return target.isKind(SyntaxKind.Identifier) ? target.getText() : null;
|
|
388
388
|
}
|
|
389
389
|
/**
|
|
390
|
-
* `const svc = await app.container.make(
|
|
390
|
+
* `const svc = await app.container.make(AssignmentService)`: the container hands
|
|
391
391
|
* back an instance of the class named — the same binding as `new`, written the
|
|
392
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 /
|
|
393
|
+
* reviewed application this shape carried the write of `POST /assignments`
|
|
394
394
|
* and 31 more sites, and none was followed (plan 0.8 §B).
|
|
395
395
|
*/
|
|
396
396
|
if (init?.isKind(SyntaxKind.CallExpression)) {
|
|
@@ -414,6 +414,7 @@ function collectEventBindings(app) {
|
|
|
414
414
|
compilerOptions: { allowJs: false }
|
|
415
415
|
});
|
|
416
416
|
for (const root of app.scanRoots) project.addSourceFilesAtPaths(`${root}/**/*.ts`);
|
|
417
|
+
project.addSourceFilesAtPaths(`${toPosix(app.root)}/start/**/*.ts`);
|
|
417
418
|
const bindings = /* @__PURE__ */ new Map();
|
|
418
419
|
for (const file of project.getSourceFiles()) for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
419
420
|
const expression = call.getExpression();
|
|
@@ -421,11 +422,16 @@ function collectEventBindings(app) {
|
|
|
421
422
|
if (expression.getName() !== "on") continue;
|
|
422
423
|
const [event, handlers] = call.getArguments();
|
|
423
424
|
if (!event || !handlers) continue;
|
|
424
|
-
|
|
425
|
-
|
|
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;
|
|
426
432
|
const refs = listenersOf(handlers, file, app);
|
|
427
433
|
if (refs.length === 0) continue;
|
|
428
|
-
bindings.set(
|
|
434
|
+
bindings.set(key, [...bindings.get(key) ?? [], ...refs]);
|
|
429
435
|
}
|
|
430
436
|
return bindings;
|
|
431
437
|
}
|
|
@@ -451,10 +457,23 @@ function resolveEventClass(expression, from, app) {
|
|
|
451
457
|
return registryEntry(registry, expression.getName(), from.getProject(), app);
|
|
452
458
|
}
|
|
453
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}`;
|
|
454
462
|
function listenersOf(handlers, from, app) {
|
|
455
463
|
const entries = handlers.isKind(SyntaxKind.ArrayLiteralExpression) ? handlers.getElements() : [handlers];
|
|
456
464
|
const refs = [];
|
|
457
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
|
+
}
|
|
458
477
|
/**
|
|
459
478
|
* `[SomeListener, 'method']`: AdonisJS lets the binding name the method,
|
|
460
479
|
* and taking `handle` on faith there would look for a body that is not
|
|
@@ -528,6 +547,8 @@ function registryEntry(registryFile, key, project, app) {
|
|
|
528
547
|
}
|
|
529
548
|
//#endregion
|
|
530
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"]);
|
|
531
552
|
/**
|
|
532
553
|
* "Event" pattern: the handler announces, and listeners act.
|
|
533
554
|
*
|
|
@@ -550,6 +571,18 @@ const eventDispatchResolver = {
|
|
|
550
571
|
if (ctx.eventBindings.size === 0) return [];
|
|
551
572
|
const expression = call.getExpression();
|
|
552
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
|
+
}
|
|
553
586
|
if (expression.getName() !== "dispatch") return [];
|
|
554
587
|
const receiver = expression.getExpression();
|
|
555
588
|
if (!Node.isIdentifier(receiver) && !Node.isPropertyAccessExpression(receiver)) return [];
|
|
@@ -639,7 +672,7 @@ const jobDispatchResolver = {
|
|
|
639
672
|
* "Local function" pattern: a helper declared in the same file, not imported.
|
|
640
673
|
*
|
|
641
674
|
* const lista = await proximos(id) // function proximos() { … }
|
|
642
|
-
* return rows.map(
|
|
675
|
+
* return rows.map(toRow) // const toRow = (row) => …
|
|
643
676
|
*
|
|
644
677
|
* A query object that keeps its helpers beside it is common, and before this
|
|
645
678
|
* every such call was unresolved: the store a helper read was reached by nobody
|
|
@@ -663,7 +696,7 @@ const localFunctionResolver = {
|
|
|
663
696
|
}];
|
|
664
697
|
}
|
|
665
698
|
};
|
|
666
|
-
/** callbacks that apply a function to each element: `rows.map(
|
|
699
|
+
/** callbacks that apply a function to each element: `rows.map(toRow)` calls `toRow` */
|
|
667
700
|
const APPLIES_CALLBACK = new Set([
|
|
668
701
|
"map",
|
|
669
702
|
"flatMap",
|
|
@@ -676,8 +709,8 @@ const APPLIES_CALLBACK = new Set([
|
|
|
676
709
|
"reduce"
|
|
677
710
|
]);
|
|
678
711
|
/**
|
|
679
|
-
* The function a call names: `proximos(id)` names `proximos`; `rows.map(
|
|
680
|
-
* 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
|
|
681
714
|
* same, whichever way it was reached.
|
|
682
715
|
*/
|
|
683
716
|
function calledFunctionOf(call) {
|
|
@@ -864,7 +897,7 @@ function transformerResourceOf(cls) {
|
|
|
864
897
|
* { autor: AutorTransformer.transform } 0 here; the nested body contributes
|
|
865
898
|
* { endereco: { rua, cidade } } leaves individually
|
|
866
899
|
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
867
|
-
* {
|
|
900
|
+
* { items: xs.map((i) => ({ a, b })) } the leaves, once
|
|
868
901
|
* ...this.pick(this.resource, [...]) the listed names
|
|
869
902
|
* ...this.toObject() 0 here; the followed body contributes
|
|
870
903
|
* ...anythingElse 1, opaque, reported
|
|
@@ -904,7 +937,7 @@ function outputFieldsIn(body, owner, stores, followed) {
|
|
|
904
937
|
* { autor: AutorTransformer.transform } 0 here; the followed body contributes
|
|
905
938
|
* { endereco: { rua, cidade } } leaves individually
|
|
906
939
|
* { tags: xs.map((t) => t.nome) } 1 — a repeating group of one attribute
|
|
907
|
-
* {
|
|
940
|
+
* { items: xs.map((i) => ({ a, b })) } the leaves, once
|
|
908
941
|
* ...this.pick(this.resource, [...]) the listed names
|
|
909
942
|
* ...this.toObject() 0 here; the followed body contributes
|
|
910
943
|
* ...anythingElse 1, opaque, reported
|
|
@@ -1241,7 +1274,7 @@ const BUILTIN_CALL_RESOLVERS = [
|
|
|
1241
1274
|
if (!found) return [];
|
|
1242
1275
|
const { file, owner } = found;
|
|
1243
1276
|
/**
|
|
1244
|
-
* `X.transform(p).useVariant('
|
|
1277
|
+
* `X.transform(p).useVariant('forSummary')`: the variant is a METHOD of the
|
|
1245
1278
|
* transformer, named after it, and it REPLACES `toObject()` — the shape that
|
|
1246
1279
|
* leaves is the variant's. The chain is visited call by call: the `useVariant`
|
|
1247
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-4sZBYGKU.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';
|
|
@@ -44,13 +44,16 @@ export declare const RULESET = "afp";
|
|
|
44
44
|
* 1.7.0, wide: a write binds to what the variable IS — a destructured named
|
|
45
45
|
* interface, a followed method's return, a relation off a row, a loop over rows,
|
|
46
46
|
* the guard's user, a service the container made — and a write nobody can type is
|
|
47
|
-
* an unresolved call instead of silence.
|
|
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. One in 1.9.0: the value of a
|
|
50
|
+
* transaction callback is what its returns name.
|
|
48
51
|
*
|
|
49
52
|
* Without the bump, a baseline saved by the previous version compares cleanly
|
|
50
53
|
* against this one and bills the tool's own improvement as work done. The guard
|
|
51
54
|
* exists for exactly that, and only this constant arms it.
|
|
52
55
|
*/
|
|
53
|
-
export declare const RULESET_VERSION = "1.
|
|
56
|
+
export declare const RULESET_VERSION = "1.9.0";
|
|
54
57
|
export type CountInput = {
|
|
55
58
|
app: AppContext;
|
|
56
59
|
stores: CollectedDataStore[];
|
|
@@ -61,6 +64,12 @@ export type CountInput = {
|
|
|
61
64
|
jsonSchemas?: Map<string, DiscoveredSchema>;
|
|
62
65
|
/** the queue jobs and who dispatches each — a job no transaction reaches is reported (plan 0.7 §D) */
|
|
63
66
|
jobs?: CollectedJob[];
|
|
67
|
+
/**
|
|
68
|
+
* The unresolved call sites as the inventory lists them — one per site, route and
|
|
69
|
+
* store problems included. Given, the count shows the same number and the same list;
|
|
70
|
+
* absent, it lists the sites the behaviors show, one each.
|
|
71
|
+
*/
|
|
72
|
+
unresolved?: UnresolvedSite[];
|
|
64
73
|
/**
|
|
65
74
|
* Stores written anywhere in the application's code, reachable from an entry
|
|
66
75
|
* 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-4sZBYGKU.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-b1tgLA1B.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -43,7 +43,7 @@ type DeliveredValue =
|
|
|
43
43
|
args: Delivery[];
|
|
44
44
|
/**
|
|
45
45
|
* One key of what the call returns — `const { data } = await q.handle()`,
|
|
46
|
-
* `
|
|
46
|
+
* `report.rows` — rather than the whole result. Resolved against the
|
|
47
47
|
* body's classified return, keeping only that key.
|
|
48
48
|
*/
|
|
49
49
|
pick?: string;
|
|
@@ -64,7 +64,11 @@ type DeliveredValue =
|
|
|
64
64
|
path: string;
|
|
65
65
|
expression: string;
|
|
66
66
|
};
|
|
67
|
-
/**
|
|
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
|
+
*/
|
|
68
72
|
export declare const PASSES_ROWS: Set<string>;
|
|
69
73
|
export type DeliveryContext = {
|
|
70
74
|
body: Node;
|
|
@@ -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
|
|
@@ -30,7 +30,7 @@ export type PageReading = {
|
|
|
30
30
|
columns: Map<string, Set<string>>;
|
|
31
31
|
/** store -> why the page could not be read for it (the store then leaves whole) */
|
|
32
32
|
unreadable: Map<string, string>;
|
|
33
|
-
/** store -> members the page reads that are NOT its columns (`
|
|
33
|
+
/** store -> members the page reads that are NOT its columns (`member.displayName`, computed on the way) */
|
|
34
34
|
unknownMembers: Map<string, Set<string>>;
|
|
35
35
|
};
|
|
36
36
|
export type PageEnvironment = {
|
|
@@ -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-4sZBYGKU.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
|
@@ -190,6 +190,11 @@ export type UnresolvedCall = {
|
|
|
190
190
|
expression: string;
|
|
191
191
|
reason: string;
|
|
192
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
|
+
};
|
|
193
198
|
export type Inventory = {
|
|
194
199
|
/** format version, for diffs across releases */
|
|
195
200
|
version: 1;
|
|
@@ -207,10 +212,16 @@ export type Inventory = {
|
|
|
207
212
|
coverage: {
|
|
208
213
|
entryPointsTotal: number;
|
|
209
214
|
entryPointsResolved: number;
|
|
215
|
+
/** distinct call sites nobody could follow, route and store problems included — the number the count shows too */
|
|
210
216
|
unresolvedCalls: number;
|
|
211
217
|
/** fraction of entry points whose handler was traced to completion */
|
|
212
218
|
ratio: number;
|
|
213
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[];
|
|
214
225
|
};
|
|
215
226
|
export type FunctionType = 'ILF' | 'EIF' | 'EI' | 'EO' | 'EQ';
|
|
216
227
|
export type Complexity = 'low' | 'average' | 'high';
|
|
@@ -283,9 +294,12 @@ export type CountResult = {
|
|
|
283
294
|
};
|
|
284
295
|
/** flags when the count does not deserve confidence */
|
|
285
296
|
confidence: {
|
|
297
|
+
/** distinct call sites nobody could follow — the same number the inventory shows */
|
|
286
298
|
unresolvedCalls: number;
|
|
287
299
|
entryPointsWithoutHandler: number;
|
|
288
300
|
warnings: string[];
|
|
301
|
+
/** the sites themselves, so the report can list them; absent on a count built by hand */
|
|
302
|
+
unresolved?: UnresolvedSite[];
|
|
289
303
|
};
|
|
290
304
|
};
|
|
291
305
|
/** Maintenance type, for enhancement-project counting. */
|