@alveolus/arch 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +1 -1
- package/dist/{docs-DcFgskuN.mjs → docs-B1Tzkjrn.mjs} +338 -14
- package/dist/docs-B1Tzkjrn.mjs.map +1 -0
- package/dist/index.d.mts +39 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/docs/guide/project-layout.md +3 -1
- package/docs/rules/index.md +12 -3
- package/docs/rules/layers/no-outward-import.md +7 -3
- package/docs/rules/strategic/no-cross-context-import.md +35 -2
- package/docs/rules/strategic/no-fat-shared-kernel.md +1 -0
- package/docs/rules/strategic/no-shared-state.md +100 -0
- package/docs/rules/strategic/no-unmapped-context.md +37 -4
- package/package.json +1 -1
- package/dist/docs-DcFgskuN.mjs.map +0 -1
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as Cli, t as Docs } from "./docs-
|
|
2
|
+
import { r as Cli, t as Docs } from "./docs-B1Tzkjrn.mjs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
//#region src/bin.ts
|
|
5
5
|
const docs = new Docs(fileURLToPath(new URL("../docs/", import.meta.url)));
|
|
@@ -166,22 +166,26 @@ var Member = class {
|
|
|
166
166
|
kind;
|
|
167
167
|
visibility;
|
|
168
168
|
isStatic;
|
|
169
|
+
isReadonly;
|
|
169
170
|
isParameterProperty;
|
|
170
171
|
isCallable;
|
|
171
172
|
valueTypes;
|
|
172
173
|
parameterTypes;
|
|
173
174
|
returns;
|
|
175
|
+
holdsCollection;
|
|
174
176
|
constructor(props) {
|
|
175
177
|
this.name = props.name;
|
|
176
178
|
this.line = props.line;
|
|
177
179
|
this.kind = props.kind;
|
|
178
180
|
this.visibility = props.visibility;
|
|
179
181
|
this.isStatic = props.isStatic;
|
|
182
|
+
this.isReadonly = props.isReadonly;
|
|
180
183
|
this.isParameterProperty = props.isParameterProperty;
|
|
181
184
|
this.isCallable = props.isCallable;
|
|
182
185
|
this.valueTypes = props.valueTypes;
|
|
183
186
|
this.parameterTypes = props.parameterTypes;
|
|
184
187
|
this.returns = props.returns;
|
|
188
|
+
this.holdsCollection = props.holdsCollection;
|
|
185
189
|
}
|
|
186
190
|
get isPublicInstance() {
|
|
187
191
|
return this.visibility === "public" && !this.isStatic;
|
|
@@ -227,6 +231,18 @@ var GlobalUse = class {
|
|
|
227
231
|
}
|
|
228
232
|
};
|
|
229
233
|
//#endregion
|
|
234
|
+
//#region src/model/dependencies/wiring.ts
|
|
235
|
+
var Wiring = class {
|
|
236
|
+
line;
|
|
237
|
+
receiver;
|
|
238
|
+
links;
|
|
239
|
+
constructor(line, receiver, links) {
|
|
240
|
+
this.line = line;
|
|
241
|
+
this.receiver = receiver;
|
|
242
|
+
this.links = links;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
//#endregion
|
|
230
246
|
//#region src/model/project.ts
|
|
231
247
|
var Project$1 = class {
|
|
232
248
|
directory;
|
|
@@ -257,6 +273,7 @@ var SourceFile = class {
|
|
|
257
273
|
statements;
|
|
258
274
|
throws;
|
|
259
275
|
globals;
|
|
276
|
+
wirings;
|
|
260
277
|
disables;
|
|
261
278
|
lines;
|
|
262
279
|
constructor(props) {
|
|
@@ -266,6 +283,7 @@ var SourceFile = class {
|
|
|
266
283
|
this.statements = props.statements;
|
|
267
284
|
this.throws = props.throws;
|
|
268
285
|
this.globals = props.globals;
|
|
286
|
+
this.wirings = props.wirings;
|
|
269
287
|
this.disables = props.disables;
|
|
270
288
|
this.lines = props.text.split(/\r?\n/);
|
|
271
289
|
}
|
|
@@ -586,6 +604,60 @@ var CoreApi = class {
|
|
|
586
604
|
}
|
|
587
605
|
};
|
|
588
606
|
//#endregion
|
|
607
|
+
//#region src/architecture/crossing.ts
|
|
608
|
+
var Crossings = class {
|
|
609
|
+
architecture;
|
|
610
|
+
constructor(architecture) {
|
|
611
|
+
this.architecture = architecture;
|
|
612
|
+
}
|
|
613
|
+
in(file) {
|
|
614
|
+
const crossings = /* @__PURE__ */ new Map();
|
|
615
|
+
for (const wiring of file.wirings) {
|
|
616
|
+
const crossing = this.of(wiring);
|
|
617
|
+
if (crossing !== void 0) crossings.set(`${crossing.line}:${crossing.expression}:${crossing.from}:${crossing.to}`, crossing);
|
|
618
|
+
}
|
|
619
|
+
return [...crossings.values()];
|
|
620
|
+
}
|
|
621
|
+
of(wiring) {
|
|
622
|
+
const receiver = this.architecture.locationOfPath(wiring.receiver);
|
|
623
|
+
if (!receiver.isInBoundedContext || receiver.context === void 0) return;
|
|
624
|
+
for (const link of wiring.links) {
|
|
625
|
+
const source = this.sourceOf(link, receiver);
|
|
626
|
+
if (source !== void 0) return {
|
|
627
|
+
expression: link.text,
|
|
628
|
+
from: receiver.context,
|
|
629
|
+
isOpenHostService: this.isOpenHostService(link, receiver),
|
|
630
|
+
line: wiring.line,
|
|
631
|
+
to: source
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
sourceOf(link, receiver) {
|
|
636
|
+
if (link.declaredIn !== void 0) {
|
|
637
|
+
const declared = this.architecture.locationOfPath(link.declaredIn);
|
|
638
|
+
if (declared.isOtherBoundedContextThan(receiver)) return declared.context;
|
|
639
|
+
}
|
|
640
|
+
return this.foreignTypesOf(link, receiver)[0]?.context;
|
|
641
|
+
}
|
|
642
|
+
isOpenHostService(link, receiver) {
|
|
643
|
+
const foreign = link.types.filter((type) => this.isForeignModel(type.declaredIn, receiver));
|
|
644
|
+
return foreign.length > 0 && foreign.every((type) => {
|
|
645
|
+
const declaration = this.architecture.classOf(type);
|
|
646
|
+
return declaration !== void 0 && this.architecture.implementsMarker(declaration, "OpenHostService");
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
foreignTypesOf(link, receiver) {
|
|
650
|
+
const locations = [];
|
|
651
|
+
for (const type of link.types) if (this.isForeignModel(type.declaredIn, receiver)) locations.push(this.architecture.locationOfPath(type.declaredIn ?? ""));
|
|
652
|
+
return locations;
|
|
653
|
+
}
|
|
654
|
+
isForeignModel(path, receiver) {
|
|
655
|
+
if (path === void 0) return false;
|
|
656
|
+
const location = this.architecture.locationOfPath(path);
|
|
657
|
+
return location.isOtherBoundedContextThan(receiver) && !location.isCompositionRoot;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
//#endregion
|
|
589
661
|
//#region src/architecture/layers.ts
|
|
590
662
|
var Layers = class {
|
|
591
663
|
isLayer(name) {
|
|
@@ -769,6 +841,7 @@ var Architecture = class {
|
|
|
769
841
|
shape;
|
|
770
842
|
layout;
|
|
771
843
|
locations = /* @__PURE__ */ new Map();
|
|
844
|
+
crossings = new Crossings(this);
|
|
772
845
|
constructor(project, settings) {
|
|
773
846
|
this.project = project;
|
|
774
847
|
this.settings = settings;
|
|
@@ -790,6 +863,12 @@ var Architecture = class {
|
|
|
790
863
|
locationOf(file) {
|
|
791
864
|
return this.locate(file.path);
|
|
792
865
|
}
|
|
866
|
+
locationOfPath(path) {
|
|
867
|
+
return this.locate(path);
|
|
868
|
+
}
|
|
869
|
+
crossingsIn(file) {
|
|
870
|
+
return this.crossings.in(file);
|
|
871
|
+
}
|
|
793
872
|
locationOfTarget(target) {
|
|
794
873
|
if (target.visibility !== "analysed") return new Location({
|
|
795
874
|
area: "outside",
|
|
@@ -882,10 +961,12 @@ var ContextMap = class {
|
|
|
882
961
|
//#region src/rules/framework/wording.ts
|
|
883
962
|
var Wording = class {
|
|
884
963
|
target(target, architecture) {
|
|
964
|
+
if (target.visibility === "dynamic") return `code loaded at runtime with ${target.path}`;
|
|
885
965
|
return `${architecture.relativePath(target.path)} (${this.location(architecture.locationOfTarget(target))})`;
|
|
886
966
|
}
|
|
887
967
|
location(location) {
|
|
888
968
|
if (location.unseen === "ignored") return "ignored by the analysis";
|
|
969
|
+
if (location.unseen === "dynamic") return "loaded at runtime";
|
|
889
970
|
if (location.unseen === "unresolved") return "not resolved by the analysis";
|
|
890
971
|
if (location.isOutside) return "outside the declared bounded contexts and shared kernel";
|
|
891
972
|
if (location.isAtRoot) return "at the root";
|
|
@@ -1089,6 +1170,7 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1089
1170
|
description: "A dependency pointing away from the domain, a file outside the layers or in the wrong folder of its layer.",
|
|
1090
1171
|
id: "layers/no-outward-import",
|
|
1091
1172
|
messages: {
|
|
1173
|
+
compositionRootImport: "The composition root imports {target}: it wires its own context and the shared kernel only.",
|
|
1092
1174
|
compositionRoots: "{context} has {count} composition roots ({names}): keep one, and move the rest into the layers.",
|
|
1093
1175
|
directlyInLayer: "The file sits directly in {layer}/: put it in the folder of its kind, such as {example}.",
|
|
1094
1176
|
drivenTooDeep: "The file is nested too deep: driven/ holds driven/<technology>/<folder>/, such as driven/pg/adapters/.",
|
|
@@ -1183,11 +1265,15 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1183
1265
|
return findings;
|
|
1184
1266
|
}
|
|
1185
1267
|
layerProblem(from, to, architecture) {
|
|
1186
|
-
if (from.isAtRoot) return to.isInBoundedContext && !to.isCompositionRoot ? {
|
|
1268
|
+
if (from.isAtRoot) return to.isOutside || to.isInBoundedContext && !to.isCompositionRoot ? {
|
|
1187
1269
|
data: {},
|
|
1188
1270
|
messageId: "rootImport"
|
|
1189
1271
|
} : void 0;
|
|
1190
|
-
if (from.isCompositionRoot
|
|
1272
|
+
if (from.isCompositionRoot) return to.isOutside ? {
|
|
1273
|
+
data: {},
|
|
1274
|
+
messageId: "compositionRootImport"
|
|
1275
|
+
} : void 0;
|
|
1276
|
+
if (to.isOtherBoundedContextThan(from)) return;
|
|
1191
1277
|
if (to.isCompositionRoot) return {
|
|
1192
1278
|
data: {},
|
|
1193
1279
|
messageId: "wiring"
|
|
@@ -1249,16 +1335,28 @@ var NoPortlessAdapterRule = class extends ClassRule {
|
|
|
1249
1335
|
var NoCrossContextImportRule = class extends ImportRule {
|
|
1250
1336
|
meta = {
|
|
1251
1337
|
contexts: "every",
|
|
1252
|
-
description: "An import from another bounded context that is not its open host service, a composition root that re-exports.",
|
|
1338
|
+
description: "An import from another bounded context that is not its open host service, a file the analysis does not see, a composition root that re-exports, a value of another context given in the wiring that is not its open host service.",
|
|
1253
1339
|
id: "strategic/no-cross-context-import",
|
|
1254
1340
|
messages: {
|
|
1341
|
+
dynamicLoad: "Loads code at runtime with {loader}: the analysis cannot tell which bounded context it reaches; use a static import.",
|
|
1255
1342
|
notOpenHostService: "Imports {target}: only an OpenHostService of another bounded context may be imported.",
|
|
1256
1343
|
outsideAntiCorruptionLayer: "Uses the open host service of {context} outside an AntiCorruptionLayer: a core context translates what it consumes in an anti-corruption layer.",
|
|
1257
1344
|
publishedLanguage: "Imports the published language of {context}: redeclare the fields you read in your own published-language/.",
|
|
1258
1345
|
reexport: "The composition root re-exports {names}: it exports its own module only, so that no other context reaches through it.",
|
|
1259
|
-
sharedKernel: "The shared kernel imports no bounded context, but imports {target}."
|
|
1346
|
+
sharedKernel: "The shared kernel imports no bounded context, but imports {target}.",
|
|
1347
|
+
unseen: "Imports {target}: the analysis cannot tell which bounded context it reaches; move the file into a bounded context or the shared kernel.",
|
|
1348
|
+
wiredModel: "Gives {expression}, from {to}, to {from}: only an OpenHostService of another bounded context may cross, in an import or in the wiring."
|
|
1260
1349
|
}
|
|
1261
1350
|
};
|
|
1351
|
+
check(architecture) {
|
|
1352
|
+
const findings = super.check(architecture);
|
|
1353
|
+
for (const file of this.filesOf(architecture)) for (const crossing of architecture.crossingsIn(file)) if (!crossing.isOpenHostService) findings.push(this.finding(file, crossing.line, crossing.expression, "wiredModel", {
|
|
1354
|
+
expression: crossing.expression,
|
|
1355
|
+
from: crossing.from,
|
|
1356
|
+
to: crossing.to
|
|
1357
|
+
}));
|
|
1358
|
+
return findings;
|
|
1359
|
+
}
|
|
1262
1360
|
appliesTo(file, architecture) {
|
|
1263
1361
|
const location = architecture.locationOf(file);
|
|
1264
1362
|
return location.isInBoundedContext || location.isInSharedKernel;
|
|
@@ -1269,6 +1367,8 @@ var NoCrossContextImportRule = class extends ImportRule {
|
|
|
1269
1367
|
const target = dependency.target;
|
|
1270
1368
|
if (target.kind !== "file") return;
|
|
1271
1369
|
const to = architecture.locationOfTarget(target);
|
|
1370
|
+
if (target.visibility === "dynamic") return this.finding(file, dependency.line, dependency.label, "dynamicLoad", { loader: target.path });
|
|
1371
|
+
if (to.isOutside) return this.finding(file, dependency.line, dependency.label, "unseen", { target: this.wording.target(target, architecture) });
|
|
1272
1372
|
if (!to.isOtherBoundedContextThan(from)) return;
|
|
1273
1373
|
if (from.isInSharedKernel) return this.finding(file, dependency.line, dependency.label, "sharedKernel", { target: this.wording.target(target, architecture) });
|
|
1274
1374
|
if (from.isCompositionRoot && to.isCompositionRoot) return;
|
|
@@ -1359,17 +1459,60 @@ var NoLeakyHostServiceRule = class extends ClassRule {
|
|
|
1359
1459
|
}
|
|
1360
1460
|
};
|
|
1361
1461
|
//#endregion
|
|
1462
|
+
//#region src/rules/strategic/no-shared-state.rule.ts
|
|
1463
|
+
var NoSharedStateRule = class extends ClassRule {
|
|
1464
|
+
meta = {
|
|
1465
|
+
contexts: "every",
|
|
1466
|
+
description: "A static field of the shared kernel that holds state: one without readonly, or one holding a collection.",
|
|
1467
|
+
id: "strategic/no-shared-state",
|
|
1468
|
+
messages: {
|
|
1469
|
+
collection: "{class}.{field} holds a collection in a static field: every context reaches the same one, a channel the context map does not show. The shared kernel shares a model, not state: integrate through an open host service.",
|
|
1470
|
+
mutable: "{class}.{field} is a static field without readonly: every context reaches the same one, a channel the context map does not show. The shared kernel shares a model, not state: integrate through an open host service."
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
findingsFor(codeClass, file, architecture) {
|
|
1474
|
+
if (!architecture.locationOf(file).isInSharedKernel) return [];
|
|
1475
|
+
const findings = [];
|
|
1476
|
+
for (const member of codeClass.members) {
|
|
1477
|
+
const messageId = this.stateOf(member);
|
|
1478
|
+
if (messageId !== void 0) findings.push(this.finding(file, member.line, member.name, messageId, {
|
|
1479
|
+
class: codeClass.name,
|
|
1480
|
+
field: member.name
|
|
1481
|
+
}));
|
|
1482
|
+
}
|
|
1483
|
+
return findings;
|
|
1484
|
+
}
|
|
1485
|
+
stateOf(member) {
|
|
1486
|
+
if (!member.isStatic || member.kind !== "field") return;
|
|
1487
|
+
if (!member.isReadonly) return "mutable";
|
|
1488
|
+
return member.holdsCollection ? "collection" : void 0;
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
//#endregion
|
|
1362
1492
|
//#region src/rules/strategic/no-unmapped-context.rule.ts
|
|
1363
1493
|
var NoUnmappedContextRule = class extends Rule {
|
|
1364
1494
|
meta = {
|
|
1365
1495
|
contexts: "every",
|
|
1366
|
-
description: "A bounded context consuming one the context map does not allow.",
|
|
1496
|
+
description: "A bounded context consuming one the context map does not allow, through an import or through the wiring.",
|
|
1367
1497
|
id: "strategic/no-unmapped-context",
|
|
1368
|
-
messages: {
|
|
1498
|
+
messages: {
|
|
1499
|
+
unmapped: "{from} consumes {to}, which the context map does not allow: reverse the dependency, or if {from} really is downstream of {to}, add {to} to contextMap.{from}.consumes.",
|
|
1500
|
+
unmappedWiring: "{from} receives {expression} from {to} here, which the context map does not allow: reverse the dependency, or if {from} really is downstream of {to}, add {to} to contextMap.{from}.consumes."
|
|
1501
|
+
}
|
|
1369
1502
|
};
|
|
1370
1503
|
check(architecture) {
|
|
1371
1504
|
const findings = [];
|
|
1372
1505
|
for (const consumption of this.consumptionsIn(architecture)) if (!architecture.contextMap.allows(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption));
|
|
1506
|
+
findings.push(...this.unmappedWirings(architecture));
|
|
1507
|
+
return findings;
|
|
1508
|
+
}
|
|
1509
|
+
unmappedWirings(architecture) {
|
|
1510
|
+
const findings = [];
|
|
1511
|
+
for (const file of this.filesOf(architecture)) for (const crossing of architecture.crossingsIn(file)) if (!architecture.contextMap.allows(crossing.from, crossing.to)) findings.push(this.finding(file, crossing.line, crossing.expression, "unmappedWiring", {
|
|
1512
|
+
expression: crossing.expression,
|
|
1513
|
+
from: crossing.from,
|
|
1514
|
+
to: crossing.to
|
|
1515
|
+
}));
|
|
1373
1516
|
return findings;
|
|
1374
1517
|
}
|
|
1375
1518
|
consumptionsIn(architecture) {
|
|
@@ -1830,6 +1973,7 @@ const ruleIds = [
|
|
|
1830
1973
|
"strategic/no-leaky-host-service",
|
|
1831
1974
|
"strategic/no-unmapped-context",
|
|
1832
1975
|
"strategic/no-fat-shared-kernel",
|
|
1976
|
+
"strategic/no-shared-state",
|
|
1833
1977
|
"layers/no-impure-domain",
|
|
1834
1978
|
"layers/no-outward-import",
|
|
1835
1979
|
"layers/no-portless-adapter",
|
|
@@ -1850,6 +1994,7 @@ var RuleRegistry = class {
|
|
|
1850
1994
|
new NoLeakyHostServiceRule(),
|
|
1851
1995
|
new NoUnmappedContextRule(),
|
|
1852
1996
|
new NoFatSharedKernelRule(),
|
|
1997
|
+
new NoSharedStateRule(),
|
|
1853
1998
|
new NoImpureDomainRule(),
|
|
1854
1999
|
new NoOutwardImportRule(),
|
|
1855
2000
|
new NoPortlessAdapterRule(),
|
|
@@ -2088,6 +2233,7 @@ var Config = class {
|
|
|
2088
2233
|
contextMap;
|
|
2089
2234
|
ignored;
|
|
2090
2235
|
rules;
|
|
2236
|
+
layout;
|
|
2091
2237
|
constructor(config, projectDir) {
|
|
2092
2238
|
this.projectDir = resolve(projectDir);
|
|
2093
2239
|
this.rootDir = resolve(projectDir, config.root);
|
|
@@ -2105,6 +2251,7 @@ var Config = class {
|
|
|
2105
2251
|
isSharedKernel: true,
|
|
2106
2252
|
name: "shared kernel"
|
|
2107
2253
|
}];
|
|
2254
|
+
this.layout = new Layout(this);
|
|
2108
2255
|
}
|
|
2109
2256
|
classifiedContexts(subdomains, boundedContexts) {
|
|
2110
2257
|
const types = [
|
|
@@ -2149,6 +2296,10 @@ var Config = class {
|
|
|
2149
2296
|
const projectPath = relative(this.projectDir, path).split(sep).join("/");
|
|
2150
2297
|
return this.ignored.some((glob) => matchesGlob(projectPath, glob));
|
|
2151
2298
|
}
|
|
2299
|
+
readsWiring(path) {
|
|
2300
|
+
const location = this.layout.locate(path);
|
|
2301
|
+
return location.isAtRoot || location.isCompositionRoot;
|
|
2302
|
+
}
|
|
2152
2303
|
severityOf(rule) {
|
|
2153
2304
|
return this.rules?.[rule] ?? "error";
|
|
2154
2305
|
}
|
|
@@ -2253,6 +2404,7 @@ var ClassReader = class {
|
|
|
2253
2404
|
const type = member.getType();
|
|
2254
2405
|
const [signature] = type.getCallSignatures();
|
|
2255
2406
|
return [this.member(member, "field", {
|
|
2407
|
+
holdsCollection: this.types.holdsCollection(type),
|
|
2256
2408
|
isCallable: signature !== void 0,
|
|
2257
2409
|
returns: signature === void 0 ? void 0 : this.types.returnShapeOf(signature.getReturnType()),
|
|
2258
2410
|
valueTypes: this.types.classTypesIn(type)
|
|
@@ -2282,8 +2434,10 @@ var ClassReader = class {
|
|
|
2282
2434
|
const isStatic = Node.isStaticable(member) && member.isStatic();
|
|
2283
2435
|
const scope = Node.isScoped(member) ? member.getScope() : Scope.Public;
|
|
2284
2436
|
return new Member({
|
|
2437
|
+
holdsCollection: types.holdsCollection ?? false,
|
|
2285
2438
|
isCallable: types.isCallable ?? false,
|
|
2286
2439
|
isParameterProperty: false,
|
|
2440
|
+
isReadonly: Node.isReadonlyable(member) && member.isReadonly(),
|
|
2287
2441
|
isStatic,
|
|
2288
2442
|
kind,
|
|
2289
2443
|
line: member.getStartLineNumber(),
|
|
@@ -2299,8 +2453,10 @@ var ClassReader = class {
|
|
|
2299
2453
|
}
|
|
2300
2454
|
constructorParameter(parameter) {
|
|
2301
2455
|
return new Member({
|
|
2456
|
+
holdsCollection: this.types.holdsCollection(parameter.getType()),
|
|
2302
2457
|
isCallable: false,
|
|
2303
2458
|
isParameterProperty: parameter.isParameterProperty(),
|
|
2459
|
+
isReadonly: parameter.isReadonly(),
|
|
2304
2460
|
isStatic: false,
|
|
2305
2461
|
kind: "constructor parameter",
|
|
2306
2462
|
line: parameter.getStartLineNumber(),
|
|
@@ -2340,6 +2496,19 @@ var ClassReader = class {
|
|
|
2340
2496
|
//#endregion
|
|
2341
2497
|
//#region src/importer/ts-morph/readers/dependency-reader.ts
|
|
2342
2498
|
const everything = ["*"];
|
|
2499
|
+
const loaderPackages = [
|
|
2500
|
+
"module",
|
|
2501
|
+
"node:module",
|
|
2502
|
+
"vm",
|
|
2503
|
+
"node:vm"
|
|
2504
|
+
];
|
|
2505
|
+
const globalLoaders = ["eval", "Function"];
|
|
2506
|
+
const loaderMembers = [
|
|
2507
|
+
"eval",
|
|
2508
|
+
"Function",
|
|
2509
|
+
"require",
|
|
2510
|
+
"getBuiltinModule"
|
|
2511
|
+
];
|
|
2343
2512
|
var DependencyReader = class {
|
|
2344
2513
|
project;
|
|
2345
2514
|
scope;
|
|
@@ -2359,7 +2528,17 @@ var DependencyReader = class {
|
|
|
2359
2528
|
for (const declaration of file.getExportDeclarations()) if (declaration.hasModuleSpecifier()) references.push(this.fromDeclaration(declaration, "re-export"));
|
|
2360
2529
|
for (const declaration of file.getDescendantsOfKind(SyntaxKind.ImportEqualsDeclaration)) references.push(...this.fromImportEquals(declaration));
|
|
2361
2530
|
for (const node of file.getDescendantsOfKind(SyntaxKind.ImportType)) references.push(this.fromImportType(node));
|
|
2362
|
-
for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression))
|
|
2531
|
+
for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
2532
|
+
references.push(...this.fromCall(call));
|
|
2533
|
+
references.push(...this.fromLoader(call));
|
|
2534
|
+
}
|
|
2535
|
+
for (const construction of file.getDescendantsOfKind(SyntaxKind.NewExpression)) references.push(...this.fromLoader(construction));
|
|
2536
|
+
for (const reference of file.getPathReferenceDirectives()) references.push({
|
|
2537
|
+
...this.fromDirective(reference, file),
|
|
2538
|
+
isPath: true
|
|
2539
|
+
});
|
|
2540
|
+
for (const reference of file.getTypeReferenceDirectives()) references.push(this.fromDirective(reference, file));
|
|
2541
|
+
for (const declaration of file.getDescendantsOfKind(SyntaxKind.ModuleDeclaration)) references.push(...this.fromAugmentation(declaration));
|
|
2363
2542
|
return references;
|
|
2364
2543
|
}
|
|
2365
2544
|
fromDeclaration(declaration, form) {
|
|
@@ -2399,6 +2578,47 @@ var DependencyReader = class {
|
|
|
2399
2578
|
if (callee.isKind(SyntaxKind.Identifier) && callee.getText() === "require") return [this.reference(call, "require", argument, everything)];
|
|
2400
2579
|
return [];
|
|
2401
2580
|
}
|
|
2581
|
+
fromDirective(reference, file) {
|
|
2582
|
+
const line = file.getLineAndColumnAtPos(reference.getPos()).line;
|
|
2583
|
+
const specifier = reference.getFileName();
|
|
2584
|
+
return {
|
|
2585
|
+
form: "reference",
|
|
2586
|
+
line,
|
|
2587
|
+
names: everything,
|
|
2588
|
+
specifier,
|
|
2589
|
+
text: specifier
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
fromAugmentation(declaration) {
|
|
2593
|
+
const name = declaration.getNameNode();
|
|
2594
|
+
if (!name.isKind(SyntaxKind.StringLiteral) || name.getLiteralValue().includes("*")) return [];
|
|
2595
|
+
return [this.reference(declaration, "augmentation", name, everything)];
|
|
2596
|
+
}
|
|
2597
|
+
fromLoader(call) {
|
|
2598
|
+
const loader = this.loaderOf(call.getExpression());
|
|
2599
|
+
if (loader === void 0) return [];
|
|
2600
|
+
return [{
|
|
2601
|
+
form: "dynamic load",
|
|
2602
|
+
line: call.getStartLineNumber(),
|
|
2603
|
+
names: [],
|
|
2604
|
+
specifier: loader,
|
|
2605
|
+
text: loader
|
|
2606
|
+
}];
|
|
2607
|
+
}
|
|
2608
|
+
loaderOf(callee) {
|
|
2609
|
+
const expression = this.unwrapped(callee);
|
|
2610
|
+
if (expression.isKind(SyntaxKind.Identifier)) return globalLoaders.includes(expression.getText()) && !this.isDeclaredInProject(expression) ? expression.getText() : void 0;
|
|
2611
|
+
if (expression.isKind(SyntaxKind.PropertyAccessExpression) && loaderMembers.includes(expression.getName())) return expression.getText();
|
|
2612
|
+
}
|
|
2613
|
+
unwrapped(expression) {
|
|
2614
|
+
if (expression.isKind(SyntaxKind.ParenthesizedExpression)) return this.unwrapped(expression.getExpression());
|
|
2615
|
+
if (expression.isKind(SyntaxKind.BinaryExpression) && expression.getOperatorToken().isKind(SyntaxKind.CommaToken)) return this.unwrapped(expression.getRight());
|
|
2616
|
+
return expression;
|
|
2617
|
+
}
|
|
2618
|
+
isDeclaredInProject(expression) {
|
|
2619
|
+
const declaration = expression.getSymbol()?.getDeclarations()[0];
|
|
2620
|
+
return declaration !== void 0 && this.isInsideProject(normalize(declaration.getSourceFile().getFilePath()));
|
|
2621
|
+
}
|
|
2402
2622
|
reference(node, form, specifierNode, names) {
|
|
2403
2623
|
const isLiteral = specifierNode.isKind(SyntaxKind.StringLiteral) || specifierNode.isKind(SyntaxKind.NoSubstitutionTemplateLiteral);
|
|
2404
2624
|
return {
|
|
@@ -2410,15 +2630,20 @@ var DependencyReader = class {
|
|
|
2410
2630
|
};
|
|
2411
2631
|
}
|
|
2412
2632
|
targetOf(reference, file) {
|
|
2633
|
+
if (reference.form === "dynamic load" || loaderPackages.includes(reference.specifier ?? "")) return {
|
|
2634
|
+
kind: "file",
|
|
2635
|
+
path: reference.text,
|
|
2636
|
+
visibility: "dynamic"
|
|
2637
|
+
};
|
|
2413
2638
|
const directory = file.getDirectoryPath();
|
|
2414
2639
|
if (reference.specifier === void 0) return {
|
|
2415
2640
|
kind: "file",
|
|
2416
2641
|
path: normalize(join(directory, reference.text)),
|
|
2417
2642
|
visibility: "unresolved"
|
|
2418
2643
|
};
|
|
2419
|
-
const resolved = this.resolve(reference.specifier, file);
|
|
2644
|
+
const resolved = reference.isPath === true ? this.referencedPath(reference.specifier, directory) : this.resolve(reference.specifier, file);
|
|
2420
2645
|
if (resolved !== void 0 && this.isInsideProject(resolved)) return this.fileTarget(resolved);
|
|
2421
|
-
if (reference.specifier.startsWith(".")) return {
|
|
2646
|
+
if (reference.isPath === true || reference.specifier.startsWith(".")) return {
|
|
2422
2647
|
kind: "file",
|
|
2423
2648
|
path: normalize(join(directory, reference.specifier)),
|
|
2424
2649
|
visibility: "unresolved"
|
|
@@ -2439,6 +2664,10 @@ var DependencyReader = class {
|
|
|
2439
2664
|
const resolved = ts.resolveModuleName(specifier, file.getFilePath(), this.project.getCompilerOptions(), this.project.getModuleResolutionHost()).resolvedModule?.resolvedFileName;
|
|
2440
2665
|
return resolved === void 0 ? void 0 : normalize(resolved);
|
|
2441
2666
|
}
|
|
2667
|
+
referencedPath(specifier, directory) {
|
|
2668
|
+
const path = normalize(join(directory, specifier));
|
|
2669
|
+
return this.project.getModuleResolutionHost().fileExists(path) ? path : void 0;
|
|
2670
|
+
}
|
|
2442
2671
|
packageNameOf(specifier) {
|
|
2443
2672
|
const segments = specifier.split("/");
|
|
2444
2673
|
const length = specifier.startsWith("@") ? 2 : 1;
|
|
@@ -2624,6 +2853,12 @@ var ThrowReader = class {
|
|
|
2624
2853
|
};
|
|
2625
2854
|
//#endregion
|
|
2626
2855
|
//#region src/importer/ts-morph/readers/type-reader.ts
|
|
2856
|
+
const collections = [
|
|
2857
|
+
"Map",
|
|
2858
|
+
"Set",
|
|
2859
|
+
"WeakMap",
|
|
2860
|
+
"WeakSet"
|
|
2861
|
+
];
|
|
2627
2862
|
var TypeReader = class {
|
|
2628
2863
|
packageNames;
|
|
2629
2864
|
constructor(packageNames) {
|
|
@@ -2646,6 +2881,12 @@ var TypeReader = class {
|
|
|
2646
2881
|
packageName: void 0
|
|
2647
2882
|
} : this.namedSymbol(symbol);
|
|
2648
2883
|
}
|
|
2884
|
+
holdsCollection(type) {
|
|
2885
|
+
if (type.isArray() || type.isTuple()) return !type.isReadonlyArray();
|
|
2886
|
+
if (collections.includes(type.getSymbol()?.getName() ?? "")) return true;
|
|
2887
|
+
if (type.getStringIndexType() !== void 0 || type.getNumberIndexType() !== void 0) return true;
|
|
2888
|
+
return type.isAnonymous() && type.getCallSignatures().length === 0 && type.getConstructSignatures().length === 0;
|
|
2889
|
+
}
|
|
2649
2890
|
returnShapeOf(type) {
|
|
2650
2891
|
if (type.getSymbol()?.getName() === "Promise") {
|
|
2651
2892
|
const [inner] = type.getTypeArguments();
|
|
@@ -2710,17 +2951,98 @@ var TypeReader = class {
|
|
|
2710
2951
|
}
|
|
2711
2952
|
};
|
|
2712
2953
|
//#endregion
|
|
2954
|
+
//#region src/importer/ts-morph/readers/wiring-reader.ts
|
|
2955
|
+
const references = [SyntaxKind.Identifier, SyntaxKind.PropertyAccessExpression];
|
|
2956
|
+
var WiringReader = class {
|
|
2957
|
+
types;
|
|
2958
|
+
scope;
|
|
2959
|
+
constructor(types, scope) {
|
|
2960
|
+
this.types = types;
|
|
2961
|
+
this.scope = scope;
|
|
2962
|
+
}
|
|
2963
|
+
read(file) {
|
|
2964
|
+
if (!this.scope.readsWiring(file.getFilePath())) return [];
|
|
2965
|
+
const wirings = [];
|
|
2966
|
+
for (const site of this.sitesOf(file)) {
|
|
2967
|
+
const receiver = this.declaringFileOf(site.receiver);
|
|
2968
|
+
if (receiver === void 0) continue;
|
|
2969
|
+
for (const value of site.values) for (const reference of this.referencesIn(value)) wirings.push(new Wiring(reference.getStartLineNumber(), receiver, this.linksOf(reference)));
|
|
2970
|
+
}
|
|
2971
|
+
return wirings;
|
|
2972
|
+
}
|
|
2973
|
+
sitesOf(file) {
|
|
2974
|
+
const sites = [];
|
|
2975
|
+
file.forEachDescendant((node) => {
|
|
2976
|
+
if (Node.isCallExpression(node) || Node.isNewExpression(node)) sites.push({
|
|
2977
|
+
receiver: node.getExpression(),
|
|
2978
|
+
values: node.getArguments()
|
|
2979
|
+
});
|
|
2980
|
+
else if (Node.isBinaryExpression(node) && node.getOperatorToken().isKind(SyntaxKind.EqualsToken)) sites.push({
|
|
2981
|
+
receiver: node.getLeft(),
|
|
2982
|
+
values: [node.getRight()]
|
|
2983
|
+
});
|
|
2984
|
+
else if ((Node.isVariableDeclaration(node) || Node.isPropertyDeclaration(node)) && node.getTypeNode() !== void 0) {
|
|
2985
|
+
const initializer = node.getInitializer();
|
|
2986
|
+
sites.push({
|
|
2987
|
+
receiver: node.getTypeNode(),
|
|
2988
|
+
values: initializer === void 0 ? [] : [initializer]
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
});
|
|
2992
|
+
return sites;
|
|
2993
|
+
}
|
|
2994
|
+
referencesIn(value) {
|
|
2995
|
+
const found = [];
|
|
2996
|
+
const visit = (node) => {
|
|
2997
|
+
if (this.isOutermostReference(node)) found.push(node);
|
|
2998
|
+
node.forEachChild(visit);
|
|
2999
|
+
};
|
|
3000
|
+
visit(value);
|
|
3001
|
+
return found;
|
|
3002
|
+
}
|
|
3003
|
+
isOutermostReference(node) {
|
|
3004
|
+
if (!references.includes(node.getKind())) return false;
|
|
3005
|
+
const parent = node.getParent();
|
|
3006
|
+
if (parent === void 0 || Node.isPropertyAccessExpression(parent)) return false;
|
|
3007
|
+
if (Node.isTypeNode(parent) || Node.isQualifiedName(parent) || Node.isExpressionWithTypeArguments(parent)) return false;
|
|
3008
|
+
return Node.isShorthandPropertyAssignment(parent) || !(Node.hasName(parent) && parent.getNameNode() === node);
|
|
3009
|
+
}
|
|
3010
|
+
linksOf(reference) {
|
|
3011
|
+
if (Node.isPropertyAccessExpression(reference)) return [...this.linksOf(reference.getExpression()), this.link(reference, reference.getNameNode())];
|
|
3012
|
+
if (Node.isIdentifier(reference)) return [this.link(reference, reference)];
|
|
3013
|
+
return [];
|
|
3014
|
+
}
|
|
3015
|
+
link(expression, name) {
|
|
3016
|
+
return {
|
|
3017
|
+
declaredIn: this.declaringFileOf(name),
|
|
3018
|
+
text: expression.getText(),
|
|
3019
|
+
types: this.types.classTypesIn(expression.getType())
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
declaringFileOf(node) {
|
|
3023
|
+
if (node === void 0) return;
|
|
3024
|
+
const named = Node.isTypeNode(node) ? node.getType().getAliasSymbol() ?? node.getType().getSymbol() : this.nameOf(node).getSymbol();
|
|
3025
|
+
const declaration = (named?.isAlias() === true ? named.getAliasedSymbol() : named)?.getDeclarations()[0];
|
|
3026
|
+
return declaration === void 0 ? void 0 : normalize(declaration.getSourceFile().getFilePath());
|
|
3027
|
+
}
|
|
3028
|
+
nameOf(node) {
|
|
3029
|
+
return Node.isPropertyAccessExpression(node) ? node.getNameNode() : node;
|
|
3030
|
+
}
|
|
3031
|
+
};
|
|
3032
|
+
//#endregion
|
|
2713
3033
|
//#region src/importer/ts-morph/ts-morph-importer.ts
|
|
2714
3034
|
var TsMorphImporter = class TsMorphImporter extends Importer {
|
|
2715
3035
|
sources;
|
|
2716
3036
|
classes;
|
|
3037
|
+
types;
|
|
2717
3038
|
statements = new StatementReader();
|
|
2718
3039
|
disables = new DisableReader();
|
|
2719
3040
|
throws = new ThrowReader();
|
|
2720
3041
|
constructor(sources) {
|
|
2721
3042
|
super();
|
|
2722
3043
|
this.sources = sources;
|
|
2723
|
-
this.
|
|
3044
|
+
this.types = new TypeReader(new PackageNames(sources.getFileSystem()));
|
|
3045
|
+
this.classes = new ClassReader(this.types);
|
|
2724
3046
|
}
|
|
2725
3047
|
static fromTsConfig(tsConfigFilePath) {
|
|
2726
3048
|
if (!existsSync(tsConfigFilePath)) throw new Error(`No TypeScript configuration found at ${tsConfigFilePath}: pass --tsconfig, or set tsconfig in alveolus.config.ts.`);
|
|
@@ -2732,12 +3054,13 @@ var TsMorphImporter = class TsMorphImporter extends Importer {
|
|
|
2732
3054
|
}
|
|
2733
3055
|
read(scope) {
|
|
2734
3056
|
const dependencies = new DependencyReader(this.sources, scope);
|
|
3057
|
+
const wirings = new WiringReader(this.types, scope);
|
|
2735
3058
|
const globals = new GlobalReader(scope.projectDir);
|
|
2736
3059
|
const files = [];
|
|
2737
|
-
for (const file of this.sources.getSourceFiles()) if (this.isInside(scope.rootDir, file.getFilePath()) && !scope.isIgnored(file.getFilePath())) files.push(this.readFile(file, dependencies, globals));
|
|
3060
|
+
for (const file of this.sources.getSourceFiles()) if (this.isInside(scope.rootDir, file.getFilePath()) && !scope.isIgnored(file.getFilePath())) files.push(this.readFile(file, dependencies, globals, wirings));
|
|
2738
3061
|
return new Project$1(scope.projectDir, files);
|
|
2739
3062
|
}
|
|
2740
|
-
readFile(file, dependencies, globals) {
|
|
3063
|
+
readFile(file, dependencies, globals, wirings) {
|
|
2741
3064
|
const globalReferences = globals.read(file);
|
|
2742
3065
|
return new SourceFile({
|
|
2743
3066
|
classes: file.getClasses().map((declaration) => this.classes.read(declaration)),
|
|
@@ -2747,7 +3070,8 @@ var TsMorphImporter = class TsMorphImporter extends Importer {
|
|
|
2747
3070
|
path: normalize(file.getFilePath()),
|
|
2748
3071
|
statements: this.statements.read(file),
|
|
2749
3072
|
text: file.getFullText(),
|
|
2750
|
-
throws: this.throws.read(file)
|
|
3073
|
+
throws: this.throws.read(file),
|
|
3074
|
+
wirings: wirings.read(file)
|
|
2751
3075
|
});
|
|
2752
3076
|
}
|
|
2753
3077
|
globalUsesOf(references) {
|
|
@@ -3042,4 +3366,4 @@ var Docs = class Docs {
|
|
|
3042
3366
|
//#endregion
|
|
3043
3367
|
export { TsMorphImporter as a, Config as c, RuleRegistry as d, Rule as f, Baseline as h, Init as i, Report as l, AllowedPackages as m, Page as n, Importer as o, Architecture as p, Cli as r, ConfigLoader as s, Docs as t, Checker as u };
|
|
3044
3368
|
|
|
3045
|
-
//# sourceMappingURL=docs-
|
|
3369
|
+
//# sourceMappingURL=docs-B1Tzkjrn.mjs.map
|