@akanjs/language 3.2.0-rc.2 → 3.2.0-rc.3
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/guardResolver.d.ts +2 -0
- package/index.cjs +114 -24
- package/index.js +114 -24
- package/moduleFacts.d.ts +7 -0
- package/package.json +1 -1
- package/server.js +111 -24
package/guardResolver.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ interface Declaration {
|
|
|
33
33
|
name: string;
|
|
34
34
|
}
|
|
35
35
|
export declare const frameworkOwner = "akanjs";
|
|
36
|
+
export declare const frameworkPattern: RegExp;
|
|
37
|
+
export declare const starExportPattern: RegExp;
|
|
36
38
|
export declare class GuardResolver {
|
|
37
39
|
#private;
|
|
38
40
|
constructor(source: AkanGraphSource, routes: RouteFactReader);
|
package/index.cjs
CHANGED
|
@@ -80,6 +80,9 @@ class AkanGraphDiff {
|
|
|
80
80
|
...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
|
|
81
81
|
...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
|
|
82
82
|
];
|
|
83
|
+
const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
|
|
84
|
+
const listed = new Set(edges.map(edgeKeyOf));
|
|
85
|
+
edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
|
|
83
86
|
const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
|
|
84
87
|
const node = headNodes.get(id) ?? baseNodes.get(id);
|
|
85
88
|
return node ? [node] : [];
|
|
@@ -537,7 +540,7 @@ var createFactReader = (ts, ast) => {
|
|
|
537
540
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
538
541
|
return `${ms / size}${unit}`;
|
|
539
542
|
};
|
|
540
|
-
const modesOf = (option) => {
|
|
543
|
+
const modesOf = (option, type) => {
|
|
541
544
|
const object = option && unwrap(option);
|
|
542
545
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
543
546
|
return [];
|
|
@@ -547,9 +550,9 @@ var createFactReader = (ts, ast) => {
|
|
|
547
550
|
if (name === "enabled")
|
|
548
551
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
549
552
|
if (name === "once")
|
|
550
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
553
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
551
554
|
if (name === "lock")
|
|
552
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
555
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
553
556
|
if (name !== "serverMode" && name !== "operationMode")
|
|
554
557
|
return [];
|
|
555
558
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -567,7 +570,8 @@ var createFactReader = (ts, ast) => {
|
|
|
567
570
|
const { root } = chainOf(property.initializer);
|
|
568
571
|
const type = root ? verbOf(root.expression) : null;
|
|
569
572
|
const [first, second] = root?.arguments ?? [];
|
|
570
|
-
const
|
|
573
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
574
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
571
575
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
572
576
|
return [
|
|
573
577
|
{
|
|
@@ -576,7 +580,7 @@ var createFactReader = (ts, ast) => {
|
|
|
576
580
|
extent: extentOf(source, property),
|
|
577
581
|
type,
|
|
578
582
|
schedule,
|
|
579
|
-
modes: modesOf(option)
|
|
583
|
+
modes: modesOf(option, type)
|
|
580
584
|
}
|
|
581
585
|
];
|
|
582
586
|
});
|
|
@@ -593,6 +597,19 @@ var createFactReader = (ts, ast) => {
|
|
|
593
597
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
594
598
|
});
|
|
595
599
|
};
|
|
600
|
+
const adaptorBindingsIn = (source) => {
|
|
601
|
+
const bindings = [];
|
|
602
|
+
const visit = (node) => {
|
|
603
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
604
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
605
|
+
if (role && adaptor)
|
|
606
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
607
|
+
}
|
|
608
|
+
ts.forEachChild(node, visit);
|
|
609
|
+
};
|
|
610
|
+
visit(source);
|
|
611
|
+
return bindings;
|
|
612
|
+
};
|
|
596
613
|
const endpointKeysOf = (source, builder, made, model) => {
|
|
597
614
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
598
615
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
@@ -713,6 +730,7 @@ var createFactReader = (ts, ast) => {
|
|
|
713
730
|
endpoints: [],
|
|
714
731
|
internals: [],
|
|
715
732
|
adaptors: [],
|
|
733
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
716
734
|
stores: []
|
|
717
735
|
};
|
|
718
736
|
for (const statement of source.statements) {
|
|
@@ -737,7 +755,8 @@ var createFactReader = (ts, ast) => {
|
|
|
737
755
|
...name,
|
|
738
756
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
739
757
|
methods: methodsOf(source, statement),
|
|
740
|
-
plugs: plugsOf(source, builder)
|
|
758
|
+
plugs: plugsOf(source, builder),
|
|
759
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
741
760
|
});
|
|
742
761
|
else if (callee === "slice") {
|
|
743
762
|
const model = moduleRefOf(call.arguments[0], "srv");
|
|
@@ -1502,6 +1521,7 @@ class GraphBuilder {
|
|
|
1502
1521
|
#resolver;
|
|
1503
1522
|
#adaptors = new Map;
|
|
1504
1523
|
#plugs = new Map;
|
|
1524
|
+
#serviceFields = new Set;
|
|
1505
1525
|
constructor(source) {
|
|
1506
1526
|
this.#source = source;
|
|
1507
1527
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
@@ -1517,16 +1537,26 @@ class GraphBuilder {
|
|
|
1517
1537
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
1518
1538
|
];
|
|
1519
1539
|
const typed = [];
|
|
1540
|
+
const adaptorCounts = new Map;
|
|
1520
1541
|
for (const file of facts)
|
|
1521
1542
|
for (const fact of file.adaptors) {
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
this.#adaptorNodeOf(declared);
|
|
1543
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1544
|
+
adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
|
|
1525
1545
|
}
|
|
1546
|
+
const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1547
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1548
|
+
const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1549
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1550
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
1551
|
+
this.#adaptorNodeOf(declared);
|
|
1552
|
+
return { declared, file };
|
|
1553
|
+
}));
|
|
1554
|
+
for (const { declared, file } of declaredAdaptors)
|
|
1555
|
+
for (const plug of declared.fact.plugs)
|
|
1556
|
+
this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
1526
1557
|
for (const file of facts)
|
|
1527
|
-
for (const
|
|
1528
|
-
|
|
1529
|
-
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
1558
|
+
for (const binding of file.adaptorBindings)
|
|
1559
|
+
this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
|
|
1530
1560
|
for (const guard of this.#resolver.declare(facts))
|
|
1531
1561
|
this.#guardNodeOf(guard);
|
|
1532
1562
|
for (const file of facts) {
|
|
@@ -1559,10 +1589,18 @@ class GraphBuilder {
|
|
|
1559
1589
|
}
|
|
1560
1590
|
for (const method of service.methods)
|
|
1561
1591
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
1562
|
-
for (const plug of service.plugs)
|
|
1563
|
-
|
|
1592
|
+
for (const plug of service.plugs) {
|
|
1593
|
+
const key = `${refName}
|
|
1594
|
+
${plug.name}`;
|
|
1595
|
+
this.#plugs.set(key, [
|
|
1596
|
+
...this.#plugs.get(key) ?? [],
|
|
1597
|
+
{ owner, target: this.#adaptorIdOf(plug.target, file) }
|
|
1598
|
+
]);
|
|
1599
|
+
}
|
|
1600
|
+
for (const field of service.fields)
|
|
1601
|
+
this.#serviceFields.add(`${owner ?? ""}
|
|
1564
1602
|
${refName}
|
|
1565
|
-
${
|
|
1603
|
+
${field}`);
|
|
1566
1604
|
}
|
|
1567
1605
|
for (const filter of file.filters) {
|
|
1568
1606
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -1821,18 +1859,70 @@ ${dir}`, node);
|
|
|
1821
1859
|
#adaptorIdOf(local, file) {
|
|
1822
1860
|
const [head, member] = local.split(".");
|
|
1823
1861
|
const imported = file.imports.find((item) => item.name === head);
|
|
1824
|
-
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1825
1862
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1863
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1864
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1865
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1866
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1826
1867
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1827
|
-
const found = from === undefined ? declared
|
|
1868
|
+
const found = from === undefined ? this.#adaptorIn(declared, file.owner, path, file.fileName) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : this.#adaptorIn(declared, from, path, file.fileName) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
|
|
1828
1869
|
if (found)
|
|
1829
|
-
return
|
|
1870
|
+
return found.id;
|
|
1871
|
+
const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1872
|
+
if (reexported)
|
|
1873
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1830
1874
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1831
1875
|
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1832
1876
|
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1833
1877
|
}
|
|
1834
|
-
#
|
|
1835
|
-
const
|
|
1878
|
+
#adaptorIn(declared, owner, path, fileName) {
|
|
1879
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1880
|
+
if (own.length < 2)
|
|
1881
|
+
return own.at(0);
|
|
1882
|
+
const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1883
|
+
const named = path === null ? own.find((adaptor) => adaptor.fileName === fileName) : own.find((adaptor) => pathOf(adaptor) === path || pathOf(adaptor) === `${path}/index`) ?? own.find((adaptor) => pathOf(adaptor).startsWith(`${path}/`));
|
|
1884
|
+
return named ?? own.at(0);
|
|
1885
|
+
}
|
|
1886
|
+
#pluggedOf({ owner, module: module2 }, field) {
|
|
1887
|
+
const plugs = this.#plugs.get(`${module2}
|
|
1888
|
+
${field}`) ?? [];
|
|
1889
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1890
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1891
|
+
${module2}
|
|
1892
|
+
${field}`))
|
|
1893
|
+
return [];
|
|
1894
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1895
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1896
|
+
}
|
|
1897
|
+
#packageAdaptorOf(name, specifier) {
|
|
1898
|
+
if (frameworkPattern.test(specifier))
|
|
1899
|
+
return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
|
|
1900
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1901
|
+
return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
|
|
1902
|
+
}
|
|
1903
|
+
#packageShimOf(owner, name, near) {
|
|
1904
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1905
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1906
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1907
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1908
|
+
if (reexport || !near)
|
|
1909
|
+
return null;
|
|
1910
|
+
const packages = new Set;
|
|
1911
|
+
for (const [fileName, words] of this.#resolver.filesIn(owner)) {
|
|
1912
|
+
const relative = this.#source.relative(fileName);
|
|
1913
|
+
const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
|
|
1914
|
+
if (!isNear || !words.includes(`
|
|
1915
|
+
export
|
|
1916
|
+
`))
|
|
1917
|
+
continue;
|
|
1918
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1919
|
+
if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
|
|
1920
|
+
packages.add(specifier);
|
|
1921
|
+
}
|
|
1922
|
+
const [only] = packages;
|
|
1923
|
+
return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
|
|
1924
|
+
}
|
|
1925
|
+
#adaptorNodeOf({ fact, owner, fileName, id }) {
|
|
1836
1926
|
const detail = fact.refName ? [fact.refName] : [];
|
|
1837
1927
|
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1838
1928
|
if (fact.doc && !node.doc)
|
|
@@ -1942,10 +2032,8 @@ st
|
|
|
1942
2032
|
visit(source);
|
|
1943
2033
|
}
|
|
1944
2034
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1945
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1946
|
-
|
|
1947
|
-
${chain[1]}`) : undefined;
|
|
1948
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
2035
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
|
|
2036
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1949
2037
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1950
2038
|
if (!targets.length)
|
|
1951
2039
|
return;
|
|
@@ -2680,6 +2768,8 @@ canPass
|
|
|
2680
2768
|
adapt
|
|
2681
2769
|
`) || words.includes(`
|
|
2682
2770
|
dangerouslyAdapt
|
|
2771
|
+
`) || words.includes(`
|
|
2772
|
+
applyAdaptor
|
|
2683
2773
|
`);
|
|
2684
2774
|
}
|
|
2685
2775
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|
package/index.js
CHANGED
|
@@ -28,6 +28,9 @@ class AkanGraphDiff {
|
|
|
28
28
|
...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
|
|
29
29
|
...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
|
|
30
30
|
];
|
|
31
|
+
const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
|
|
32
|
+
const listed = new Set(edges.map(edgeKeyOf));
|
|
33
|
+
edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
|
|
31
34
|
const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
|
|
32
35
|
const node = headNodes.get(id) ?? baseNodes.get(id);
|
|
33
36
|
return node ? [node] : [];
|
|
@@ -485,7 +488,7 @@ var createFactReader = (ts, ast) => {
|
|
|
485
488
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
486
489
|
return `${ms / size}${unit}`;
|
|
487
490
|
};
|
|
488
|
-
const modesOf = (option) => {
|
|
491
|
+
const modesOf = (option, type) => {
|
|
489
492
|
const object = option && unwrap(option);
|
|
490
493
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
491
494
|
return [];
|
|
@@ -495,9 +498,9 @@ var createFactReader = (ts, ast) => {
|
|
|
495
498
|
if (name === "enabled")
|
|
496
499
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
497
500
|
if (name === "once")
|
|
498
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
501
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
499
502
|
if (name === "lock")
|
|
500
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
503
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
501
504
|
if (name !== "serverMode" && name !== "operationMode")
|
|
502
505
|
return [];
|
|
503
506
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -515,7 +518,8 @@ var createFactReader = (ts, ast) => {
|
|
|
515
518
|
const { root } = chainOf(property.initializer);
|
|
516
519
|
const type = root ? verbOf(root.expression) : null;
|
|
517
520
|
const [first, second] = root?.arguments ?? [];
|
|
518
|
-
const
|
|
521
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
522
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
519
523
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
520
524
|
return [
|
|
521
525
|
{
|
|
@@ -524,7 +528,7 @@ var createFactReader = (ts, ast) => {
|
|
|
524
528
|
extent: extentOf(source, property),
|
|
525
529
|
type,
|
|
526
530
|
schedule,
|
|
527
|
-
modes: modesOf(option)
|
|
531
|
+
modes: modesOf(option, type)
|
|
528
532
|
}
|
|
529
533
|
];
|
|
530
534
|
});
|
|
@@ -541,6 +545,19 @@ var createFactReader = (ts, ast) => {
|
|
|
541
545
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
542
546
|
});
|
|
543
547
|
};
|
|
548
|
+
const adaptorBindingsIn = (source) => {
|
|
549
|
+
const bindings = [];
|
|
550
|
+
const visit = (node) => {
|
|
551
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
552
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
553
|
+
if (role && adaptor)
|
|
554
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
555
|
+
}
|
|
556
|
+
ts.forEachChild(node, visit);
|
|
557
|
+
};
|
|
558
|
+
visit(source);
|
|
559
|
+
return bindings;
|
|
560
|
+
};
|
|
544
561
|
const endpointKeysOf = (source, builder, made, model) => {
|
|
545
562
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
546
563
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
@@ -661,6 +678,7 @@ var createFactReader = (ts, ast) => {
|
|
|
661
678
|
endpoints: [],
|
|
662
679
|
internals: [],
|
|
663
680
|
adaptors: [],
|
|
681
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
664
682
|
stores: []
|
|
665
683
|
};
|
|
666
684
|
for (const statement of source.statements) {
|
|
@@ -685,7 +703,8 @@ var createFactReader = (ts, ast) => {
|
|
|
685
703
|
...name,
|
|
686
704
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
687
705
|
methods: methodsOf(source, statement),
|
|
688
|
-
plugs: plugsOf(source, builder)
|
|
706
|
+
plugs: plugsOf(source, builder),
|
|
707
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
689
708
|
});
|
|
690
709
|
else if (callee === "slice") {
|
|
691
710
|
const model = moduleRefOf(call.arguments[0], "srv");
|
|
@@ -1450,6 +1469,7 @@ class GraphBuilder {
|
|
|
1450
1469
|
#resolver;
|
|
1451
1470
|
#adaptors = new Map;
|
|
1452
1471
|
#plugs = new Map;
|
|
1472
|
+
#serviceFields = new Set;
|
|
1453
1473
|
constructor(source) {
|
|
1454
1474
|
this.#source = source;
|
|
1455
1475
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
@@ -1465,16 +1485,26 @@ class GraphBuilder {
|
|
|
1465
1485
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
1466
1486
|
];
|
|
1467
1487
|
const typed = [];
|
|
1488
|
+
const adaptorCounts = new Map;
|
|
1468
1489
|
for (const file of facts)
|
|
1469
1490
|
for (const fact of file.adaptors) {
|
|
1470
|
-
const
|
|
1471
|
-
|
|
1472
|
-
this.#adaptorNodeOf(declared);
|
|
1491
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1492
|
+
adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
|
|
1473
1493
|
}
|
|
1494
|
+
const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1495
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1496
|
+
const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1497
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1498
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
1499
|
+
this.#adaptorNodeOf(declared);
|
|
1500
|
+
return { declared, file };
|
|
1501
|
+
}));
|
|
1502
|
+
for (const { declared, file } of declaredAdaptors)
|
|
1503
|
+
for (const plug of declared.fact.plugs)
|
|
1504
|
+
this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
1474
1505
|
for (const file of facts)
|
|
1475
|
-
for (const
|
|
1476
|
-
|
|
1477
|
-
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
1506
|
+
for (const binding of file.adaptorBindings)
|
|
1507
|
+
this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
|
|
1478
1508
|
for (const guard of this.#resolver.declare(facts))
|
|
1479
1509
|
this.#guardNodeOf(guard);
|
|
1480
1510
|
for (const file of facts) {
|
|
@@ -1507,10 +1537,18 @@ class GraphBuilder {
|
|
|
1507
1537
|
}
|
|
1508
1538
|
for (const method of service.methods)
|
|
1509
1539
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
1510
|
-
for (const plug of service.plugs)
|
|
1511
|
-
|
|
1540
|
+
for (const plug of service.plugs) {
|
|
1541
|
+
const key = `${refName}
|
|
1542
|
+
${plug.name}`;
|
|
1543
|
+
this.#plugs.set(key, [
|
|
1544
|
+
...this.#plugs.get(key) ?? [],
|
|
1545
|
+
{ owner, target: this.#adaptorIdOf(plug.target, file) }
|
|
1546
|
+
]);
|
|
1547
|
+
}
|
|
1548
|
+
for (const field of service.fields)
|
|
1549
|
+
this.#serviceFields.add(`${owner ?? ""}
|
|
1512
1550
|
${refName}
|
|
1513
|
-
${
|
|
1551
|
+
${field}`);
|
|
1514
1552
|
}
|
|
1515
1553
|
for (const filter of file.filters) {
|
|
1516
1554
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -1769,18 +1807,70 @@ ${dir}`, node);
|
|
|
1769
1807
|
#adaptorIdOf(local, file) {
|
|
1770
1808
|
const [head, member] = local.split(".");
|
|
1771
1809
|
const imported = file.imports.find((item) => item.name === head);
|
|
1772
|
-
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1773
1810
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1811
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1812
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1813
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1814
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1774
1815
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1775
|
-
const found = from === undefined ? declared
|
|
1816
|
+
const found = from === undefined ? this.#adaptorIn(declared, file.owner, path, file.fileName) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : this.#adaptorIn(declared, from, path, file.fileName) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
|
|
1776
1817
|
if (found)
|
|
1777
|
-
return
|
|
1818
|
+
return found.id;
|
|
1819
|
+
const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1820
|
+
if (reexported)
|
|
1821
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1778
1822
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1779
1823
|
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1780
1824
|
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1781
1825
|
}
|
|
1782
|
-
#
|
|
1783
|
-
const
|
|
1826
|
+
#adaptorIn(declared, owner, path, fileName) {
|
|
1827
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1828
|
+
if (own.length < 2)
|
|
1829
|
+
return own.at(0);
|
|
1830
|
+
const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1831
|
+
const named = path === null ? own.find((adaptor) => adaptor.fileName === fileName) : own.find((adaptor) => pathOf(adaptor) === path || pathOf(adaptor) === `${path}/index`) ?? own.find((adaptor) => pathOf(adaptor).startsWith(`${path}/`));
|
|
1832
|
+
return named ?? own.at(0);
|
|
1833
|
+
}
|
|
1834
|
+
#pluggedOf({ owner, module }, field) {
|
|
1835
|
+
const plugs = this.#plugs.get(`${module}
|
|
1836
|
+
${field}`) ?? [];
|
|
1837
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1838
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1839
|
+
${module}
|
|
1840
|
+
${field}`))
|
|
1841
|
+
return [];
|
|
1842
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1843
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1844
|
+
}
|
|
1845
|
+
#packageAdaptorOf(name, specifier) {
|
|
1846
|
+
if (frameworkPattern.test(specifier))
|
|
1847
|
+
return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
|
|
1848
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1849
|
+
return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
|
|
1850
|
+
}
|
|
1851
|
+
#packageShimOf(owner, name, near) {
|
|
1852
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1853
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1854
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1855
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1856
|
+
if (reexport || !near)
|
|
1857
|
+
return null;
|
|
1858
|
+
const packages = new Set;
|
|
1859
|
+
for (const [fileName, words] of this.#resolver.filesIn(owner)) {
|
|
1860
|
+
const relative = this.#source.relative(fileName);
|
|
1861
|
+
const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
|
|
1862
|
+
if (!isNear || !words.includes(`
|
|
1863
|
+
export
|
|
1864
|
+
`))
|
|
1865
|
+
continue;
|
|
1866
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1867
|
+
if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
|
|
1868
|
+
packages.add(specifier);
|
|
1869
|
+
}
|
|
1870
|
+
const [only] = packages;
|
|
1871
|
+
return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
|
|
1872
|
+
}
|
|
1873
|
+
#adaptorNodeOf({ fact, owner, fileName, id }) {
|
|
1784
1874
|
const detail = fact.refName ? [fact.refName] : [];
|
|
1785
1875
|
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1786
1876
|
if (fact.doc && !node.doc)
|
|
@@ -1890,10 +1980,8 @@ st
|
|
|
1890
1980
|
visit(source);
|
|
1891
1981
|
}
|
|
1892
1982
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1893
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1894
|
-
|
|
1895
|
-
${chain[1]}`) : undefined;
|
|
1896
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1983
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
|
|
1984
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1897
1985
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1898
1986
|
if (!targets.length)
|
|
1899
1987
|
return;
|
|
@@ -2628,6 +2716,8 @@ canPass
|
|
|
2628
2716
|
adapt
|
|
2629
2717
|
`) || words.includes(`
|
|
2630
2718
|
dangerouslyAdapt
|
|
2719
|
+
`) || words.includes(`
|
|
2720
|
+
applyAdaptor
|
|
2631
2721
|
`);
|
|
2632
2722
|
}
|
|
2633
2723
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|
package/moduleFacts.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export interface PlugFact extends NamedFact {
|
|
|
63
63
|
export interface ServiceClassFact extends ModuleClassFact {
|
|
64
64
|
methods: MemberFact[];
|
|
65
65
|
plugs: PlugFact[];
|
|
66
|
+
fields: string[];
|
|
66
67
|
}
|
|
67
68
|
export interface InternalKeyFact extends MemberFact {
|
|
68
69
|
type: string | null;
|
|
@@ -77,6 +78,11 @@ export interface AdaptorFact extends MemberFact {
|
|
|
77
78
|
plugs: PlugFact[];
|
|
78
79
|
doc: string | null;
|
|
79
80
|
}
|
|
81
|
+
export interface AdaptorBindingFact {
|
|
82
|
+
role: string;
|
|
83
|
+
adaptor: string;
|
|
84
|
+
span: AkanSpan;
|
|
85
|
+
}
|
|
80
86
|
export interface StoreClassFact extends ModuleClassFact {
|
|
81
87
|
stateKeys: NamedFact[];
|
|
82
88
|
actions: MemberFact[];
|
|
@@ -103,6 +109,7 @@ export interface FileFacts {
|
|
|
103
109
|
endpoints: EndpointClassFact[];
|
|
104
110
|
internals: InternalClassFact[];
|
|
105
111
|
adaptors: AdaptorFact[];
|
|
112
|
+
adaptorBindings: AdaptorBindingFact[];
|
|
106
113
|
stores: StoreClassFact[];
|
|
107
114
|
}
|
|
108
115
|
export declare const testFilePattern: RegExp;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/language",
|
|
3
|
-
"version": "3.2.0-rc.
|
|
3
|
+
"version": "3.2.0-rc.3",
|
|
4
4
|
"description": "Editor-independent Akan.js language core: resolves derived names (slices, queries, CRUD, form setters) back to the source key that produced them.",
|
|
5
5
|
"sourceType": "module",
|
|
6
6
|
"type": "module",
|
package/server.js
CHANGED
|
@@ -384,7 +384,7 @@ var createFactReader = (ts, ast) => {
|
|
|
384
384
|
const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
|
|
385
385
|
return `${ms / size}${unit}`;
|
|
386
386
|
};
|
|
387
|
-
const modesOf = (option) => {
|
|
387
|
+
const modesOf = (option, type) => {
|
|
388
388
|
const object = option && unwrap(option);
|
|
389
389
|
if (!object || !ts.isObjectLiteralExpression(object))
|
|
390
390
|
return [];
|
|
@@ -394,9 +394,9 @@ var createFactReader = (ts, ast) => {
|
|
|
394
394
|
if (name === "enabled")
|
|
395
395
|
return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
|
|
396
396
|
if (name === "once")
|
|
397
|
-
return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
397
|
+
return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
|
|
398
398
|
if (name === "lock")
|
|
399
|
-
return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
399
|
+
return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
|
|
400
400
|
if (name !== "serverMode" && name !== "operationMode")
|
|
401
401
|
return [];
|
|
402
402
|
const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
|
|
@@ -414,7 +414,8 @@ var createFactReader = (ts, ast) => {
|
|
|
414
414
|
const { root } = chainOf(property.initializer);
|
|
415
415
|
const type = root ? verbOf(root.expression) : null;
|
|
416
416
|
const [first, second] = root?.arguments ?? [];
|
|
417
|
-
const
|
|
417
|
+
const cron = type === "cron" && first ? unwrap(first) : null;
|
|
418
|
+
const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
|
|
418
419
|
const option = type === "initialize" || type === "destroy" ? first : second;
|
|
419
420
|
return [
|
|
420
421
|
{
|
|
@@ -423,7 +424,7 @@ var createFactReader = (ts, ast) => {
|
|
|
423
424
|
extent: extentOf(source, property),
|
|
424
425
|
type,
|
|
425
426
|
schedule,
|
|
426
|
-
modes: modesOf(option)
|
|
427
|
+
modes: modesOf(option, type)
|
|
427
428
|
}
|
|
428
429
|
];
|
|
429
430
|
});
|
|
@@ -440,6 +441,19 @@ var createFactReader = (ts, ast) => {
|
|
|
440
441
|
return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
|
|
441
442
|
});
|
|
442
443
|
};
|
|
444
|
+
const adaptorBindingsIn = (source) => {
|
|
445
|
+
const bindings = [];
|
|
446
|
+
const visit = (node) => {
|
|
447
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
|
|
448
|
+
const [role, adaptor] = node.arguments.map(guardNameOf);
|
|
449
|
+
if (role && adaptor)
|
|
450
|
+
bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
|
|
451
|
+
}
|
|
452
|
+
ts.forEachChild(node, visit);
|
|
453
|
+
};
|
|
454
|
+
visit(source);
|
|
455
|
+
return bindings;
|
|
456
|
+
};
|
|
443
457
|
const endpointKeysOf = (source, builder, made, model) => {
|
|
444
458
|
const verbOf = verbReaderOf(builder?.parameters[0]);
|
|
445
459
|
return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
|
|
@@ -560,6 +574,7 @@ var createFactReader = (ts, ast) => {
|
|
|
560
574
|
endpoints: [],
|
|
561
575
|
internals: [],
|
|
562
576
|
adaptors: [],
|
|
577
|
+
adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
|
|
563
578
|
stores: []
|
|
564
579
|
};
|
|
565
580
|
for (const statement of source.statements) {
|
|
@@ -584,7 +599,8 @@ var createFactReader = (ts, ast) => {
|
|
|
584
599
|
...name,
|
|
585
600
|
model: moduleRefOf(call.arguments[0], "db"),
|
|
586
601
|
methods: methodsOf(source, statement),
|
|
587
|
-
plugs: plugsOf(source, builder)
|
|
602
|
+
plugs: plugsOf(source, builder),
|
|
603
|
+
fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
|
|
588
604
|
});
|
|
589
605
|
else if (callee === "slice") {
|
|
590
606
|
const model = moduleRefOf(call.arguments[0], "srv");
|
|
@@ -1349,6 +1365,7 @@ class GraphBuilder {
|
|
|
1349
1365
|
#resolver;
|
|
1350
1366
|
#adaptors = new Map;
|
|
1351
1367
|
#plugs = new Map;
|
|
1368
|
+
#serviceFields = new Set;
|
|
1352
1369
|
constructor(source) {
|
|
1353
1370
|
this.#source = source;
|
|
1354
1371
|
this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
|
|
@@ -1364,16 +1381,26 @@ class GraphBuilder {
|
|
|
1364
1381
|
...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
|
|
1365
1382
|
];
|
|
1366
1383
|
const typed = [];
|
|
1384
|
+
const adaptorCounts = new Map;
|
|
1367
1385
|
for (const file of facts)
|
|
1368
1386
|
for (const fact of file.adaptors) {
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
this.#adaptorNodeOf(declared);
|
|
1387
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1388
|
+
adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
|
|
1372
1389
|
}
|
|
1390
|
+
const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
|
|
1391
|
+
const key = this.#key("adaptor", file.owner, fact.name);
|
|
1392
|
+
const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
|
|
1393
|
+
const declared = { fact, owner: file.owner, fileName: file.fileName, id };
|
|
1394
|
+
this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
|
|
1395
|
+
this.#adaptorNodeOf(declared);
|
|
1396
|
+
return { declared, file };
|
|
1397
|
+
}));
|
|
1398
|
+
for (const { declared, file } of declaredAdaptors)
|
|
1399
|
+
for (const plug of declared.fact.plugs)
|
|
1400
|
+
this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
1373
1401
|
for (const file of facts)
|
|
1374
|
-
for (const
|
|
1375
|
-
|
|
1376
|
-
this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
|
|
1402
|
+
for (const binding of file.adaptorBindings)
|
|
1403
|
+
this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
|
|
1377
1404
|
for (const guard of this.#resolver.declare(facts))
|
|
1378
1405
|
this.#guardNodeOf(guard);
|
|
1379
1406
|
for (const file of facts) {
|
|
@@ -1406,10 +1433,18 @@ class GraphBuilder {
|
|
|
1406
1433
|
}
|
|
1407
1434
|
for (const method of service.methods)
|
|
1408
1435
|
this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
|
|
1409
|
-
for (const plug of service.plugs)
|
|
1410
|
-
|
|
1436
|
+
for (const plug of service.plugs) {
|
|
1437
|
+
const key = `${refName}
|
|
1438
|
+
${plug.name}`;
|
|
1439
|
+
this.#plugs.set(key, [
|
|
1440
|
+
...this.#plugs.get(key) ?? [],
|
|
1441
|
+
{ owner, target: this.#adaptorIdOf(plug.target, file) }
|
|
1442
|
+
]);
|
|
1443
|
+
}
|
|
1444
|
+
for (const field of service.fields)
|
|
1445
|
+
this.#serviceFields.add(`${owner ?? ""}
|
|
1411
1446
|
${refName}
|
|
1412
|
-
${
|
|
1447
|
+
${field}`);
|
|
1413
1448
|
}
|
|
1414
1449
|
for (const filter of file.filters) {
|
|
1415
1450
|
if (!filter.name.endsWith("Filter"))
|
|
@@ -1668,18 +1703,70 @@ ${dir}`, node);
|
|
|
1668
1703
|
#adaptorIdOf(local, file) {
|
|
1669
1704
|
const [head, member] = local.split(".");
|
|
1670
1705
|
const imported = file.imports.find((item) => item.name === head);
|
|
1671
|
-
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1672
1706
|
const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
|
|
1707
|
+
const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
|
|
1708
|
+
if (imported && path === null && !frameworkPattern.test(imported.from))
|
|
1709
|
+
return this.#packageAdaptorOf(name, imported.from);
|
|
1710
|
+
const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
|
|
1673
1711
|
const declared = this.#adaptors.get(name) ?? [];
|
|
1674
|
-
const found = from === undefined ? declared
|
|
1712
|
+
const found = from === undefined ? this.#adaptorIn(declared, file.owner, path, file.fileName) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : this.#adaptorIn(declared, from, path, file.fileName) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
|
|
1675
1713
|
if (found)
|
|
1676
|
-
return
|
|
1714
|
+
return found.id;
|
|
1715
|
+
const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
|
|
1716
|
+
if (reexported)
|
|
1717
|
+
return this.#packageAdaptorOf(reexported.name, reexported.from);
|
|
1677
1718
|
const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
|
|
1678
1719
|
const detail = from === frameworkOwner ? [frameworkOwner] : [];
|
|
1679
1720
|
return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
|
|
1680
1721
|
}
|
|
1681
|
-
#
|
|
1682
|
-
const
|
|
1722
|
+
#adaptorIn(declared, owner, path, fileName) {
|
|
1723
|
+
const own = declared.filter((adaptor) => adaptor.owner === owner);
|
|
1724
|
+
if (own.length < 2)
|
|
1725
|
+
return own.at(0);
|
|
1726
|
+
const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
|
|
1727
|
+
const named = path === null ? own.find((adaptor) => adaptor.fileName === fileName) : own.find((adaptor) => pathOf(adaptor) === path || pathOf(adaptor) === `${path}/index`) ?? own.find((adaptor) => pathOf(adaptor).startsWith(`${path}/`));
|
|
1728
|
+
return named ?? own.at(0);
|
|
1729
|
+
}
|
|
1730
|
+
#pluggedOf({ owner, module }, field) {
|
|
1731
|
+
const plugs = this.#plugs.get(`${module}
|
|
1732
|
+
${field}`) ?? [];
|
|
1733
|
+
const own = plugs.filter((plug) => plug.owner === owner);
|
|
1734
|
+
if (!own.length && this.#serviceFields.has(`${owner ?? ""}
|
|
1735
|
+
${module}
|
|
1736
|
+
${field}`))
|
|
1737
|
+
return [];
|
|
1738
|
+
const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
|
|
1739
|
+
return [...new Set(inherited.map(({ target }) => target))];
|
|
1740
|
+
}
|
|
1741
|
+
#packageAdaptorOf(name, specifier) {
|
|
1742
|
+
if (frameworkPattern.test(specifier))
|
|
1743
|
+
return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
|
|
1744
|
+
const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
1745
|
+
return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
|
|
1746
|
+
}
|
|
1747
|
+
#packageShimOf(owner, name, near) {
|
|
1748
|
+
const reexport = this.#resolver.reexportOf(owner, name, near);
|
|
1749
|
+
const [shim] = reexport?.site.imports ?? [];
|
|
1750
|
+
if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
|
|
1751
|
+
return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
|
|
1752
|
+
if (reexport || !near)
|
|
1753
|
+
return null;
|
|
1754
|
+
const packages = new Set;
|
|
1755
|
+
for (const [fileName, words] of this.#resolver.filesIn(owner)) {
|
|
1756
|
+
const relative = this.#source.relative(fileName);
|
|
1757
|
+
const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
|
|
1758
|
+
if (!isNear || !words.includes(`
|
|
1759
|
+
export
|
|
1760
|
+
`))
|
|
1761
|
+
continue;
|
|
1762
|
+
for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
|
|
1763
|
+
if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
|
|
1764
|
+
packages.add(specifier);
|
|
1765
|
+
}
|
|
1766
|
+
const [only] = packages;
|
|
1767
|
+
return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
|
|
1768
|
+
}
|
|
1769
|
+
#adaptorNodeOf({ fact, owner, fileName, id }) {
|
|
1683
1770
|
const detail = fact.refName ? [fact.refName] : [];
|
|
1684
1771
|
const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
|
|
1685
1772
|
if (fact.doc && !node.doc)
|
|
@@ -1789,10 +1876,8 @@ st
|
|
|
1789
1876
|
visit(source);
|
|
1790
1877
|
}
|
|
1791
1878
|
#link(fileName, source, identifier, chain, context, owner) {
|
|
1792
|
-
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#
|
|
1793
|
-
|
|
1794
|
-
${chain[1]}`) : undefined;
|
|
1795
|
-
const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1879
|
+
const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
|
|
1880
|
+
const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
|
|
1796
1881
|
const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
|
|
1797
1882
|
if (!targets.length)
|
|
1798
1883
|
return;
|
|
@@ -2527,6 +2612,8 @@ canPass
|
|
|
2527
2612
|
adapt
|
|
2528
2613
|
`) || words.includes(`
|
|
2529
2614
|
dangerouslyAdapt
|
|
2615
|
+
`) || words.includes(`
|
|
2616
|
+
applyAdaptor
|
|
2530
2617
|
`);
|
|
2531
2618
|
}
|
|
2532
2619
|
static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
|