@alveolus/arch 0.4.0 → 0.6.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-B6M9DxTM.mjs} +732 -15
- package/dist/docs-B6M9DxTM.mjs.map +1 -0
- package/dist/index.d.mts +68 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/docs/core/strategic/open-host-services.md +1 -1
- package/docs/guide/project-layout.md +3 -1
- package/docs/rules/index.md +17 -4
- package/docs/rules/layers/no-outward-import.md +7 -3
- package/docs/rules/strategic/no-cross-context-import.md +64 -2
- package/docs/rules/strategic/no-fat-shared-kernel.md +1 -0
- package/docs/rules/strategic/no-leaky-host-service.md +59 -6
- package/docs/rules/strategic/no-shared-state.md +126 -0
- package/docs/rules/strategic/no-unmapped-context.md +46 -7
- package/package.json +1 -1
- package/dist/docs-DcFgskuN.mjs.map +0 -1
|
@@ -166,22 +166,32 @@ 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;
|
|
176
|
+
receivesFunction;
|
|
177
|
+
erasedType;
|
|
178
|
+
opaqueValue;
|
|
174
179
|
constructor(props) {
|
|
175
180
|
this.name = props.name;
|
|
176
181
|
this.line = props.line;
|
|
177
182
|
this.kind = props.kind;
|
|
178
183
|
this.visibility = props.visibility;
|
|
179
184
|
this.isStatic = props.isStatic;
|
|
185
|
+
this.isReadonly = props.isReadonly;
|
|
180
186
|
this.isParameterProperty = props.isParameterProperty;
|
|
181
187
|
this.isCallable = props.isCallable;
|
|
182
188
|
this.valueTypes = props.valueTypes;
|
|
183
189
|
this.parameterTypes = props.parameterTypes;
|
|
184
190
|
this.returns = props.returns;
|
|
191
|
+
this.holdsCollection = props.holdsCollection;
|
|
192
|
+
this.receivesFunction = props.receivesFunction;
|
|
193
|
+
this.erasedType = props.erasedType;
|
|
194
|
+
this.opaqueValue = props.opaqueValue;
|
|
185
195
|
}
|
|
186
196
|
get isPublicInstance() {
|
|
187
197
|
return this.visibility === "public" && !this.isStatic;
|
|
@@ -213,6 +223,18 @@ var Dependency = class {
|
|
|
213
223
|
}
|
|
214
224
|
};
|
|
215
225
|
//#endregion
|
|
226
|
+
//#region src/model/dependencies/global-channel.ts
|
|
227
|
+
var GlobalChannel = class {
|
|
228
|
+
line;
|
|
229
|
+
name;
|
|
230
|
+
access;
|
|
231
|
+
constructor(line, name, access) {
|
|
232
|
+
this.line = line;
|
|
233
|
+
this.name = name;
|
|
234
|
+
this.access = access;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
//#endregion
|
|
216
238
|
//#region src/model/dependencies/global-use.ts
|
|
217
239
|
var GlobalUse = class {
|
|
218
240
|
line;
|
|
@@ -227,6 +249,32 @@ var GlobalUse = class {
|
|
|
227
249
|
}
|
|
228
250
|
};
|
|
229
251
|
//#endregion
|
|
252
|
+
//#region src/model/dependencies/module-reach.ts
|
|
253
|
+
var ModuleReach = class {
|
|
254
|
+
line;
|
|
255
|
+
expression;
|
|
256
|
+
means;
|
|
257
|
+
module;
|
|
258
|
+
constructor(line, expression, means, module) {
|
|
259
|
+
this.line = line;
|
|
260
|
+
this.expression = expression;
|
|
261
|
+
this.means = means;
|
|
262
|
+
this.module = module;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/model/dependencies/wiring.ts
|
|
267
|
+
var Wiring = class {
|
|
268
|
+
line;
|
|
269
|
+
receiver;
|
|
270
|
+
links;
|
|
271
|
+
constructor(line, receiver, links) {
|
|
272
|
+
this.line = line;
|
|
273
|
+
this.receiver = receiver;
|
|
274
|
+
this.links = links;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
//#endregion
|
|
230
278
|
//#region src/model/project.ts
|
|
231
279
|
var Project$1 = class {
|
|
232
280
|
directory;
|
|
@@ -257,6 +305,9 @@ var SourceFile = class {
|
|
|
257
305
|
statements;
|
|
258
306
|
throws;
|
|
259
307
|
globals;
|
|
308
|
+
globalChannels;
|
|
309
|
+
wirings;
|
|
310
|
+
moduleReaches;
|
|
260
311
|
disables;
|
|
261
312
|
lines;
|
|
262
313
|
constructor(props) {
|
|
@@ -266,6 +317,9 @@ var SourceFile = class {
|
|
|
266
317
|
this.statements = props.statements;
|
|
267
318
|
this.throws = props.throws;
|
|
268
319
|
this.globals = props.globals;
|
|
320
|
+
this.globalChannels = props.globalChannels;
|
|
321
|
+
this.wirings = props.wirings;
|
|
322
|
+
this.moduleReaches = props.moduleReaches;
|
|
269
323
|
this.disables = props.disables;
|
|
270
324
|
this.lines = props.text.split(/\r?\n/);
|
|
271
325
|
}
|
|
@@ -586,6 +640,60 @@ var CoreApi = class {
|
|
|
586
640
|
}
|
|
587
641
|
};
|
|
588
642
|
//#endregion
|
|
643
|
+
//#region src/architecture/crossing.ts
|
|
644
|
+
var Crossings = class {
|
|
645
|
+
architecture;
|
|
646
|
+
constructor(architecture) {
|
|
647
|
+
this.architecture = architecture;
|
|
648
|
+
}
|
|
649
|
+
in(file) {
|
|
650
|
+
const crossings = /* @__PURE__ */ new Map();
|
|
651
|
+
for (const wiring of file.wirings) {
|
|
652
|
+
const crossing = this.of(wiring);
|
|
653
|
+
if (crossing !== void 0) crossings.set(`${crossing.line}:${crossing.expression}:${crossing.from}:${crossing.to}`, crossing);
|
|
654
|
+
}
|
|
655
|
+
return [...crossings.values()];
|
|
656
|
+
}
|
|
657
|
+
of(wiring) {
|
|
658
|
+
const receiver = this.architecture.locationOfPath(wiring.receiver);
|
|
659
|
+
if (!receiver.isInBoundedContext || receiver.context === void 0) return;
|
|
660
|
+
for (const link of wiring.links) {
|
|
661
|
+
const source = this.sourceOf(link, receiver);
|
|
662
|
+
if (source !== void 0) return {
|
|
663
|
+
expression: link.text,
|
|
664
|
+
from: receiver.context,
|
|
665
|
+
isOpenHostService: this.isOpenHostService(link, receiver),
|
|
666
|
+
line: wiring.line,
|
|
667
|
+
to: source
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
sourceOf(link, receiver) {
|
|
672
|
+
if (link.declaredIn !== void 0) {
|
|
673
|
+
const declared = this.architecture.locationOfPath(link.declaredIn);
|
|
674
|
+
if (declared.isOtherBoundedContextThan(receiver)) return declared.context;
|
|
675
|
+
}
|
|
676
|
+
return this.foreignTypesOf(link, receiver)[0]?.context;
|
|
677
|
+
}
|
|
678
|
+
isOpenHostService(link, receiver) {
|
|
679
|
+
const foreign = link.types.filter((type) => this.isForeignModel(type.declaredIn, receiver));
|
|
680
|
+
return foreign.length > 0 && foreign.every((type) => {
|
|
681
|
+
const declaration = this.architecture.classOf(type);
|
|
682
|
+
return declaration !== void 0 && this.architecture.implementsMarker(declaration, "OpenHostService");
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
foreignTypesOf(link, receiver) {
|
|
686
|
+
const locations = [];
|
|
687
|
+
for (const type of link.types) if (this.isForeignModel(type.declaredIn, receiver)) locations.push(this.architecture.locationOfPath(type.declaredIn ?? ""));
|
|
688
|
+
return locations;
|
|
689
|
+
}
|
|
690
|
+
isForeignModel(path, receiver) {
|
|
691
|
+
if (path === void 0) return false;
|
|
692
|
+
const location = this.architecture.locationOfPath(path);
|
|
693
|
+
return location.isOtherBoundedContextThan(receiver) && !location.isCompositionRoot;
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
//#endregion
|
|
589
697
|
//#region src/architecture/layers.ts
|
|
590
698
|
var Layers = class {
|
|
591
699
|
isLayer(name) {
|
|
@@ -769,6 +877,7 @@ var Architecture = class {
|
|
|
769
877
|
shape;
|
|
770
878
|
layout;
|
|
771
879
|
locations = /* @__PURE__ */ new Map();
|
|
880
|
+
crossings = new Crossings(this);
|
|
772
881
|
constructor(project, settings) {
|
|
773
882
|
this.project = project;
|
|
774
883
|
this.settings = settings;
|
|
@@ -790,6 +899,12 @@ var Architecture = class {
|
|
|
790
899
|
locationOf(file) {
|
|
791
900
|
return this.locate(file.path);
|
|
792
901
|
}
|
|
902
|
+
locationOfPath(path) {
|
|
903
|
+
return this.locate(path);
|
|
904
|
+
}
|
|
905
|
+
crossingsIn(file) {
|
|
906
|
+
return this.crossings.in(file);
|
|
907
|
+
}
|
|
793
908
|
locationOfTarget(target) {
|
|
794
909
|
if (target.visibility !== "analysed") return new Location({
|
|
795
910
|
area: "outside",
|
|
@@ -882,10 +997,12 @@ var ContextMap = class {
|
|
|
882
997
|
//#region src/rules/framework/wording.ts
|
|
883
998
|
var Wording = class {
|
|
884
999
|
target(target, architecture) {
|
|
1000
|
+
if (target.visibility === "dynamic") return `code loaded at runtime with ${target.path}`;
|
|
885
1001
|
return `${architecture.relativePath(target.path)} (${this.location(architecture.locationOfTarget(target))})`;
|
|
886
1002
|
}
|
|
887
1003
|
location(location) {
|
|
888
1004
|
if (location.unseen === "ignored") return "ignored by the analysis";
|
|
1005
|
+
if (location.unseen === "dynamic") return "loaded at runtime";
|
|
889
1006
|
if (location.unseen === "unresolved") return "not resolved by the analysis";
|
|
890
1007
|
if (location.isOutside) return "outside the declared bounded contexts and shared kernel";
|
|
891
1008
|
if (location.isAtRoot) return "at the root";
|
|
@@ -1089,6 +1206,7 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1089
1206
|
description: "A dependency pointing away from the domain, a file outside the layers or in the wrong folder of its layer.",
|
|
1090
1207
|
id: "layers/no-outward-import",
|
|
1091
1208
|
messages: {
|
|
1209
|
+
compositionRootImport: "The composition root imports {target}: it wires its own context and the shared kernel only.",
|
|
1092
1210
|
compositionRoots: "{context} has {count} composition roots ({names}): keep one, and move the rest into the layers.",
|
|
1093
1211
|
directlyInLayer: "The file sits directly in {layer}/: put it in the folder of its kind, such as {example}.",
|
|
1094
1212
|
drivenTooDeep: "The file is nested too deep: driven/ holds driven/<technology>/<folder>/, such as driven/pg/adapters/.",
|
|
@@ -1183,11 +1301,15 @@ var NoOutwardImportRule = class extends ImportRule {
|
|
|
1183
1301
|
return findings;
|
|
1184
1302
|
}
|
|
1185
1303
|
layerProblem(from, to, architecture) {
|
|
1186
|
-
if (from.isAtRoot) return to.isInBoundedContext && !to.isCompositionRoot ? {
|
|
1304
|
+
if (from.isAtRoot) return to.isOutside || to.isInBoundedContext && !to.isCompositionRoot ? {
|
|
1187
1305
|
data: {},
|
|
1188
1306
|
messageId: "rootImport"
|
|
1189
1307
|
} : void 0;
|
|
1190
|
-
if (from.isCompositionRoot
|
|
1308
|
+
if (from.isCompositionRoot) return to.isOutside ? {
|
|
1309
|
+
data: {},
|
|
1310
|
+
messageId: "compositionRootImport"
|
|
1311
|
+
} : void 0;
|
|
1312
|
+
if (to.isOtherBoundedContextThan(from)) return;
|
|
1191
1313
|
if (to.isCompositionRoot) return {
|
|
1192
1314
|
data: {},
|
|
1193
1315
|
messageId: "wiring"
|
|
@@ -1249,16 +1371,57 @@ var NoPortlessAdapterRule = class extends ClassRule {
|
|
|
1249
1371
|
var NoCrossContextImportRule = class extends ImportRule {
|
|
1250
1372
|
meta = {
|
|
1251
1373
|
contexts: "every",
|
|
1252
|
-
description: "An import from another bounded context that is not its open host service, a composition root that re-exports.",
|
|
1374
|
+
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
1375
|
id: "strategic/no-cross-context-import",
|
|
1254
1376
|
messages: {
|
|
1377
|
+
dynamicLoad: "Loads code at runtime with {loader}: the analysis cannot tell which bounded context it reaches; use a static import.",
|
|
1378
|
+
globalChannel: "{access} {name}, which no file declares: two contexts can meet there without the context map showing it. Integrate through an open host service, or publish an integration event.",
|
|
1255
1379
|
notOpenHostService: "Imports {target}: only an OpenHostService of another bounded context may be imported.",
|
|
1256
1380
|
outsideAntiCorruptionLayer: "Uses the open host service of {context} outside an AntiCorruptionLayer: a core context translates what it consumes in an anti-corruption layer.",
|
|
1257
1381
|
publishedLanguage: "Imports the published language of {context}: redeclare the fields you read in your own published-language/.",
|
|
1382
|
+
reachedModule: "Reaches into {expression} with {means}: in the wiring, a module of another context is read by its properties, so that what crosses can be seen.",
|
|
1258
1383
|
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}."
|
|
1384
|
+
sharedKernel: "The shared kernel imports no bounded context, but imports {target}.",
|
|
1385
|
+
unseen: "Imports {target}: the analysis cannot tell which bounded context it reaches; move the file into a bounded context or the shared kernel.",
|
|
1386
|
+
wiredModel: "Gives {expression}, from {to}, to {from}: only an OpenHostService of another bounded context may cross, in an import or in the wiring."
|
|
1260
1387
|
}
|
|
1261
1388
|
};
|
|
1389
|
+
check(architecture) {
|
|
1390
|
+
const findings = super.check(architecture);
|
|
1391
|
+
for (const file of this.filesOf(architecture)) {
|
|
1392
|
+
findings.push(...this.reachedModules(file, architecture));
|
|
1393
|
+
findings.push(...this.globalChannels(file, architecture));
|
|
1394
|
+
for (const crossing of architecture.crossingsIn(file)) if (!crossing.isOpenHostService) findings.push(this.finding(file, crossing.line, crossing.expression, "wiredModel", {
|
|
1395
|
+
expression: crossing.expression,
|
|
1396
|
+
from: crossing.from,
|
|
1397
|
+
to: crossing.to
|
|
1398
|
+
}));
|
|
1399
|
+
}
|
|
1400
|
+
return findings;
|
|
1401
|
+
}
|
|
1402
|
+
globalChannels(file, architecture) {
|
|
1403
|
+
const location = architecture.locationOf(file);
|
|
1404
|
+
if (!location.isInBoundedContext && !location.isInSharedKernel && !location.isAtRoot) return [];
|
|
1405
|
+
return file.globalChannels.map((channel) => {
|
|
1406
|
+
const access = channel.access === "writes" ? "Writes" : "Reads";
|
|
1407
|
+
return this.finding(file, channel.line, channel.name, "globalChannel", {
|
|
1408
|
+
access,
|
|
1409
|
+
name: channel.name
|
|
1410
|
+
});
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
reachedModules(file, architecture) {
|
|
1414
|
+
const from = architecture.locationOf(file);
|
|
1415
|
+
const findings = [];
|
|
1416
|
+
for (const reach of file.moduleReaches) {
|
|
1417
|
+
const module = architecture.locationOfPath(reach.module.declaredIn ?? "");
|
|
1418
|
+
if (module.isCompositionRoot && module.isInBoundedContext && !module.isSameContextAs(from)) findings.push(this.finding(file, reach.line, reach.expression, "reachedModule", {
|
|
1419
|
+
expression: reach.expression,
|
|
1420
|
+
means: reach.means
|
|
1421
|
+
}));
|
|
1422
|
+
}
|
|
1423
|
+
return findings;
|
|
1424
|
+
}
|
|
1262
1425
|
appliesTo(file, architecture) {
|
|
1263
1426
|
const location = architecture.locationOf(file);
|
|
1264
1427
|
return location.isInBoundedContext || location.isInSharedKernel;
|
|
@@ -1269,6 +1432,8 @@ var NoCrossContextImportRule = class extends ImportRule {
|
|
|
1269
1432
|
const target = dependency.target;
|
|
1270
1433
|
if (target.kind !== "file") return;
|
|
1271
1434
|
const to = architecture.locationOfTarget(target);
|
|
1435
|
+
if (target.visibility === "dynamic") return this.finding(file, dependency.line, dependency.label, "dynamicLoad", { loader: target.path });
|
|
1436
|
+
if (to.isOutside) return this.finding(file, dependency.line, dependency.label, "unseen", { target: this.wording.target(target, architecture) });
|
|
1272
1437
|
if (!to.isOtherBoundedContextThan(from)) return;
|
|
1273
1438
|
if (from.isInSharedKernel) return this.finding(file, dependency.line, dependency.label, "sharedKernel", { target: this.wording.target(target, architecture) });
|
|
1274
1439
|
if (from.isCompositionRoot && to.isCompositionRoot) return;
|
|
@@ -1324,15 +1489,24 @@ var NoFatSharedKernelRule = class extends ClassRule {
|
|
|
1324
1489
|
var NoLeakyHostServiceRule = class extends ClassRule {
|
|
1325
1490
|
meta = {
|
|
1326
1491
|
contexts: "every",
|
|
1327
|
-
description: "An open host service that exposes a class of its context instead of the published language.",
|
|
1492
|
+
description: "An open host service that exposes a class of its context, receives a function or returns an erased type, instead of speaking the published language.",
|
|
1328
1493
|
id: "strategic/no-leaky-host-service",
|
|
1329
|
-
messages: {
|
|
1494
|
+
messages: {
|
|
1495
|
+
behaviour: "{member} receives a function: an open host service receives data, never behaviour, or the upstream context ends up running code of another one. To let another context react, publish an integration event.",
|
|
1496
|
+
erased: "{member} returns {type}: an open host service returns a type of its published language, so that what leaves the context can be seen.",
|
|
1497
|
+
exposes: "{member} exposes {type}, {kind}{owner}: an open host service speaks the published language."
|
|
1498
|
+
}
|
|
1330
1499
|
};
|
|
1331
1500
|
findingsFor(codeClass, file, architecture) {
|
|
1332
1501
|
if (!architecture.implementsMarker(codeClass, "OpenHostService")) return [];
|
|
1333
1502
|
const findings = /* @__PURE__ */ new Map();
|
|
1334
1503
|
for (const member of codeClass.publicSurface) {
|
|
1335
1504
|
const symbol = `${codeClass.name}.${member.name}`;
|
|
1505
|
+
if (member.receivesFunction) findings.set(`${symbol}:behaviour`, this.finding(file, member.line, symbol, "behaviour", { member: symbol }));
|
|
1506
|
+
if (member.erasedType !== void 0) findings.set(`${symbol}:erased`, this.finding(file, member.line, symbol, "erased", {
|
|
1507
|
+
member: symbol,
|
|
1508
|
+
type: member.erasedType
|
|
1509
|
+
}));
|
|
1336
1510
|
for (const type of [...member.parameterTypes, ...member.valueTypes]) {
|
|
1337
1511
|
const key = `${symbol}:${type.name}`;
|
|
1338
1512
|
if (this.leaks(type, architecture) && !findings.has(key)) findings.set(key, this.finding(file, member.line, symbol, "exposes", this.describe(symbol, type, architecture)));
|
|
@@ -1359,17 +1533,83 @@ var NoLeakyHostServiceRule = class extends ClassRule {
|
|
|
1359
1533
|
}
|
|
1360
1534
|
};
|
|
1361
1535
|
//#endregion
|
|
1536
|
+
//#region src/rules/strategic/no-shared-state.rule.ts
|
|
1537
|
+
var NoSharedStateRule = class extends ClassRule {
|
|
1538
|
+
meta = {
|
|
1539
|
+
contexts: "every",
|
|
1540
|
+
description: "A static field of the shared kernel that holds state: one without readonly, a collection, or anything but a value.",
|
|
1541
|
+
id: "strategic/no-shared-state",
|
|
1542
|
+
messages: {
|
|
1543
|
+
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.",
|
|
1544
|
+
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.",
|
|
1545
|
+
notValue: "{class}.{field} holds {holds} in a static field, which can keep state every context reaches, a channel the context map does not show. A static field of the shared kernel holds a value: a primitive, a value object, an identifier, a class of domainDependencies, or a readonly collection of them."
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
findingsFor(codeClass, file, architecture) {
|
|
1549
|
+
if (!architecture.locationOf(file).isInSharedKernel) return [];
|
|
1550
|
+
const findings = [];
|
|
1551
|
+
for (const member of codeClass.members) {
|
|
1552
|
+
const state = this.stateOf(member, architecture);
|
|
1553
|
+
if (state !== void 0) findings.push(this.finding(file, member.line, member.name, state.messageId, {
|
|
1554
|
+
...state.data,
|
|
1555
|
+
class: codeClass.name,
|
|
1556
|
+
field: member.name
|
|
1557
|
+
}));
|
|
1558
|
+
}
|
|
1559
|
+
return findings;
|
|
1560
|
+
}
|
|
1561
|
+
stateOf(member, architecture) {
|
|
1562
|
+
if (!member.isStatic || member.kind !== "field") return;
|
|
1563
|
+
if (!member.isReadonly) return {
|
|
1564
|
+
data: {},
|
|
1565
|
+
messageId: "mutable"
|
|
1566
|
+
};
|
|
1567
|
+
if (member.holdsCollection) return {
|
|
1568
|
+
data: {},
|
|
1569
|
+
messageId: "collection"
|
|
1570
|
+
};
|
|
1571
|
+
if (member.opaqueValue !== void 0) return {
|
|
1572
|
+
data: { holds: member.opaqueValue },
|
|
1573
|
+
messageId: "notValue"
|
|
1574
|
+
};
|
|
1575
|
+
const service = member.valueTypes.find((type) => !this.isValue(type, architecture));
|
|
1576
|
+
if (service === void 0) return;
|
|
1577
|
+
return {
|
|
1578
|
+
data: { holds: `${/^[AEIOU]/.test(service.name) ? "an" : "a"} ${service.name}` },
|
|
1579
|
+
messageId: "notValue"
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
isValue(type, architecture) {
|
|
1583
|
+
if (architecture.is(type, "ValueObject") || architecture.is(type, "Identifier")) return true;
|
|
1584
|
+
const packageName = type.lineage[0]?.packageName;
|
|
1585
|
+
return packageName !== void 0 && architecture.domainDependencies.has(packageName);
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
//#endregion
|
|
1362
1589
|
//#region src/rules/strategic/no-unmapped-context.rule.ts
|
|
1363
1590
|
var NoUnmappedContextRule = class extends Rule {
|
|
1364
1591
|
meta = {
|
|
1365
1592
|
contexts: "every",
|
|
1366
|
-
description: "A bounded context consuming one the context map does not allow.",
|
|
1593
|
+
description: "A bounded context consuming one the context map does not allow, through an import or through the wiring.",
|
|
1367
1594
|
id: "strategic/no-unmapped-context",
|
|
1368
|
-
messages: {
|
|
1595
|
+
messages: {
|
|
1596
|
+
unmapped: "{from} consumes {to}, which the context map does not allow: reverse the dependency with an integration event that {from} publishes and {to} subscribes to, not with a callback; or if {from} really is downstream of {to}, add {to} to contextMap.{from}.consumes.",
|
|
1597
|
+
unmappedWiring: "{from} receives {expression} from {to} here, which the context map does not allow: reverse the dependency with an integration event that {from} publishes and {to} subscribes to, not with a callback; or if {from} really is downstream of {to}, add {to} to contextMap.{from}.consumes."
|
|
1598
|
+
}
|
|
1369
1599
|
};
|
|
1370
1600
|
check(architecture) {
|
|
1371
1601
|
const findings = [];
|
|
1372
1602
|
for (const consumption of this.consumptionsIn(architecture)) if (!architecture.contextMap.allows(consumption.from, consumption.to)) findings.push(this.consumptionFinding(consumption));
|
|
1603
|
+
findings.push(...this.unmappedWirings(architecture));
|
|
1604
|
+
return findings;
|
|
1605
|
+
}
|
|
1606
|
+
unmappedWirings(architecture) {
|
|
1607
|
+
const findings = [];
|
|
1608
|
+
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", {
|
|
1609
|
+
expression: crossing.expression,
|
|
1610
|
+
from: crossing.from,
|
|
1611
|
+
to: crossing.to
|
|
1612
|
+
}));
|
|
1373
1613
|
return findings;
|
|
1374
1614
|
}
|
|
1375
1615
|
consumptionsIn(architecture) {
|
|
@@ -1830,6 +2070,7 @@ const ruleIds = [
|
|
|
1830
2070
|
"strategic/no-leaky-host-service",
|
|
1831
2071
|
"strategic/no-unmapped-context",
|
|
1832
2072
|
"strategic/no-fat-shared-kernel",
|
|
2073
|
+
"strategic/no-shared-state",
|
|
1833
2074
|
"layers/no-impure-domain",
|
|
1834
2075
|
"layers/no-outward-import",
|
|
1835
2076
|
"layers/no-portless-adapter",
|
|
@@ -1850,6 +2091,7 @@ var RuleRegistry = class {
|
|
|
1850
2091
|
new NoLeakyHostServiceRule(),
|
|
1851
2092
|
new NoUnmappedContextRule(),
|
|
1852
2093
|
new NoFatSharedKernelRule(),
|
|
2094
|
+
new NoSharedStateRule(),
|
|
1853
2095
|
new NoImpureDomainRule(),
|
|
1854
2096
|
new NoOutwardImportRule(),
|
|
1855
2097
|
new NoPortlessAdapterRule(),
|
|
@@ -2088,6 +2330,7 @@ var Config = class {
|
|
|
2088
2330
|
contextMap;
|
|
2089
2331
|
ignored;
|
|
2090
2332
|
rules;
|
|
2333
|
+
layout;
|
|
2091
2334
|
constructor(config, projectDir) {
|
|
2092
2335
|
this.projectDir = resolve(projectDir);
|
|
2093
2336
|
this.rootDir = resolve(projectDir, config.root);
|
|
@@ -2105,6 +2348,7 @@ var Config = class {
|
|
|
2105
2348
|
isSharedKernel: true,
|
|
2106
2349
|
name: "shared kernel"
|
|
2107
2350
|
}];
|
|
2351
|
+
this.layout = new Layout(this);
|
|
2108
2352
|
}
|
|
2109
2353
|
classifiedContexts(subdomains, boundedContexts) {
|
|
2110
2354
|
const types = [
|
|
@@ -2149,6 +2393,10 @@ var Config = class {
|
|
|
2149
2393
|
const projectPath = relative(this.projectDir, path).split(sep).join("/");
|
|
2150
2394
|
return this.ignored.some((glob) => matchesGlob(projectPath, glob));
|
|
2151
2395
|
}
|
|
2396
|
+
readsWiring(path) {
|
|
2397
|
+
const location = this.layout.locate(path);
|
|
2398
|
+
return location.isAtRoot || location.isCompositionRoot;
|
|
2399
|
+
}
|
|
2152
2400
|
severityOf(rule) {
|
|
2153
2401
|
return this.rules?.[rule] ?? "error";
|
|
2154
2402
|
}
|
|
@@ -2253,7 +2501,11 @@ var ClassReader = class {
|
|
|
2253
2501
|
const type = member.getType();
|
|
2254
2502
|
const [signature] = type.getCallSignatures();
|
|
2255
2503
|
return [this.member(member, "field", {
|
|
2504
|
+
erasedType: this.types.erasedNameOf(signature === void 0 ? type : signature.getReturnType()),
|
|
2505
|
+
holdsCollection: this.types.holdsCollection(type),
|
|
2256
2506
|
isCallable: signature !== void 0,
|
|
2507
|
+
opaqueValue: this.types.opaqueValueIn(type),
|
|
2508
|
+
receivesFunction: signature !== void 0 && signature.getParameters().some((parameter) => this.types.holdsFunction(parameter.getTypeAtLocation(member))),
|
|
2257
2509
|
returns: signature === void 0 ? void 0 : this.types.returnShapeOf(signature.getReturnType()),
|
|
2258
2510
|
valueTypes: this.types.classTypesIn(type)
|
|
2259
2511
|
})];
|
|
@@ -2261,8 +2513,11 @@ var ClassReader = class {
|
|
|
2261
2513
|
if (Node.isMethodDeclaration(member)) {
|
|
2262
2514
|
const returnType = member.getReturnType();
|
|
2263
2515
|
const parameterTypes = member.getParameters().flatMap((parameter) => this.types.classTypesIn(parameter.getType()));
|
|
2516
|
+
const receivesFunction = member.getParameters().some((parameter) => this.types.holdsFunction(parameter.getType()));
|
|
2264
2517
|
return [this.member(member, "method", {
|
|
2518
|
+
erasedType: this.types.erasedNameOf(returnType),
|
|
2265
2519
|
parameterTypes,
|
|
2520
|
+
receivesFunction,
|
|
2266
2521
|
returns: this.types.returnShapeOf(returnType),
|
|
2267
2522
|
valueTypes: this.types.classTypesIn(returnType)
|
|
2268
2523
|
})];
|
|
@@ -2270,6 +2525,7 @@ var ClassReader = class {
|
|
|
2270
2525
|
if (Node.isGetAccessorDeclaration(member)) {
|
|
2271
2526
|
const returnType = member.getReturnType();
|
|
2272
2527
|
return [this.member(member, "getter", {
|
|
2528
|
+
erasedType: this.types.erasedNameOf(returnType),
|
|
2273
2529
|
returns: this.types.returnShapeOf(returnType),
|
|
2274
2530
|
valueTypes: this.types.classTypesIn(returnType)
|
|
2275
2531
|
})];
|
|
@@ -2282,13 +2538,18 @@ var ClassReader = class {
|
|
|
2282
2538
|
const isStatic = Node.isStaticable(member) && member.isStatic();
|
|
2283
2539
|
const scope = Node.isScoped(member) ? member.getScope() : Scope.Public;
|
|
2284
2540
|
return new Member({
|
|
2541
|
+
erasedType: types.erasedType,
|
|
2542
|
+
holdsCollection: types.holdsCollection ?? false,
|
|
2285
2543
|
isCallable: types.isCallable ?? false,
|
|
2286
2544
|
isParameterProperty: false,
|
|
2545
|
+
isReadonly: Node.isReadonlyable(member) && member.isReadonly(),
|
|
2287
2546
|
isStatic,
|
|
2288
2547
|
kind,
|
|
2289
2548
|
line: member.getStartLineNumber(),
|
|
2290
2549
|
name,
|
|
2550
|
+
opaqueValue: types.opaqueValue,
|
|
2291
2551
|
parameterTypes: types.parameterTypes ?? [],
|
|
2552
|
+
receivesFunction: types.receivesFunction ?? false,
|
|
2292
2553
|
returns: types.returns,
|
|
2293
2554
|
valueTypes: types.valueTypes ?? [],
|
|
2294
2555
|
visibility: name.startsWith("#") ? "private" : this.visibilityOf(scope)
|
|
@@ -2299,13 +2560,18 @@ var ClassReader = class {
|
|
|
2299
2560
|
}
|
|
2300
2561
|
constructorParameter(parameter) {
|
|
2301
2562
|
return new Member({
|
|
2563
|
+
erasedType: void 0,
|
|
2564
|
+
holdsCollection: this.types.holdsCollection(parameter.getType()),
|
|
2302
2565
|
isCallable: false,
|
|
2303
2566
|
isParameterProperty: parameter.isParameterProperty(),
|
|
2567
|
+
isReadonly: parameter.isReadonly(),
|
|
2304
2568
|
isStatic: false,
|
|
2305
2569
|
kind: "constructor parameter",
|
|
2306
2570
|
line: parameter.getStartLineNumber(),
|
|
2307
2571
|
name: parameter.getName(),
|
|
2572
|
+
opaqueValue: void 0,
|
|
2308
2573
|
parameterTypes: [],
|
|
2574
|
+
receivesFunction: false,
|
|
2309
2575
|
returns: void 0,
|
|
2310
2576
|
valueTypes: this.types.classTypesIn(parameter.getType()),
|
|
2311
2577
|
visibility: this.visibilityOf(parameter.getScope() ?? Scope.Public)
|
|
@@ -2338,11 +2604,101 @@ var ClassReader = class {
|
|
|
2338
2604
|
}
|
|
2339
2605
|
};
|
|
2340
2606
|
//#endregion
|
|
2607
|
+
//#region src/importer/ts-morph/readers/loader-reader.ts
|
|
2608
|
+
const globalObjects$1 = [
|
|
2609
|
+
"globalThis",
|
|
2610
|
+
"global",
|
|
2611
|
+
"window",
|
|
2612
|
+
"self"
|
|
2613
|
+
];
|
|
2614
|
+
const globalLoaders = ["eval", "Function"];
|
|
2615
|
+
const loaderMembers = [
|
|
2616
|
+
"eval",
|
|
2617
|
+
"Function",
|
|
2618
|
+
"require",
|
|
2619
|
+
"getBuiltinModule"
|
|
2620
|
+
];
|
|
2621
|
+
const reflections = [
|
|
2622
|
+
"get",
|
|
2623
|
+
"apply",
|
|
2624
|
+
"construct"
|
|
2625
|
+
];
|
|
2626
|
+
var LoaderReader = class {
|
|
2627
|
+
read(file) {
|
|
2628
|
+
const loads = [];
|
|
2629
|
+
file.forEachDescendant((node) => {
|
|
2630
|
+
const loader = this.loaderAt(node);
|
|
2631
|
+
if (loader !== void 0) loads.push({
|
|
2632
|
+
line: node.getStartLineNumber(),
|
|
2633
|
+
loader
|
|
2634
|
+
});
|
|
2635
|
+
});
|
|
2636
|
+
return loads;
|
|
2637
|
+
}
|
|
2638
|
+
loaderAt(node) {
|
|
2639
|
+
if (Node.isCallExpression(node) || Node.isNewExpression(node)) return this.calledLoader(node);
|
|
2640
|
+
if (Node.isElementAccessExpression(node)) return this.globalLookup(node.getExpression(), node.getArgumentExpression());
|
|
2641
|
+
}
|
|
2642
|
+
calledLoader(call) {
|
|
2643
|
+
const callee = call.getExpression();
|
|
2644
|
+
const bare = this.unwrapped(callee);
|
|
2645
|
+
if (Node.isElementAccessExpression(bare)) return;
|
|
2646
|
+
const reflected = this.reflectedLoader(bare, call.getArguments());
|
|
2647
|
+
if (reflected !== void 0) return reflected;
|
|
2648
|
+
return this.namedLoader(bare) ?? this.typedLoader(callee) ?? this.typedLoader(bare);
|
|
2649
|
+
}
|
|
2650
|
+
reflectedLoader(callee, args) {
|
|
2651
|
+
if (!Node.isPropertyAccessExpression(callee) || callee.getExpression().getText() !== "Reflect" || !reflections.includes(callee.getName())) return;
|
|
2652
|
+
const [target, key] = args;
|
|
2653
|
+
if (target === void 0) return;
|
|
2654
|
+
if (callee.getName() === "get") return this.globalLookup(target, key);
|
|
2655
|
+
const bare = this.unwrapped(target);
|
|
2656
|
+
return this.namedLoader(bare) ?? this.typedLoader(target) ?? this.typedLoader(bare);
|
|
2657
|
+
}
|
|
2658
|
+
globalLookup(object, key) {
|
|
2659
|
+
const bare = this.unwrapped(object);
|
|
2660
|
+
if (!Node.isIdentifier(bare) || !globalObjects$1.includes(bare.getText()) || !this.isFromLibrary(bare)) return;
|
|
2661
|
+
if (key === void 0 || !Node.isStringLiteral(key)) return `${bare.getText()}[…]`;
|
|
2662
|
+
return globalLoaders.includes(key.getLiteralValue()) ? `${bare.getText()}.${key.getLiteralValue()}` : void 0;
|
|
2663
|
+
}
|
|
2664
|
+
namedLoader(callee) {
|
|
2665
|
+
if (Node.isIdentifier(callee)) return globalLoaders.includes(callee.getText()) && this.isFromLibrary(callee) ? callee.getText() : void 0;
|
|
2666
|
+
if (!Node.isPropertyAccessExpression(callee)) return;
|
|
2667
|
+
if (loaderMembers.includes(callee.getName())) return callee.getText();
|
|
2668
|
+
if (callee.getName() === "constructor") {
|
|
2669
|
+
const owner = callee.getExpression().getType();
|
|
2670
|
+
return owner.isAny() || owner.getCallSignatures().length > 0 ? callee.getText() : void 0;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
typedLoader(callee) {
|
|
2674
|
+
const symbol = callee.getType().getSymbol();
|
|
2675
|
+
if (symbol?.getName() === "FunctionConstructor") return "Function";
|
|
2676
|
+
const declaration = symbol?.getDeclarations()[0];
|
|
2677
|
+
if (symbol?.getName() === "eval" && declaration?.getSourceFile().isDeclarationFile() === true) return "eval";
|
|
2678
|
+
}
|
|
2679
|
+
isFromLibrary(identifier) {
|
|
2680
|
+
const declaration = identifier.getSymbol()?.getDeclarations()[0];
|
|
2681
|
+
return declaration === void 0 || declaration.getSourceFile().isDeclarationFile();
|
|
2682
|
+
}
|
|
2683
|
+
unwrapped(expression) {
|
|
2684
|
+
if (Node.isParenthesizedExpression(expression) || Node.isAsExpression(expression) || Node.isNonNullExpression(expression) || Node.isSatisfiesExpression(expression)) return this.unwrapped(expression.getExpression());
|
|
2685
|
+
if (Node.isBinaryExpression(expression) && expression.getOperatorToken().getText() === ",") return this.unwrapped(expression.getRight());
|
|
2686
|
+
return expression;
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
//#endregion
|
|
2341
2690
|
//#region src/importer/ts-morph/readers/dependency-reader.ts
|
|
2342
2691
|
const everything = ["*"];
|
|
2692
|
+
const loaderPackages = [
|
|
2693
|
+
"module",
|
|
2694
|
+
"node:module",
|
|
2695
|
+
"vm",
|
|
2696
|
+
"node:vm"
|
|
2697
|
+
];
|
|
2343
2698
|
var DependencyReader = class {
|
|
2344
2699
|
project;
|
|
2345
2700
|
scope;
|
|
2701
|
+
loaders = new LoaderReader();
|
|
2346
2702
|
constructor(project, scope) {
|
|
2347
2703
|
this.project = project;
|
|
2348
2704
|
this.scope = scope;
|
|
@@ -2360,6 +2716,19 @@ var DependencyReader = class {
|
|
|
2360
2716
|
for (const declaration of file.getDescendantsOfKind(SyntaxKind.ImportEqualsDeclaration)) references.push(...this.fromImportEquals(declaration));
|
|
2361
2717
|
for (const node of file.getDescendantsOfKind(SyntaxKind.ImportType)) references.push(this.fromImportType(node));
|
|
2362
2718
|
for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) references.push(...this.fromCall(call));
|
|
2719
|
+
for (const load of this.loaders.read(file)) references.push({
|
|
2720
|
+
form: "dynamic load",
|
|
2721
|
+
line: load.line,
|
|
2722
|
+
names: [],
|
|
2723
|
+
specifier: load.loader,
|
|
2724
|
+
text: load.loader
|
|
2725
|
+
});
|
|
2726
|
+
for (const reference of file.getPathReferenceDirectives()) references.push({
|
|
2727
|
+
...this.fromDirective(reference, file),
|
|
2728
|
+
isPath: true
|
|
2729
|
+
});
|
|
2730
|
+
for (const reference of file.getTypeReferenceDirectives()) references.push(this.fromDirective(reference, file));
|
|
2731
|
+
for (const declaration of file.getDescendantsOfKind(SyntaxKind.ModuleDeclaration)) references.push(...this.fromAugmentation(declaration));
|
|
2363
2732
|
return references;
|
|
2364
2733
|
}
|
|
2365
2734
|
fromDeclaration(declaration, form) {
|
|
@@ -2399,6 +2768,22 @@ var DependencyReader = class {
|
|
|
2399
2768
|
if (callee.isKind(SyntaxKind.Identifier) && callee.getText() === "require") return [this.reference(call, "require", argument, everything)];
|
|
2400
2769
|
return [];
|
|
2401
2770
|
}
|
|
2771
|
+
fromDirective(reference, file) {
|
|
2772
|
+
const line = file.getLineAndColumnAtPos(reference.getPos()).line;
|
|
2773
|
+
const specifier = reference.getFileName();
|
|
2774
|
+
return {
|
|
2775
|
+
form: "reference",
|
|
2776
|
+
line,
|
|
2777
|
+
names: everything,
|
|
2778
|
+
specifier,
|
|
2779
|
+
text: specifier
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
fromAugmentation(declaration) {
|
|
2783
|
+
const name = declaration.getNameNode();
|
|
2784
|
+
if (!name.isKind(SyntaxKind.StringLiteral) || name.getLiteralValue().includes("*")) return [];
|
|
2785
|
+
return [this.reference(declaration, "augmentation", name, everything)];
|
|
2786
|
+
}
|
|
2402
2787
|
reference(node, form, specifierNode, names) {
|
|
2403
2788
|
const isLiteral = specifierNode.isKind(SyntaxKind.StringLiteral) || specifierNode.isKind(SyntaxKind.NoSubstitutionTemplateLiteral);
|
|
2404
2789
|
return {
|
|
@@ -2410,15 +2795,20 @@ var DependencyReader = class {
|
|
|
2410
2795
|
};
|
|
2411
2796
|
}
|
|
2412
2797
|
targetOf(reference, file) {
|
|
2798
|
+
if (reference.form === "dynamic load" || loaderPackages.includes(reference.specifier ?? "")) return {
|
|
2799
|
+
kind: "file",
|
|
2800
|
+
path: reference.text,
|
|
2801
|
+
visibility: "dynamic"
|
|
2802
|
+
};
|
|
2413
2803
|
const directory = file.getDirectoryPath();
|
|
2414
2804
|
if (reference.specifier === void 0) return {
|
|
2415
2805
|
kind: "file",
|
|
2416
2806
|
path: normalize(join(directory, reference.text)),
|
|
2417
2807
|
visibility: "unresolved"
|
|
2418
2808
|
};
|
|
2419
|
-
const resolved = this.resolve(reference.specifier, file);
|
|
2809
|
+
const resolved = reference.isPath === true ? this.referencedPath(reference.specifier, directory) : this.resolve(reference.specifier, file);
|
|
2420
2810
|
if (resolved !== void 0 && this.isInsideProject(resolved)) return this.fileTarget(resolved);
|
|
2421
|
-
if (reference.specifier.startsWith(".")) return {
|
|
2811
|
+
if (reference.isPath === true || reference.specifier.startsWith(".")) return {
|
|
2422
2812
|
kind: "file",
|
|
2423
2813
|
path: normalize(join(directory, reference.specifier)),
|
|
2424
2814
|
visibility: "unresolved"
|
|
@@ -2439,6 +2829,10 @@ var DependencyReader = class {
|
|
|
2439
2829
|
const resolved = ts.resolveModuleName(specifier, file.getFilePath(), this.project.getCompilerOptions(), this.project.getModuleResolutionHost()).resolvedModule?.resolvedFileName;
|
|
2440
2830
|
return resolved === void 0 ? void 0 : normalize(resolved);
|
|
2441
2831
|
}
|
|
2832
|
+
referencedPath(specifier, directory) {
|
|
2833
|
+
const path = normalize(join(directory, specifier));
|
|
2834
|
+
return this.project.getModuleResolutionHost().fileExists(path) ? path : void 0;
|
|
2835
|
+
}
|
|
2442
2836
|
packageNameOf(specifier) {
|
|
2443
2837
|
const segments = specifier.split("/");
|
|
2444
2838
|
const length = specifier.startsWith("@") ? 2 : 1;
|
|
@@ -2464,6 +2858,97 @@ var DisableReader = class {
|
|
|
2464
2858
|
}
|
|
2465
2859
|
};
|
|
2466
2860
|
//#endregion
|
|
2861
|
+
//#region src/importer/ts-morph/readers/global-channel-reader.ts
|
|
2862
|
+
const globalObjects = [
|
|
2863
|
+
"globalThis",
|
|
2864
|
+
"global",
|
|
2865
|
+
"window",
|
|
2866
|
+
"self"
|
|
2867
|
+
];
|
|
2868
|
+
const assignments = [
|
|
2869
|
+
SyntaxKind.EqualsToken,
|
|
2870
|
+
SyntaxKind.QuestionQuestionEqualsToken,
|
|
2871
|
+
SyntaxKind.BarBarEqualsToken,
|
|
2872
|
+
SyntaxKind.AmpersandAmpersandEqualsToken,
|
|
2873
|
+
SyntaxKind.PlusEqualsToken
|
|
2874
|
+
];
|
|
2875
|
+
const reflectionWrites = [
|
|
2876
|
+
"set",
|
|
2877
|
+
"defineProperty",
|
|
2878
|
+
"deleteProperty"
|
|
2879
|
+
];
|
|
2880
|
+
const objectWrites = [
|
|
2881
|
+
"defineProperty",
|
|
2882
|
+
"defineProperties",
|
|
2883
|
+
"assign"
|
|
2884
|
+
];
|
|
2885
|
+
var GlobalChannelReader = class {
|
|
2886
|
+
read(file) {
|
|
2887
|
+
const channels = [];
|
|
2888
|
+
file.forEachDescendant((node) => {
|
|
2889
|
+
channels.push(...this.channelsAt(node));
|
|
2890
|
+
});
|
|
2891
|
+
return channels;
|
|
2892
|
+
}
|
|
2893
|
+
channelsAt(node) {
|
|
2894
|
+
if (Node.isPropertyAccessExpression(node)) return this.lookup(node, node.getExpression(), node.getName());
|
|
2895
|
+
if (Node.isElementAccessExpression(node)) {
|
|
2896
|
+
const key = node.getArgumentExpression();
|
|
2897
|
+
return key !== void 0 && Node.isStringLiteral(key) ? this.lookup(node, node.getExpression(), key.getLiteralValue()) : [];
|
|
2898
|
+
}
|
|
2899
|
+
if (Node.isCallExpression(node)) return this.reflected(node);
|
|
2900
|
+
return [];
|
|
2901
|
+
}
|
|
2902
|
+
lookup(access, object, name) {
|
|
2903
|
+
const global = this.globalObject(object);
|
|
2904
|
+
if (global === void 0 || global.getType().getProperty(name) !== void 0) return [];
|
|
2905
|
+
return [new GlobalChannel(access.getStartLineNumber(), `${global.getText()}.${name}`, this.accessOf(access))];
|
|
2906
|
+
}
|
|
2907
|
+
reflected(call) {
|
|
2908
|
+
const callee = call.getExpression();
|
|
2909
|
+
if (!Node.isPropertyAccessExpression(callee)) return [];
|
|
2910
|
+
const owner = callee.getExpression().getText();
|
|
2911
|
+
const method = callee.getName();
|
|
2912
|
+
const [target, key] = call.getArguments();
|
|
2913
|
+
const global = target === void 0 ? void 0 : this.globalObject(target);
|
|
2914
|
+
if (global === void 0) return [];
|
|
2915
|
+
if (owner === "Reflect" && key !== void 0 && Node.isStringLiteral(key)) {
|
|
2916
|
+
const access = reflectionWrites.includes(method) ? "writes" : "reads";
|
|
2917
|
+
return this.undeclared(global, [key.getLiteralValue()], call.getStartLineNumber(), access);
|
|
2918
|
+
}
|
|
2919
|
+
if (owner === "Object" && objectWrites.includes(method) && key !== void 0) {
|
|
2920
|
+
const names = Node.isStringLiteral(key) ? [key.getLiteralValue()] : this.propertyNames(key);
|
|
2921
|
+
return this.undeclared(global, names, call.getStartLineNumber(), "writes");
|
|
2922
|
+
}
|
|
2923
|
+
return [];
|
|
2924
|
+
}
|
|
2925
|
+
undeclared(global, names, line, access) {
|
|
2926
|
+
const type = global.getType();
|
|
2927
|
+
return names.filter((name) => type.getProperty(name) === void 0).map((name) => new GlobalChannel(line, `${global.getText()}.${name}`, access));
|
|
2928
|
+
}
|
|
2929
|
+
propertyNames(node) {
|
|
2930
|
+
if (!Node.isObjectLiteralExpression(node)) return [];
|
|
2931
|
+
const names = [];
|
|
2932
|
+
for (const property of node.getProperties()) if (Node.isPropertyAssignment(property) || Node.isShorthandPropertyAssignment(property) || Node.isMethodDeclaration(property)) names.push(property.getName());
|
|
2933
|
+
return names;
|
|
2934
|
+
}
|
|
2935
|
+
globalObject(node) {
|
|
2936
|
+
const bare = this.unwrapped(node);
|
|
2937
|
+
if (!Node.isIdentifier(bare) || !globalObjects.includes(bare.getText())) return;
|
|
2938
|
+
const declaration = bare.getSymbol()?.getDeclarations()[0];
|
|
2939
|
+
return declaration === void 0 || declaration.getSourceFile().isDeclarationFile() ? bare : void 0;
|
|
2940
|
+
}
|
|
2941
|
+
accessOf(access) {
|
|
2942
|
+
const parent = access.getParent();
|
|
2943
|
+
if (Node.isBinaryExpression(parent) && parent.getLeft() === access && assignments.includes(parent.getOperatorToken().getKind())) return "writes";
|
|
2944
|
+
return Node.isDeleteExpression(parent) ? "writes" : "reads";
|
|
2945
|
+
}
|
|
2946
|
+
unwrapped(node) {
|
|
2947
|
+
if (Node.isParenthesizedExpression(node) || Node.isAsExpression(node) || Node.isNonNullExpression(node) || Node.isSatisfiesExpression(node)) return this.unwrapped(node.getExpression());
|
|
2948
|
+
return node;
|
|
2949
|
+
}
|
|
2950
|
+
};
|
|
2951
|
+
//#endregion
|
|
2467
2952
|
//#region src/importer/ts-morph/readers/global-reader.ts
|
|
2468
2953
|
const ecmaScriptLibrary = /^lib\.(es|decorators).*\.d\.ts$/;
|
|
2469
2954
|
var GlobalReader = class {
|
|
@@ -2535,6 +3020,70 @@ var GlobalReader = class {
|
|
|
2535
3020
|
}
|
|
2536
3021
|
};
|
|
2537
3022
|
//#endregion
|
|
3023
|
+
//#region src/importer/ts-morph/readers/module-reach-reader.ts
|
|
3024
|
+
const candidates = [
|
|
3025
|
+
SyntaxKind.Identifier,
|
|
3026
|
+
SyntaxKind.PropertyAccessExpression,
|
|
3027
|
+
SyntaxKind.ElementAccessExpression,
|
|
3028
|
+
SyntaxKind.CallExpression,
|
|
3029
|
+
SyntaxKind.NewExpression
|
|
3030
|
+
];
|
|
3031
|
+
var ModuleReachReader = class {
|
|
3032
|
+
types;
|
|
3033
|
+
scope;
|
|
3034
|
+
constructor(types, scope) {
|
|
3035
|
+
this.types = types;
|
|
3036
|
+
this.scope = scope;
|
|
3037
|
+
}
|
|
3038
|
+
read(file) {
|
|
3039
|
+
if (!this.scope.readsWiring(file.getFilePath())) return [];
|
|
3040
|
+
const reaches = [];
|
|
3041
|
+
file.forEachDescendant((node) => {
|
|
3042
|
+
const reach = this.reachAt(node, file);
|
|
3043
|
+
if (reach !== void 0) reaches.push(reach);
|
|
3044
|
+
});
|
|
3045
|
+
return reaches;
|
|
3046
|
+
}
|
|
3047
|
+
reachAt(node, file) {
|
|
3048
|
+
if (!candidates.includes(node.getKind()) || this.isName(node)) return;
|
|
3049
|
+
const type = node.getType();
|
|
3050
|
+
if (!type.isClass()) return;
|
|
3051
|
+
const module = this.types.classTypeOf(type);
|
|
3052
|
+
if (module.declaredIn === void 0 || module.declaredIn === file.getFilePath() || !this.scope.readsWiring(module.declaredIn)) return;
|
|
3053
|
+
const means = this.meansOf(node);
|
|
3054
|
+
return means === void 0 ? void 0 : new ModuleReach(node.getStartLineNumber(), node.getText(), means, module);
|
|
3055
|
+
}
|
|
3056
|
+
meansOf(node) {
|
|
3057
|
+
const { child, parent } = this.usageOf(node);
|
|
3058
|
+
if (parent === void 0) return;
|
|
3059
|
+
if (Node.isElementAccessExpression(parent) && parent.getExpression() === child) return "brackets";
|
|
3060
|
+
if (Node.isSpreadAssignment(parent) || Node.isSpreadElement(parent)) return "a spread";
|
|
3061
|
+
if (Node.isVariableDeclaration(parent) && !Node.isIdentifier(parent.getNameNode())) return "destructuring";
|
|
3062
|
+
if ((Node.isCallExpression(parent) || Node.isNewExpression(parent)) && parent.getArguments().includes(child)) return this.isModuleConstruction(parent) ? void 0 : parent.getExpression().getText();
|
|
3063
|
+
}
|
|
3064
|
+
usageOf(node) {
|
|
3065
|
+
let child = node;
|
|
3066
|
+
let parent = node.getParent();
|
|
3067
|
+
while (parent !== void 0 && (Node.isParenthesizedExpression(parent) || Node.isAsExpression(parent) || Node.isNonNullExpression(parent) || Node.isSatisfiesExpression(parent))) {
|
|
3068
|
+
child = parent;
|
|
3069
|
+
parent = parent.getParent();
|
|
3070
|
+
}
|
|
3071
|
+
return {
|
|
3072
|
+
child,
|
|
3073
|
+
parent
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
isModuleConstruction(call) {
|
|
3077
|
+
if (!Node.isNewExpression(call)) return false;
|
|
3078
|
+
const declaredIn = call.getType().getSymbol()?.getDeclarations()[0]?.getSourceFile().getFilePath();
|
|
3079
|
+
return declaredIn !== void 0 && this.scope.readsWiring(declaredIn);
|
|
3080
|
+
}
|
|
3081
|
+
isName(node) {
|
|
3082
|
+
const parent = node.getParent();
|
|
3083
|
+
return parent !== void 0 && Node.hasName(parent) && parent.getNameNode() === node;
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
//#endregion
|
|
2538
3087
|
//#region src/importer/ts-morph/readers/statement-reader.ts
|
|
2539
3088
|
const declarative = /* @__PURE__ */ new Set([
|
|
2540
3089
|
SyntaxKind.ImportDeclaration,
|
|
@@ -2624,6 +3173,17 @@ var ThrowReader = class {
|
|
|
2624
3173
|
};
|
|
2625
3174
|
//#endregion
|
|
2626
3175
|
//#region src/importer/ts-morph/readers/type-reader.ts
|
|
3176
|
+
const collections = [
|
|
3177
|
+
"Map",
|
|
3178
|
+
"Set",
|
|
3179
|
+
"WeakMap",
|
|
3180
|
+
"WeakSet"
|
|
3181
|
+
];
|
|
3182
|
+
const readonlyCollections = [
|
|
3183
|
+
"ReadonlyMap",
|
|
3184
|
+
"ReadonlySet",
|
|
3185
|
+
"ReadonlyArray"
|
|
3186
|
+
];
|
|
2627
3187
|
var TypeReader = class {
|
|
2628
3188
|
packageNames;
|
|
2629
3189
|
constructor(packageNames) {
|
|
@@ -2646,6 +3206,24 @@ var TypeReader = class {
|
|
|
2646
3206
|
packageName: void 0
|
|
2647
3207
|
} : this.namedSymbol(symbol);
|
|
2648
3208
|
}
|
|
3209
|
+
holdsCollection(type) {
|
|
3210
|
+
if (type.isArray() || type.isTuple()) return !type.isReadonlyArray();
|
|
3211
|
+
if (collections.includes(type.getSymbol()?.getName() ?? "")) return true;
|
|
3212
|
+
if (type.getStringIndexType() !== void 0 || type.getNumberIndexType() !== void 0) return true;
|
|
3213
|
+
return type.isAnonymous() && type.getCallSignatures().length === 0 && type.getConstructSignatures().length === 0;
|
|
3214
|
+
}
|
|
3215
|
+
opaqueValueIn(type) {
|
|
3216
|
+
return this.findsOpaqueValue(type, /* @__PURE__ */ new Set());
|
|
3217
|
+
}
|
|
3218
|
+
holdsFunction(type) {
|
|
3219
|
+
return this.findsFunction(type, /* @__PURE__ */ new Set());
|
|
3220
|
+
}
|
|
3221
|
+
erasedNameOf(type) {
|
|
3222
|
+
const settled = this.settled(type);
|
|
3223
|
+
if (settled.isAny()) return "any";
|
|
3224
|
+
if (settled.isUnknown()) return "unknown";
|
|
3225
|
+
return (settled.getFlags() & ts.TypeFlags.NonPrimitive) === 0 ? void 0 : "object";
|
|
3226
|
+
}
|
|
2649
3227
|
returnShapeOf(type) {
|
|
2650
3228
|
if (type.getSymbol()?.getName() === "Promise") {
|
|
2651
3229
|
const [inner] = type.getTypeArguments();
|
|
@@ -2658,6 +3236,52 @@ var TypeReader = class {
|
|
|
2658
3236
|
union: members.map((member) => this.namedTypeOf(member))
|
|
2659
3237
|
};
|
|
2660
3238
|
}
|
|
3239
|
+
findsOpaqueValue(type, seen) {
|
|
3240
|
+
if (seen.has(type.compilerType)) return;
|
|
3241
|
+
seen.add(type.compilerType);
|
|
3242
|
+
if (type.isAny() || type.isUnknown()) return type.getText();
|
|
3243
|
+
if (type.isUnion()) return this.firstOpaque(type.getUnionTypes(), seen);
|
|
3244
|
+
if (type.isIntersection()) return this.firstOpaque(type.getIntersectionTypes(), seen);
|
|
3245
|
+
if (!type.isObject() || type.isClass()) return;
|
|
3246
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) return "a function";
|
|
3247
|
+
if (type.isArray() || type.isTuple()) {
|
|
3248
|
+
if (!type.isReadonlyArray()) return "a mutable array";
|
|
3249
|
+
return this.firstOpaque(type.isTuple() ? type.getTupleElements() : type.getTypeArguments(), seen);
|
|
3250
|
+
}
|
|
3251
|
+
if (readonlyCollections.includes(type.getSymbol()?.getName() ?? "")) return this.firstOpaque(type.getTypeArguments(), seen);
|
|
3252
|
+
return "an object";
|
|
3253
|
+
}
|
|
3254
|
+
firstOpaque(types, seen) {
|
|
3255
|
+
for (const inner of types) {
|
|
3256
|
+
const opaque = this.findsOpaqueValue(inner, seen);
|
|
3257
|
+
if (opaque !== void 0) return opaque;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
findsFunction(type, seen) {
|
|
3261
|
+
if (seen.has(type.compilerType)) return false;
|
|
3262
|
+
seen.add(type.compilerType);
|
|
3263
|
+
if (type.isUnion()) return type.getUnionTypes().some((member) => this.findsFunction(member, seen));
|
|
3264
|
+
if (type.isIntersection()) return type.getIntersectionTypes().some((member) => this.findsFunction(member, seen));
|
|
3265
|
+
if (type.isClass()) return false;
|
|
3266
|
+
if (type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0) return true;
|
|
3267
|
+
return this.dataTypesOf(type).some((inner) => this.findsFunction(inner, seen));
|
|
3268
|
+
}
|
|
3269
|
+
dataTypesOf(type) {
|
|
3270
|
+
const inner = [
|
|
3271
|
+
...type.getTypeArguments(),
|
|
3272
|
+
...type.getAliasTypeArguments(),
|
|
3273
|
+
...type.getTupleElements()
|
|
3274
|
+
];
|
|
3275
|
+
if (this.isDeclaredByProject(type)) for (const property of type.getProperties()) {
|
|
3276
|
+
const declaration = property.getDeclarations()[0];
|
|
3277
|
+
if (declaration !== void 0) inner.push(declaration.getType());
|
|
3278
|
+
}
|
|
3279
|
+
return inner;
|
|
3280
|
+
}
|
|
3281
|
+
settled(type) {
|
|
3282
|
+
const [inner] = type.getTypeArguments();
|
|
3283
|
+
return type.getSymbol()?.getName() === "Promise" && inner !== void 0 ? this.settled(inner) : type;
|
|
3284
|
+
}
|
|
2661
3285
|
lineageOf(type) {
|
|
2662
3286
|
const classType = type.getTargetType() ?? type;
|
|
2663
3287
|
const symbol = classType.getSymbol();
|
|
@@ -2710,17 +3334,99 @@ var TypeReader = class {
|
|
|
2710
3334
|
}
|
|
2711
3335
|
};
|
|
2712
3336
|
//#endregion
|
|
3337
|
+
//#region src/importer/ts-morph/readers/wiring-reader.ts
|
|
3338
|
+
const references = [SyntaxKind.Identifier, SyntaxKind.PropertyAccessExpression];
|
|
3339
|
+
var WiringReader = class {
|
|
3340
|
+
types;
|
|
3341
|
+
scope;
|
|
3342
|
+
constructor(types, scope) {
|
|
3343
|
+
this.types = types;
|
|
3344
|
+
this.scope = scope;
|
|
3345
|
+
}
|
|
3346
|
+
read(file) {
|
|
3347
|
+
if (!this.scope.readsWiring(file.getFilePath())) return [];
|
|
3348
|
+
const wirings = [];
|
|
3349
|
+
for (const site of this.sitesOf(file)) {
|
|
3350
|
+
const receiver = this.declaringFileOf(site.receiver);
|
|
3351
|
+
if (receiver === void 0) continue;
|
|
3352
|
+
for (const value of site.values) for (const reference of this.referencesIn(value)) wirings.push(new Wiring(reference.getStartLineNumber(), receiver, this.linksOf(reference)));
|
|
3353
|
+
}
|
|
3354
|
+
return wirings;
|
|
3355
|
+
}
|
|
3356
|
+
sitesOf(file) {
|
|
3357
|
+
const sites = [];
|
|
3358
|
+
file.forEachDescendant((node) => {
|
|
3359
|
+
if (Node.isCallExpression(node) || Node.isNewExpression(node)) sites.push({
|
|
3360
|
+
receiver: node.getExpression(),
|
|
3361
|
+
values: node.getArguments()
|
|
3362
|
+
});
|
|
3363
|
+
else if (Node.isBinaryExpression(node) && node.getOperatorToken().isKind(SyntaxKind.EqualsToken)) sites.push({
|
|
3364
|
+
receiver: node.getLeft(),
|
|
3365
|
+
values: [node.getRight()]
|
|
3366
|
+
});
|
|
3367
|
+
else if ((Node.isVariableDeclaration(node) || Node.isPropertyDeclaration(node)) && node.getTypeNode() !== void 0) {
|
|
3368
|
+
const initializer = node.getInitializer();
|
|
3369
|
+
sites.push({
|
|
3370
|
+
receiver: node.getTypeNode(),
|
|
3371
|
+
values: initializer === void 0 ? [] : [initializer]
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
return sites;
|
|
3376
|
+
}
|
|
3377
|
+
referencesIn(value) {
|
|
3378
|
+
const found = [];
|
|
3379
|
+
const visit = (node) => {
|
|
3380
|
+
if (this.isOutermostReference(node)) found.push(node);
|
|
3381
|
+
node.forEachChild(visit);
|
|
3382
|
+
};
|
|
3383
|
+
visit(value);
|
|
3384
|
+
return found;
|
|
3385
|
+
}
|
|
3386
|
+
isOutermostReference(node) {
|
|
3387
|
+
if (!references.includes(node.getKind())) return false;
|
|
3388
|
+
const parent = node.getParent();
|
|
3389
|
+
if (parent === void 0 || Node.isPropertyAccessExpression(parent)) return false;
|
|
3390
|
+
if (Node.isTypeNode(parent) || Node.isQualifiedName(parent) || Node.isExpressionWithTypeArguments(parent)) return false;
|
|
3391
|
+
return Node.isShorthandPropertyAssignment(parent) || !(Node.hasName(parent) && parent.getNameNode() === node);
|
|
3392
|
+
}
|
|
3393
|
+
linksOf(reference) {
|
|
3394
|
+
if (Node.isPropertyAccessExpression(reference)) return [...this.linksOf(reference.getExpression()), this.link(reference, reference.getNameNode())];
|
|
3395
|
+
if (Node.isIdentifier(reference)) return [this.link(reference, reference)];
|
|
3396
|
+
return [];
|
|
3397
|
+
}
|
|
3398
|
+
link(expression, name) {
|
|
3399
|
+
return {
|
|
3400
|
+
declaredIn: this.declaringFileOf(name),
|
|
3401
|
+
text: expression.getText(),
|
|
3402
|
+
types: this.types.classTypesIn(expression.getType())
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
declaringFileOf(node) {
|
|
3406
|
+
if (node === void 0) return;
|
|
3407
|
+
const named = Node.isTypeNode(node) ? node.getType().getAliasSymbol() ?? node.getType().getSymbol() : this.nameOf(node).getSymbol();
|
|
3408
|
+
const declaration = (named?.isAlias() === true ? named.getAliasedSymbol() : named)?.getDeclarations()[0];
|
|
3409
|
+
return declaration === void 0 ? void 0 : normalize(declaration.getSourceFile().getFilePath());
|
|
3410
|
+
}
|
|
3411
|
+
nameOf(node) {
|
|
3412
|
+
return Node.isPropertyAccessExpression(node) ? node.getNameNode() : node;
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
//#endregion
|
|
2713
3416
|
//#region src/importer/ts-morph/ts-morph-importer.ts
|
|
2714
3417
|
var TsMorphImporter = class TsMorphImporter extends Importer {
|
|
2715
3418
|
sources;
|
|
2716
3419
|
classes;
|
|
3420
|
+
types;
|
|
2717
3421
|
statements = new StatementReader();
|
|
2718
3422
|
disables = new DisableReader();
|
|
2719
3423
|
throws = new ThrowReader();
|
|
3424
|
+
globalChannels = new GlobalChannelReader();
|
|
2720
3425
|
constructor(sources) {
|
|
2721
3426
|
super();
|
|
2722
3427
|
this.sources = sources;
|
|
2723
|
-
this.
|
|
3428
|
+
this.types = new TypeReader(new PackageNames(sources.getFileSystem()));
|
|
3429
|
+
this.classes = new ClassReader(this.types);
|
|
2724
3430
|
}
|
|
2725
3431
|
static fromTsConfig(tsConfigFilePath) {
|
|
2726
3432
|
if (!existsSync(tsConfigFilePath)) throw new Error(`No TypeScript configuration found at ${tsConfigFilePath}: pass --tsconfig, or set tsconfig in alveolus.config.ts.`);
|
|
@@ -2732,22 +3438,33 @@ var TsMorphImporter = class TsMorphImporter extends Importer {
|
|
|
2732
3438
|
}
|
|
2733
3439
|
read(scope) {
|
|
2734
3440
|
const dependencies = new DependencyReader(this.sources, scope);
|
|
3441
|
+
const wirings = new WiringReader(this.types, scope);
|
|
3442
|
+
const reaches = new ModuleReachReader(this.types, scope);
|
|
2735
3443
|
const globals = new GlobalReader(scope.projectDir);
|
|
2736
3444
|
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,
|
|
3445
|
+
for (const file of this.sources.getSourceFiles()) if (this.isInside(scope.rootDir, file.getFilePath()) && !scope.isIgnored(file.getFilePath())) files.push(this.readFile(file, {
|
|
3446
|
+
dependencies,
|
|
3447
|
+
globals,
|
|
3448
|
+
reaches,
|
|
3449
|
+
wirings
|
|
3450
|
+
}));
|
|
2738
3451
|
return new Project$1(scope.projectDir, files);
|
|
2739
3452
|
}
|
|
2740
|
-
readFile(file,
|
|
3453
|
+
readFile(file, readers) {
|
|
3454
|
+
const { dependencies, globals, reaches, wirings } = readers;
|
|
2741
3455
|
const globalReferences = globals.read(file);
|
|
2742
3456
|
return new SourceFile({
|
|
2743
3457
|
classes: file.getClasses().map((declaration) => this.classes.read(declaration)),
|
|
2744
3458
|
dependencies: dependencies.read(file, globalReferences),
|
|
2745
3459
|
disables: this.disables.read(file),
|
|
3460
|
+
globalChannels: this.globalChannels.read(file),
|
|
2746
3461
|
globals: this.globalUsesOf(globalReferences),
|
|
3462
|
+
moduleReaches: reaches.read(file),
|
|
2747
3463
|
path: normalize(file.getFilePath()),
|
|
2748
3464
|
statements: this.statements.read(file),
|
|
2749
3465
|
text: file.getFullText(),
|
|
2750
|
-
throws: this.throws.read(file)
|
|
3466
|
+
throws: this.throws.read(file),
|
|
3467
|
+
wirings: wirings.read(file)
|
|
2751
3468
|
});
|
|
2752
3469
|
}
|
|
2753
3470
|
globalUsesOf(references) {
|
|
@@ -3042,4 +3759,4 @@ var Docs = class Docs {
|
|
|
3042
3759
|
//#endregion
|
|
3043
3760
|
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
3761
|
|
|
3045
|
-
//# sourceMappingURL=docs-
|
|
3762
|
+
//# sourceMappingURL=docs-B6M9DxTM.mjs.map
|