@filipebraida/adonis-function-points 0.10.0 → 0.11.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 +53 -0
- package/build/commands/main.js +6 -6
- package/build/{fp_calibrate-DUPUXVaJ.js → fp_calibrate-U5xdt5Qe.js} +1 -1
- package/build/{fp_count-B5hxDyGJ.js → fp_count-mDQgQeEq.js} +1 -1
- package/build/{fp_diff-7S8NcKIY.js → fp_diff-BnWi2Fx8.js} +1 -1
- package/build/{fp_explain-C4X_-Rp7.js → fp_explain-CQESTS5W.js} +1 -1
- package/build/{fp_inventory-BJlIoetL.js → fp_inventory-D3_6QCD2.js} +1 -1
- package/build/{fp_metrics-BDKaNL0B.js → fp_metrics-lnnhnVaw.js} +1 -1
- package/build/index.js +2 -2
- package/build/{pipeline-4sZBYGKU.js → pipeline-CdDJRsLM.js} +308 -27
- package/build/{resolvers-CneCj3sT.js → resolvers-Bz1F-Tgf.js} +1 -1
- package/build/{runners-b1tgLA1B.js → runners-CzzlX7h-.js} +4 -3
- package/build/src/cli.js +2 -2
- package/build/src/inventory/graph/call_graph.d.ts +16 -0
- package/build/src/inventory/graph/noise.d.ts +4 -1
- package/build/src/inventory/resolvers/index.js +1 -1
- package/build/src/inventory/sources/data_stores.d.ts +7 -0
- package/build/src/pipeline.js +1 -1
- package/build/src/types.d.ts +12 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,59 @@ 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.11.0
|
|
10
|
+
|
|
11
|
+
Rule set unchanged (`afp@1.9.0`); no number moves. What changes is what the **coverage**
|
|
12
|
+
means: it measures what of the application the walk could not follow, and nothing else.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **A call into a package is outside the boundary — noted, not an unresolved call.** An
|
|
17
|
+
authorisation service from a package injected into the application's façade, a variant of an
|
|
18
|
+
attachment whose column type is a package's, a content collection built by a package factory,
|
|
19
|
+
a mailer method inherited from a package base: none is code the analysis should follow, so none
|
|
20
|
+
is a gap it failed to follow. What a package does with its own tables is technical (§4); what it
|
|
21
|
+
hands back is a value. Read structurally — the injected property's declared type, the model
|
|
22
|
+
property's declared type, the exported const's factory, the class's base, each resolving to a
|
|
23
|
+
specifier the application does not — never by package name. One note per site in
|
|
24
|
+
`inventory.notes`, naming the transactions that reach it; behind the same noise gate as an
|
|
25
|
+
unresolved call, so what was silent stays silent. A receiver typed by the application stays a
|
|
26
|
+
gap. Fixture `fronteira_pacote` (23 FP before and after; four unresolved calls become three
|
|
27
|
+
notes and the one gap of the application).
|
|
28
|
+
- **A `Map` or `Set` that arrives through the constructor, one level down a named type**
|
|
29
|
+
(`this.extras?.panel?.get(id)` with `extras?: Extras`, `Extras = { panel: Map<…> }`) is noise,
|
|
30
|
+
as `private names = new Map()` already was. Fixture `patterns/native_receivers`.
|
|
31
|
+
- **A command's `@flags.*` / `@args.*` property is a string, a number or a boolean**: a method on
|
|
32
|
+
it (`this.name.trim()`) is the language's, not a gap.
|
|
33
|
+
- **A method of an interface says so**: "interface method: the implementation is injected at
|
|
34
|
+
runtime and cannot be followed statically" — a gap, with the right words, where "probably
|
|
35
|
+
inherited from a package class" pointed the reader at the wrong place.
|
|
36
|
+
- **A local mixin factory whose returned class declares no `@column`** adds behaviour, not
|
|
37
|
+
attributes: a note. One that declares a column stays a gap — reading its columns off the
|
|
38
|
+
returned class is a rule for a release that moves numbers.
|
|
39
|
+
|
|
40
|
+
On the three validated applications, unresolved calls go from 13 / 8 / 12 to **0 / 0 / 1** —
|
|
41
|
+
the one an interface method, which is a gap of the application — with no point moved.
|
|
42
|
+
|
|
43
|
+
### Documented
|
|
44
|
+
|
|
45
|
+
- counting-decisions §4 gains "A call into a package is outside the boundary".
|
|
46
|
+
|
|
47
|
+
## 0.10.1
|
|
48
|
+
|
|
49
|
+
Rule set unchanged (`afp@1.9.0`); no number moves.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **A base class or a mixin from a package is a fact about the declaration, not an
|
|
54
|
+
unresolved call.** `compose(Base, Auditable)`, `withAuthFinder(…)` inline or through a local
|
|
55
|
+
const (which came out as "base class not found in the application" — code that is in the
|
|
56
|
+
application, built by a package's factory). Nobody follows a mixin, and in function points
|
|
57
|
+
it is nothing: what it adds is technical, never a user-recognisable attribute; the model's
|
|
58
|
+
own columns and the schema decide. Listed as a **note** by `fp:inventory` and in
|
|
59
|
+
`inventory.notes`, kept out of the coverage number. A base of the application that was not
|
|
60
|
+
found stays an unresolved call.
|
|
61
|
+
|
|
9
62
|
## 0.10.0
|
|
10
63
|
|
|
11
64
|
**Rule set `afp@1.9.0`.** One rule and two report fixes, from a team's third review of their
|
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-D3_6QCD2.js")
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
commandName: "fp:metrics",
|
|
21
|
-
importer: () => import("../fp_metrics-
|
|
21
|
+
importer: () => import("../fp_metrics-lnnhnVaw.js")
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
commandName: "fp:count",
|
|
25
|
-
importer: () => import("../fp_count-
|
|
25
|
+
importer: () => import("../fp_count-mDQgQeEq.js")
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
commandName: "fp:explain",
|
|
29
|
-
importer: () => import("../fp_explain-
|
|
29
|
+
importer: () => import("../fp_explain-CQESTS5W.js")
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
commandName: "fp:diff",
|
|
33
|
-
importer: () => import("../fp_diff-
|
|
33
|
+
importer: () => import("../fp_diff-BnWi2Fx8.js")
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
commandName: "fp:calibrate",
|
|
37
|
-
importer: () => import("../fp_calibrate-
|
|
37
|
+
importer: () => import("../fp_calibrate-U5xdt5Qe.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-CzzlX7h-.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-CzzlX7h-.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-CzzlX7h-.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-CzzlX7h-.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-CzzlX7h-.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-CzzlX7h-.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-Bz1F-Tgf.js";
|
|
5
|
+
import { a as DEFAULT_TECHNICAL_PATTERNS, i as RULESET_VERSION, n as analyze, r as RULESET, t as CoverageTooLowError } from "./pipeline-CdDJRsLM.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
|
|
1
|
+
import { _ as isSeeder, a as chainShapeOf, b as toPosix, c as transformerResourceOf, d as EXECUTION_METHODS, f as collectEventBindings, g as isApplicationCode, h as rootSymbolOf, i as resolveCall, l as unwrap$1, m as hooksFiredBy, o as mappedLiteralOf, p as detectAccess, r as isTechnicalWrite, s as outputFieldsIn, t as BUILTIN_CALL_RESOLVERS, u as DISPATCH_METHODS, v as relativeTo, y as samePath } from "./resolvers-Bz1F-Tgf.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";
|
|
@@ -384,6 +384,7 @@ async function collectDataStores(app) {
|
|
|
384
384
|
if (app.generated.dataSchema) project.addSourceFileAtPathIfExists(app.generated.dataSchema);
|
|
385
385
|
const candidates = [];
|
|
386
386
|
const unresolved = [];
|
|
387
|
+
const notes = [];
|
|
387
388
|
/**
|
|
388
389
|
* Classes appearing as an ANCESTOR of some model.
|
|
389
390
|
*
|
|
@@ -395,7 +396,7 @@ async function collectDataStores(app) {
|
|
|
395
396
|
for (const file of project.getSourceFiles()) {
|
|
396
397
|
if (samePath(file.getFilePath(), app.generated.dataSchema)) continue;
|
|
397
398
|
for (const cls of file.getClasses()) {
|
|
398
|
-
const described = describeStore(cls, app, project, unresolved);
|
|
399
|
+
const described = describeStore(cls, app, project, unresolved, notes);
|
|
399
400
|
if (!described) continue;
|
|
400
401
|
candidates.push(described.store);
|
|
401
402
|
for (const ancestor of described.ancestors) ancestors.add(ancestor);
|
|
@@ -403,18 +404,20 @@ async function collectDataStores(app) {
|
|
|
403
404
|
}
|
|
404
405
|
return {
|
|
405
406
|
stores: candidates.filter((store) => !ancestors.has(store.id)).sort(byName),
|
|
406
|
-
unresolved
|
|
407
|
+
unresolved,
|
|
408
|
+
notes
|
|
407
409
|
};
|
|
408
410
|
}
|
|
409
411
|
/** stable identity of a class, to separate a base from a store */
|
|
410
412
|
const classKey = (cls) => `${cls.getSourceFile().getFilePath()}#${cls.getName()}`;
|
|
411
413
|
const byName = (a, b) => a.name.localeCompare(b.name);
|
|
412
|
-
function describeStore(cls, app, project, unresolved) {
|
|
414
|
+
function describeStore(cls, app, project, unresolved, notes) {
|
|
413
415
|
const name = cls.getName();
|
|
414
416
|
if (!name) return null;
|
|
415
417
|
const chain = walkChain(cls, app, project);
|
|
416
418
|
if (!chain.reachesLucid) return null;
|
|
417
419
|
unresolved.push(...chain.unresolved);
|
|
420
|
+
notes.push(...chain.notes);
|
|
418
421
|
const file = cls.getSourceFile().getFilePath();
|
|
419
422
|
return {
|
|
420
423
|
store: {
|
|
@@ -455,6 +458,7 @@ function walkChain(start, app, project) {
|
|
|
455
458
|
* the coverage report.
|
|
456
459
|
*/
|
|
457
460
|
const unresolved = [];
|
|
461
|
+
const notes = [];
|
|
458
462
|
let reachesLucid = false;
|
|
459
463
|
let columnSource = "ast";
|
|
460
464
|
const visit = (cls) => {
|
|
@@ -475,6 +479,31 @@ function walkChain(start, app, project) {
|
|
|
475
479
|
visit(resolved);
|
|
476
480
|
continue;
|
|
477
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* A base or a mixin from a PACKAGE — `compose(Base, Auditable)` imported from
|
|
484
|
+
* outside, `withAuthFinder(…)` inline or through a local const — is a fact about
|
|
485
|
+
* the declaration, not a gap in the walk: nobody follows a mixin. What it adds is
|
|
486
|
+
* technical (a hashed password, audit stamps) and never a user-recognisable
|
|
487
|
+
* attribute (§7.2); the model's own columns and the schema decide. Noted, and kept
|
|
488
|
+
* out of the coverage number. A base of the application that was not found stays
|
|
489
|
+
* a gap.
|
|
490
|
+
*/
|
|
491
|
+
/**
|
|
492
|
+
* A mixin factory OF THE APPLICATION whose returned class declares no `@column`
|
|
493
|
+
* adds methods, not attributes: a fact, not a gap. One that declares columns
|
|
494
|
+
* stays a gap — reading them off the returned class is a rule for a release
|
|
495
|
+
* that moves numbers (plan 0.11 §E).
|
|
496
|
+
*/
|
|
497
|
+
const columnless = localFactoryWithoutColumns(parent, cls.getSourceFile(), app, project);
|
|
498
|
+
if (columnless) {
|
|
499
|
+
notes.push(`${cls.getName() ?? "?"}: local mixin factory ${columnless} declares no column — adds behaviour, not attributes`);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const packaged = packageOriginOf(parent, cls.getSourceFile(), app);
|
|
503
|
+
if (packaged) {
|
|
504
|
+
notes.push(`${cls.getName() ?? "?"}: base or mixin from ${packaged.specifier} (${packaged.expression}) — technical, adds no user-recognisable attribute; the model's own columns and the schema decide`);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
478
507
|
unresolved.push({
|
|
479
508
|
file: cls.getSourceFile().getFilePath(),
|
|
480
509
|
line: parent.getStartLineNumber(),
|
|
@@ -489,8 +518,75 @@ function walkChain(start, app, project) {
|
|
|
489
518
|
classes,
|
|
490
519
|
attributes: [...attributes.values()],
|
|
491
520
|
columnSource,
|
|
492
|
-
unresolved
|
|
521
|
+
unresolved,
|
|
522
|
+
notes
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* `compose(Base, withTracking())` where `withTracking` is a function of the application
|
|
527
|
+
* whose body returns a class expression: the factory's name when that class declares no
|
|
528
|
+
* `@column`, `null` when it declares one (or when the factory cannot be read).
|
|
529
|
+
*/
|
|
530
|
+
function localFactoryWithoutColumns(parent, file, app, project) {
|
|
531
|
+
if (!Node.isCallExpression(parent)) return null;
|
|
532
|
+
const callee = parent.getExpression();
|
|
533
|
+
if (!Node.isIdentifier(callee)) return null;
|
|
534
|
+
const name = callee.getText();
|
|
535
|
+
let declared = file.getFunction(name);
|
|
536
|
+
if (!declared) {
|
|
537
|
+
const origin = originOf$1(name, file);
|
|
538
|
+
const target = origin ? app.resolveSpecifier(origin.specifier) : null;
|
|
539
|
+
declared = (target ? project.getSourceFile(target) ?? project.addSourceFileAtPathIfExists(target) : null)?.getFunction(origin?.exportedName === "default" ? name : origin?.exportedName ?? name) ?? void 0;
|
|
540
|
+
}
|
|
541
|
+
if (!declared) return null;
|
|
542
|
+
return [...declared.getDescendantsOfKind(SyntaxKind.ClassExpression), ...declared.getDescendantsOfKind(SyntaxKind.ClassDeclaration)].some((c) => c.getProperties().some((p) => p.getDecorators().some((d) => /^column\b/.test(d.getFullName())))) ? null : `${name}()`;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Where a base or a mixin comes from, when it comes from a package: the class
|
|
546
|
+
* imported from a specifier the application does not resolve, a factory call whose
|
|
547
|
+
* callee is, or a local const initialised by such a call.
|
|
548
|
+
*/
|
|
549
|
+
function packageOriginOf(parent, file, app) {
|
|
550
|
+
const fromPackage = (local) => {
|
|
551
|
+
const origin = originOf$1(local, file);
|
|
552
|
+
return origin && !app.resolveSpecifier(origin.specifier) ? origin.specifier : null;
|
|
553
|
+
};
|
|
554
|
+
const rootOf = (node) => {
|
|
555
|
+
let current = node;
|
|
556
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
557
|
+
if (Node.isCallExpression(current) || Node.isPropertyAccessExpression(current)) {
|
|
558
|
+
current = current.getExpression();
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
return Node.isIdentifier(current) ? current.getText() : null;
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
};
|
|
565
|
+
const brief = (text) => text.replace(/\s+/g, " ").slice(0, 60);
|
|
566
|
+
if (Node.isCallExpression(parent)) {
|
|
567
|
+
const root = rootOf(parent);
|
|
568
|
+
const specifier = root ? fromPackage(root) : null;
|
|
569
|
+
return specifier ? {
|
|
570
|
+
specifier,
|
|
571
|
+
expression: brief(parent.getText())
|
|
572
|
+
} : null;
|
|
573
|
+
}
|
|
574
|
+
if (!Node.isIdentifier(parent)) return null;
|
|
575
|
+
const direct = fromPackage(parent.getText());
|
|
576
|
+
if (direct) return {
|
|
577
|
+
specifier: direct,
|
|
578
|
+
expression: parent.getText()
|
|
493
579
|
};
|
|
580
|
+
const initializer = file.getVariableDeclaration(parent.getText())?.getInitializer();
|
|
581
|
+
if (initializer && Node.isCallExpression(initializer)) {
|
|
582
|
+
const root = rootOf(initializer);
|
|
583
|
+
const specifier = root ? fromPackage(root) : null;
|
|
584
|
+
if (specifier) return {
|
|
585
|
+
specifier,
|
|
586
|
+
expression: `${parent.getText()} = ${brief(initializer.getText())}`
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
494
590
|
}
|
|
495
591
|
/**
|
|
496
592
|
* Why the base class could not be resolved.
|
|
@@ -1193,8 +1289,7 @@ const FRAMEWORK_SERVICES = new Set([
|
|
|
1193
1289
|
"app",
|
|
1194
1290
|
"prompt"
|
|
1195
1291
|
]);
|
|
1196
|
-
|
|
1197
|
-
function isNoise(call, owner) {
|
|
1292
|
+
function isNoise(call, owner, membersOf) {
|
|
1198
1293
|
const expression = call.getExpression();
|
|
1199
1294
|
if (!Node.isPropertyAccessExpression(expression)) return false;
|
|
1200
1295
|
if (NEVER_DATA_METHODS.has(expression.getName())) return true;
|
|
@@ -1207,7 +1302,7 @@ function isNoise(call, owner) {
|
|
|
1207
1302
|
* than a bare identifier.
|
|
1208
1303
|
*/
|
|
1209
1304
|
if (Node.isPropertyAccessExpression(receiver) && Node.isThisExpression(receiver.getExpression()) && FRAMEWORK_SERVICES.has(receiver.getName())) return true;
|
|
1210
|
-
return isNativeReceiver(receiver, owner);
|
|
1305
|
+
return isNativeReceiver(receiver, owner, membersOf);
|
|
1211
1306
|
}
|
|
1212
1307
|
/**
|
|
1213
1308
|
* Iteration over a list, checked BEFORE the resolvers run.
|
|
@@ -1233,14 +1328,40 @@ function isIteration(method, call) {
|
|
|
1233
1328
|
return first !== void 0 && (Node.isArrowFunction(first) || Node.isFunctionExpression(first));
|
|
1234
1329
|
}
|
|
1235
1330
|
/** Does the receiver resolve to a built-in, by its declaration? */
|
|
1236
|
-
function isNativeReceiver(receiver, owner) {
|
|
1331
|
+
function isNativeReceiver(receiver, owner, membersOf) {
|
|
1237
1332
|
if (Node.isArrayLiteralExpression(receiver) || Node.isStringLiteral(receiver)) return true;
|
|
1238
|
-
if (Node.isPropertyAccessExpression(receiver)
|
|
1333
|
+
if (Node.isPropertyAccessExpression(receiver)) {
|
|
1239
1334
|
const inner = receiver.getExpression();
|
|
1240
|
-
if (Node.isThisExpression(inner) && owner) return isNativeProperty(owner, receiver.getName());
|
|
1335
|
+
if (Node.isThisExpression(inner) && owner) return isNativeProperty(owner, receiver.getName()) || isPrimitiveInput(owner, receiver.getName());
|
|
1336
|
+
/**
|
|
1337
|
+
* `this.extras?.painel?.get(id)` where `constructor(protected extras?: Extras)` and
|
|
1338
|
+
* `type Extras = { painel: Map<number, Row> }`: the lookups a transformer receives
|
|
1339
|
+
* arrive one level down, through a named type. The caller resolves the type; here
|
|
1340
|
+
* only its member's type is read.
|
|
1341
|
+
*/
|
|
1342
|
+
if (Node.isPropertyAccessExpression(inner) && Node.isThisExpression(inner.getExpression()) && owner && membersOf) {
|
|
1343
|
+
const typeNode = declaredOn(owner, inner.getName())?.getTypeNode();
|
|
1344
|
+
if (!typeNode) return false;
|
|
1345
|
+
const memberType = membersOf(typeNode).get(receiver.getName());
|
|
1346
|
+
return !!memberType && NATIVE_TYPES.has(memberType.replace(/<.*/, "").trim());
|
|
1347
|
+
}
|
|
1241
1348
|
}
|
|
1242
1349
|
return false;
|
|
1243
1350
|
}
|
|
1351
|
+
/** a class property or a constructor parameter property by name */
|
|
1352
|
+
function declaredOn(owner, name) {
|
|
1353
|
+
return owner.getProperty(name) ?? owner.getConstructors()[0]?.getParameters().find((parameter) => parameter.getName() === name);
|
|
1354
|
+
}
|
|
1355
|
+
/** an ace command's `@flags.*` / `@args.*` property is a string, a number or a boolean: any method on it is the language's */
|
|
1356
|
+
function isPrimitiveInput(owner, name) {
|
|
1357
|
+
const property = owner.getProperty(name);
|
|
1358
|
+
if (!property) return false;
|
|
1359
|
+
return property.getDecorators().some((decorator) => {
|
|
1360
|
+
const callee = decorator.getCallExpression()?.getExpression();
|
|
1361
|
+
const root = callee && Node.isPropertyAccessExpression(callee) ? callee.getExpression().getText() : "";
|
|
1362
|
+
return root === "flags" || root === "args";
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1244
1365
|
function isNativeProperty(owner, name) {
|
|
1245
1366
|
/**
|
|
1246
1367
|
* Both shapes declare a field: a class property, and a constructor parameter
|
|
@@ -3252,6 +3373,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3252
3373
|
const accesses = [];
|
|
3253
3374
|
const followUps = [];
|
|
3254
3375
|
const unresolved = [];
|
|
3376
|
+
const packageCalls = [];
|
|
3255
3377
|
const reads = [];
|
|
3256
3378
|
/** calls a strategy claimed, and where they lead: a nested transformer's keys arrive through its body */
|
|
3257
3379
|
const followedCalls = /* @__PURE__ */ new Map();
|
|
@@ -3371,7 +3493,14 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3371
3493
|
* counted as EOs under a coverage of 99.5%, because a write on an unknown
|
|
3372
3494
|
* local was dropped without a word. It lowers coverage and is named.
|
|
3373
3495
|
*/
|
|
3374
|
-
|
|
3496
|
+
/**
|
|
3497
|
+
* A call INTO a package — `this.authz.can()` on an injected package type,
|
|
3498
|
+
* `row.avatar.getVariant()` on a package-typed column, `catalogue.load()` on a
|
|
3499
|
+
* package factory's product — is outside the boundary: what a package does with
|
|
3500
|
+
* its own tables is technical (§4), what it hands back is a value. Not a gap
|
|
3501
|
+
* the walk failed at; noted (plan 0.11 §A).
|
|
3502
|
+
*/
|
|
3503
|
+
if (isUnreadableWrite(call, symbols, imports, packageImports, body) && !isNoise(call, owner, (typeNode) => membersOfType(typeNode, file, app))) {
|
|
3375
3504
|
unresolved.push({
|
|
3376
3505
|
file: ref.file,
|
|
3377
3506
|
line: call.getStartLineNumber(),
|
|
@@ -3380,12 +3509,24 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3380
3509
|
});
|
|
3381
3510
|
continue;
|
|
3382
3511
|
}
|
|
3383
|
-
if (isWorthReporting(call, symbols, imports) && !isNoise(call, owner))
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3512
|
+
if (isWorthReporting(call, symbols, imports) && !isNoise(call, owner, (typeNode) => membersOfType(typeNode, file, app))) {
|
|
3513
|
+
const specifier = packageTargetOf(call, owner, file, symbols, app, storesByName, sourceFile);
|
|
3514
|
+
if (specifier) {
|
|
3515
|
+
packageCalls.push({
|
|
3516
|
+
file: ref.file,
|
|
3517
|
+
line: call.getStartLineNumber(),
|
|
3518
|
+
expression: call.getExpression().getText().replace(/\s+/g, ""),
|
|
3519
|
+
specifier
|
|
3520
|
+
});
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
unresolved.push({
|
|
3524
|
+
file: ref.file,
|
|
3525
|
+
line: call.getStartLineNumber(),
|
|
3526
|
+
expression: call.getExpression().getText().replace(/\s+/g, ""),
|
|
3527
|
+
reason: "call that no strategy knew how to follow"
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3389
3530
|
}
|
|
3390
3531
|
/**
|
|
3391
3532
|
* Read after the loop: whether a key holds a nested transformer is known only
|
|
@@ -3405,6 +3546,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3405
3546
|
followUps,
|
|
3406
3547
|
unresolved,
|
|
3407
3548
|
validators: validator.fields,
|
|
3549
|
+
packageCalls,
|
|
3408
3550
|
commandFields,
|
|
3409
3551
|
opaqueValidators: validator.opaque,
|
|
3410
3552
|
requestFields: request.fields,
|
|
@@ -3514,6 +3656,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3514
3656
|
const writtenStores = /* @__PURE__ */ new Set();
|
|
3515
3657
|
const inputFields = /* @__PURE__ */ new Set();
|
|
3516
3658
|
const commandFields = /* @__PURE__ */ new Set();
|
|
3659
|
+
const packageCalls = [];
|
|
3517
3660
|
const opaqueInputFields = /* @__PURE__ */ new Set();
|
|
3518
3661
|
const requestFields = /* @__PURE__ */ new Set();
|
|
3519
3662
|
let opaqueRequest = false;
|
|
@@ -3677,12 +3820,36 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3677
3820
|
* Dropping it silently is the worst possible defect: the transaction
|
|
3678
3821
|
* loses a path and nobody knows.
|
|
3679
3822
|
*/
|
|
3680
|
-
if (!isNoiseMember(ref.file, ref.member))
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3823
|
+
if (!isNoiseMember(ref.file, ref.member)) {
|
|
3824
|
+
/**
|
|
3825
|
+
* `catalogue.load()` where `catalogue` is `defineCollection(…)` from a package,
|
|
3826
|
+
* or a class extending a package base without the member: the body is the
|
|
3827
|
+
* package's. Outside the boundary — noted, not a gap (plan 0.11 §A).
|
|
3828
|
+
*/
|
|
3829
|
+
const source = sourceFile(ref.file);
|
|
3830
|
+
const fromPackage = source ? packageMemberOriginOf(source, ref.member, app) : null;
|
|
3831
|
+
if (fromPackage) {
|
|
3832
|
+
packageCalls.push({
|
|
3833
|
+
file: ref.file,
|
|
3834
|
+
line: ref.line ?? 0,
|
|
3835
|
+
expression: `${pathOf(ref.file)}.${ref.member ?? "handle"}`,
|
|
3836
|
+
specifier: fromPackage
|
|
3837
|
+
});
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
/**
|
|
3841
|
+
* `fonte.varrerPorDocumento()` where the file declares an INTERFACE with that
|
|
3842
|
+
* member and no class: there is no body because the implementation is injected
|
|
3843
|
+
* at runtime — a gap, and the right words for it.
|
|
3844
|
+
*/
|
|
3845
|
+
const declaredAsInterface = !!ref.member && !!source && source.getInterfaces().some((i) => i.getMethod(ref.member) || i.getProperty(ref.member));
|
|
3846
|
+
unresolved.push({
|
|
3847
|
+
file: ref.file,
|
|
3848
|
+
line: ref.line ?? 0,
|
|
3849
|
+
expression: `${pathOf(ref.file)}.${ref.member ?? "handle"}`,
|
|
3850
|
+
reason: declaredAsInterface ? "interface method: the implementation is injected at runtime and cannot be followed statically" : "body not found in the resolved file: probably inherited from a package class"
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3686
3853
|
return;
|
|
3687
3854
|
}
|
|
3688
3855
|
let bodyWrites = false;
|
|
@@ -3702,6 +3869,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3702
3869
|
unresolved.push(...facts.unresolved);
|
|
3703
3870
|
for (const field of facts.validators) inputFields.add(field);
|
|
3704
3871
|
for (const field of facts.commandFields) commandFields.add(field);
|
|
3872
|
+
for (const packaged of facts.packageCalls) packageCalls.push(packaged);
|
|
3705
3873
|
for (const field of facts.opaqueValidators) opaqueInputFields.add(field);
|
|
3706
3874
|
for (const field of facts.requestFields) requestFields.add(field);
|
|
3707
3875
|
if (facts.opaqueRequest) opaqueRequest = true;
|
|
@@ -3768,6 +3936,7 @@ function createAnalyzer(app, stores, options = {}) {
|
|
|
3768
3936
|
writtenStores: [...writtenStores].sort(),
|
|
3769
3937
|
inputFields: [...inputFields].sort(),
|
|
3770
3938
|
commandFields: [...commandFields].sort(),
|
|
3939
|
+
packageCalls,
|
|
3771
3940
|
opaqueInputFields: [...opaqueInputFields].sort(),
|
|
3772
3941
|
requestFields: [...requestFields].sort(),
|
|
3773
3942
|
opaqueRequest,
|
|
@@ -4002,7 +4171,91 @@ function dependencyTypeOf(node) {
|
|
|
4002
4171
|
if (Node.isIdentifier(target)) return target.getText();
|
|
4003
4172
|
}
|
|
4004
4173
|
}
|
|
4005
|
-
/** type
|
|
4174
|
+
/** the specifier a type name is imported from in `file`, when the application does not resolve it */
|
|
4175
|
+
function packageSpecifierOfType(typeName, file, app) {
|
|
4176
|
+
const bare = typeName.replace(/<.*/, "").replace(/\|.*$/, "").replace(/\[\]$/, "").trim();
|
|
4177
|
+
for (const declaration of file.getImportDeclarations()) {
|
|
4178
|
+
const specifier = declaration.getModuleSpecifierValue();
|
|
4179
|
+
if (![declaration.getDefaultImport()?.getText(), ...declaration.getNamedImports().map((n) => n.getAliasNode()?.getText() ?? n.getName())].includes(bare)) continue;
|
|
4180
|
+
return app.resolveSpecifier(specifier) ? null : specifier;
|
|
4181
|
+
}
|
|
4182
|
+
return null;
|
|
4183
|
+
}
|
|
4184
|
+
/** the declared type of `this.<property>` on the owner class: a constructor parameter or a property */
|
|
4185
|
+
function declaredTypeOfProperty(owner, property) {
|
|
4186
|
+
if (!owner) return void 0;
|
|
4187
|
+
const parameter = owner.getConstructors()[0]?.getParameters().find((p) => p.getName() === property);
|
|
4188
|
+
if (parameter) return dependencyTypeOf(parameter);
|
|
4189
|
+
const declared = owner.getProperties().find((p) => p.getName() === property);
|
|
4190
|
+
return declared ? dependencyTypeOf(declared) : void 0;
|
|
4191
|
+
}
|
|
4192
|
+
/**
|
|
4193
|
+
* Does this call go INTO a package? Structurally, never by name:
|
|
4194
|
+
*
|
|
4195
|
+
* this.authz.can(…) `authz` injected as a type imported from a package
|
|
4196
|
+
* this.resource.avatar.getVariant a column of the store whose declared type is a package's
|
|
4197
|
+
* attachmentManager.create(…) the receiver itself imported from a package
|
|
4198
|
+
*
|
|
4199
|
+
* The specifier comes back so the note can say which package.
|
|
4200
|
+
*/
|
|
4201
|
+
function packageTargetOf(call, owner, file, symbols, app, stores, sourceFile) {
|
|
4202
|
+
const callee = call.getExpression();
|
|
4203
|
+
if (!Node.isPropertyAccessExpression(callee)) return null;
|
|
4204
|
+
const receiver = unwrapAwait(callee.getExpression());
|
|
4205
|
+
if (Node.isPropertyAccessExpression(receiver) && Node.isThisExpression(receiver.getExpression())) {
|
|
4206
|
+
const declared = declaredTypeOfProperty(owner, receiver.getName());
|
|
4207
|
+
if (declared) return packageSpecifierOfType(declared, file, app);
|
|
4208
|
+
}
|
|
4209
|
+
if (Node.isPropertyAccessExpression(receiver)) {
|
|
4210
|
+
const holder = receiver.getExpression();
|
|
4211
|
+
const holderText = holder.getText();
|
|
4212
|
+
const store = symbols.get(rootSymbolOf(holder) ?? "") ?? (holderText === "this.resource" && owner ? transformerResourceOf(owner) : null);
|
|
4213
|
+
const declared = store ? stores.get(store) : void 0;
|
|
4214
|
+
const modelFile = declared ? sourceFile(declared.provenance.file) : null;
|
|
4215
|
+
if (declared && modelFile) {
|
|
4216
|
+
const type = modelFile.getClasses().flatMap((c) => c.getProperties()).find((p) => p.getName() === receiver.getName())?.getTypeNode()?.getText() ?? declared.attributes.find((a) => a.name === receiver.getName())?.type;
|
|
4217
|
+
if (type) return packageSpecifierOfType(type, modelFile, app);
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
if (Node.isIdentifier(receiver)) return packageSpecifierOfType(receiver.getText(), file, app);
|
|
4221
|
+
return null;
|
|
4222
|
+
}
|
|
4223
|
+
/**
|
|
4224
|
+
* A member the resolved file does not declare, because it is a package's: the
|
|
4225
|
+
* exported symbol is a const built by a package factory, or a class extending a
|
|
4226
|
+
* package base. Returns the package's specifier.
|
|
4227
|
+
*/
|
|
4228
|
+
function packageMemberOriginOf(file, member, app) {
|
|
4229
|
+
const rootOf = (node) => {
|
|
4230
|
+
let current = node;
|
|
4231
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
4232
|
+
if (Node.isCallExpression(current) || Node.isPropertyAccessExpression(current)) {
|
|
4233
|
+
current = current.getExpression();
|
|
4234
|
+
continue;
|
|
4235
|
+
}
|
|
4236
|
+
return Node.isIdentifier(current) ? current.getText() : null;
|
|
4237
|
+
}
|
|
4238
|
+
return null;
|
|
4239
|
+
};
|
|
4240
|
+
const defaultExported = file.getExportAssignments().map((e) => e.getExpression().getText());
|
|
4241
|
+
const declarations = file.getVariableDeclarations();
|
|
4242
|
+
const exported = declarations.filter((d) => d.getVariableStatement()?.isExported() || defaultExported.includes(d.getName()));
|
|
4243
|
+
for (const declaration of exported.length > 0 ? exported : declarations) {
|
|
4244
|
+
const initializer = declaration.getInitializer();
|
|
4245
|
+
if (!initializer || !Node.isCallExpression(initializer)) continue;
|
|
4246
|
+
const root = rootOf(initializer);
|
|
4247
|
+
const specifier = root ? packageSpecifierOfType(root, file, app) : null;
|
|
4248
|
+
if (specifier) return specifier;
|
|
4249
|
+
}
|
|
4250
|
+
for (const cls of file.getClasses()) {
|
|
4251
|
+
if (member && cls.getMethod(member)) return null;
|
|
4252
|
+
const parent = cls.getExtends()?.getExpression();
|
|
4253
|
+
const root = parent ? rootOf(parent) : null;
|
|
4254
|
+
const specifier = root ? packageSpecifierOfType(root, file, app) : null;
|
|
4255
|
+
if (specifier) return specifier;
|
|
4256
|
+
}
|
|
4257
|
+
return null;
|
|
4258
|
+
}
|
|
4006
4259
|
function resolveTypeToFile(typeName, file, app) {
|
|
4007
4260
|
const bare = typeName.replace(/<.*/, "").trim();
|
|
4008
4261
|
for (const declaration of file.getImportDeclarations()) {
|
|
@@ -5763,9 +6016,32 @@ var CoverageTooLowError = class extends Error {
|
|
|
5763
6016
|
this.name = "CoverageTooLowError";
|
|
5764
6017
|
}
|
|
5765
6018
|
};
|
|
6019
|
+
/**
|
|
6020
|
+
* One note per call site into a package, naming the transactions that reach it —
|
|
6021
|
+
* outside the boundary (§4): what a package does with its own tables is technical,
|
|
6022
|
+
* what it hands back is a value. Never in coverage (plan 0.11 §A).
|
|
6023
|
+
*/
|
|
6024
|
+
function packageCallNotes(entryPoints, behaviors, emit) {
|
|
6025
|
+
const sites = /* @__PURE__ */ new Map();
|
|
6026
|
+
for (const entry of entryPoints) {
|
|
6027
|
+
const behavior = behaviors.get(entry.id);
|
|
6028
|
+
for (const call of behavior?.packageCalls ?? []) {
|
|
6029
|
+
const key = `${call.file}:${call.line}:${call.expression}`;
|
|
6030
|
+
const site = sites.get(key) ?? {
|
|
6031
|
+
specifier: call.specifier,
|
|
6032
|
+
expression: call.expression,
|
|
6033
|
+
where: call.line > 0 ? `${emit(call.file)}:${call.line}` : emit(call.file),
|
|
6034
|
+
reached: []
|
|
6035
|
+
};
|
|
6036
|
+
if (!site.reached.includes(entry.identity)) site.reached.push(entry.identity);
|
|
6037
|
+
sites.set(key, site);
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
return [...sites.values()].sort((a, b) => a.where.localeCompare(b.where)).map((site) => `${site.reached.join(", ")}: call into ${site.specifier} (${site.expression}) — outside the boundary; what a package does with its own tables is technical (§4), what it hands back is a value [${site.where}]`);
|
|
6041
|
+
}
|
|
5766
6042
|
async function analyze(root, options = {}) {
|
|
5767
6043
|
const app = await discoverApp(root);
|
|
5768
|
-
const { stores, unresolved: storeProblems } = await collectDataStores(app);
|
|
6044
|
+
const { stores, unresolved: storeProblems, notes: storeNotes } = await collectDataStores(app);
|
|
5769
6045
|
const routes = await collectEntryPoints(app);
|
|
5770
6046
|
const routeProblems = routes.unresolved;
|
|
5771
6047
|
const entryPoints = [...routes.entryPoints, ...collectCommands(app)];
|
|
@@ -5895,6 +6171,10 @@ async function analyze(root, options = {}) {
|
|
|
5895
6171
|
transformedStores: behavior.transformedStores,
|
|
5896
6172
|
delivered: behavior.delivered,
|
|
5897
6173
|
pageReads: behavior.pageReads,
|
|
6174
|
+
packageCalls: behavior.packageCalls.map((c) => ({
|
|
6175
|
+
...c,
|
|
6176
|
+
file: emit(c.file)
|
|
6177
|
+
})),
|
|
5898
6178
|
unreadablePages: behavior.unreadablePages,
|
|
5899
6179
|
outputReads: behavior.outputReads,
|
|
5900
6180
|
trace: behavior.trace.map((step) => ({
|
|
@@ -5915,7 +6195,8 @@ async function analyze(root, options = {}) {
|
|
|
5915
6195
|
unresolved: unresolvedSites.map((site) => ({
|
|
5916
6196
|
...site,
|
|
5917
6197
|
file: emit(site.file)
|
|
5918
|
-
}))
|
|
6198
|
+
})),
|
|
6199
|
+
notes: [...storeNotes, ...packageCallNotes(entryPoints, behaviors, emit)]
|
|
5919
6200
|
};
|
|
5920
6201
|
const minimum = options.minCoverage ?? 0;
|
|
5921
6202
|
if (inventory.coverage.ratio < minimum) throw new CoverageTooLowError(inventory.coverage.ratio, minimum);
|
|
@@ -1368,4 +1368,4 @@ function ignoreCalls(options) {
|
|
|
1368
1368
|
};
|
|
1369
1369
|
}
|
|
1370
1370
|
//#endregion
|
|
1371
|
-
export {
|
|
1371
|
+
export { isSeeder as _, chainShapeOf as a, toPosix as b, transformerResourceOf as c, EXECUTION_METHODS as d, collectEventBindings as f, isApplicationCode as g, rootSymbolOf as h, resolveCall as i, unwrap as l, hooksFiredBy as m, ignoreCalls as n, mappedLiteralOf as o, detectAccess as p, isTechnicalWrite as r, outputFieldsIn as s, BUILTIN_CALL_RESOLVERS as t, DISPATCH_METHODS as u, relativeTo as v, samePath as y };
|
|
@@ -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 {
|
|
3
|
-
import { n as analyze } from "./pipeline-
|
|
2
|
+
import { b as toPosix } from "./resolvers-Bz1F-Tgf.js";
|
|
3
|
+
import { n as analyze } from "./pipeline-CdDJRsLM.js";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
@@ -312,7 +312,8 @@ async function runInventory(options) {
|
|
|
312
312
|
`data stores: ${inventory.dataStores.length}`,
|
|
313
313
|
`entry points: ${coverage.entryPointsTotal}`,
|
|
314
314
|
`coverage: ${(coverage.ratio * 100).toFixed(1)}% (${coverage.unresolvedCalls} unresolved calls)`,
|
|
315
|
-
...inventory.unresolved.map((site) => ` ${describeSite(site)}`)
|
|
315
|
+
...inventory.unresolved.map((site) => ` ${describeSite(site)}`),
|
|
316
|
+
...inventory.notes.length > 0 ? ["notes (declarations, not gaps):", ...inventory.notes.map((note) => ` ${note}`)] : []
|
|
316
317
|
].join("\n")
|
|
317
318
|
};
|
|
318
319
|
}
|
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-CdDJRsLM.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-CzzlX7h-.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
1
2
|
import type { ClassDeclaration, SourceFile } from 'ts-morph';
|
|
2
3
|
import type { AppContext } from '../app_context.js';
|
|
3
4
|
import type { CollectedDataStore } from '../sources/data_stores.js';
|
|
@@ -104,6 +105,12 @@ export type Behavior = {
|
|
|
104
105
|
*/
|
|
105
106
|
pageReads: Record<string, string[]>;
|
|
106
107
|
unreadablePages: Record<string, string>;
|
|
108
|
+
/**
|
|
109
|
+
* Calls into a PACKAGE the walk met on the way — an injected package type, a
|
|
110
|
+
* package-typed column, a package factory's product, a member inherited from a
|
|
111
|
+
* package base. Outside the boundary (§4): noted, never a gap (plan 0.11 §A).
|
|
112
|
+
*/
|
|
113
|
+
packageCalls: PackageCall[];
|
|
107
114
|
trace: TraceStep[];
|
|
108
115
|
/** bodies reached, for `fp:diff` */
|
|
109
116
|
scope: ScopeEntry[];
|
|
@@ -157,6 +164,15 @@ export declare function analyzeHandler(app: AppContext, stores: CollectedDataSto
|
|
|
157
164
|
* as any import. No type checker is needed.
|
|
158
165
|
*/
|
|
159
166
|
export declare function injectedFor(owner: ClassDeclaration | undefined, file: SourceFile, app: AppContext): Map<string, string>;
|
|
167
|
+
/** type identifier -> application file where it is declared */
|
|
168
|
+
/** a call whose target resolves to a package: where, what, which package */
|
|
169
|
+
export type PackageCall = {
|
|
170
|
+
file: string;
|
|
171
|
+
line: number;
|
|
172
|
+
expression: string;
|
|
173
|
+
specifier: string;
|
|
174
|
+
};
|
|
175
|
+
export declare function membersOfType(typeNode: Node, file: SourceFile, app: AppContext): Map<string, string>;
|
|
160
176
|
/**
|
|
161
177
|
* Both maps a file's imports produce: where a local name resolves, and what it
|
|
162
178
|
* was called where it was exported.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
1
2
|
import type { CallExpression, ClassDeclaration } from 'ts-morph';
|
|
2
3
|
/** Is this call one that cannot reach a data store? */
|
|
3
|
-
|
|
4
|
+
/** resolves a type annotation to its members (`name -> type text`), when the caller can */
|
|
5
|
+
export type MembersOf = (typeNode: Node) => Map<string, string>;
|
|
6
|
+
export declare function isNoise(call: CallExpression, owner?: ClassDeclaration, membersOf?: MembersOf): boolean;
|
|
4
7
|
/**
|
|
5
8
|
* Iteration over a list, checked BEFORE the resolvers run.
|
|
6
9
|
*
|
|
@@ -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-Bz1F-Tgf.js";
|
|
2
2
|
export { BUILTIN_CALL_RESOLVERS, ignoreCalls, isTechnicalWrite, resolveCall };
|
|
@@ -27,5 +27,12 @@ export type DataStoreCollection = {
|
|
|
27
27
|
stores: CollectedDataStore[];
|
|
28
28
|
/** chains that left the application, required by AFP §6.5.3 */
|
|
29
29
|
unresolved: UnresolvedCall[];
|
|
30
|
+
/**
|
|
31
|
+
* Facts about the declarations that are not gaps: a base class or a mixin that
|
|
32
|
+
* comes from a package. What it adds is technical (a hashed password, audit
|
|
33
|
+
* stamps, a soft-delete mark) and never a user-recognisable attribute (§7.2); the
|
|
34
|
+
* model's own columns and the schema decide. Listed, never counted in coverage.
|
|
35
|
+
*/
|
|
36
|
+
notes: string[];
|
|
30
37
|
};
|
|
31
38
|
export declare function collectDataStores(app: AppContext): Promise<DataStoreCollection>;
|
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-CdDJRsLM.js";
|
|
2
2
|
export { CoverageTooLowError, analyze };
|
package/build/src/types.d.ts
CHANGED
|
@@ -148,6 +148,13 @@ export type HandlerBehavior = {
|
|
|
148
148
|
pageReads?: Record<string, string[]>;
|
|
149
149
|
/** stores a page could not be read for, and why: they leave whole */
|
|
150
150
|
unreadablePages?: Record<string, string>;
|
|
151
|
+
/** calls into a package met on the way: outside the boundary, noted, never a gap (plan 0.11 §A) */
|
|
152
|
+
packageCalls?: {
|
|
153
|
+
file: string;
|
|
154
|
+
line: number;
|
|
155
|
+
expression: string;
|
|
156
|
+
specifier: string;
|
|
157
|
+
}[];
|
|
151
158
|
/**
|
|
152
159
|
* How each store was read: rows whole, `.select()` columns, or one aggregate
|
|
153
160
|
* scalar; by its own chain (`direct`) or preloaded through another store (`via`).
|
|
@@ -222,6 +229,11 @@ export type Inventory = {
|
|
|
222
229
|
* on. One entry per site — a body five routes reach is one gap, not five.
|
|
223
230
|
*/
|
|
224
231
|
unresolved: UnresolvedSite[];
|
|
232
|
+
/**
|
|
233
|
+
* Facts about the declarations that are not gaps — a base class or a mixin from a
|
|
234
|
+
* package, whose additions are technical. Listed for the reader; never in coverage.
|
|
235
|
+
*/
|
|
236
|
+
notes: string[];
|
|
225
237
|
};
|
|
226
238
|
export type FunctionType = 'ILF' | 'EIF' | 'EI' | 'EO' | 'EQ';
|
|
227
239
|
export type Complexity = 'low' | 'average' | 'high';
|