@fairfox/polly 0.16.0 → 0.18.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/tools/verify/src/cli.js +1061 -32
- package/dist/tools/verify/src/cli.js.map +12 -8
- package/dist/tools/verify/src/config.d.ts +6 -1
- package/dist/tools/verify/src/config.js +3 -1
- package/dist/tools/verify/src/config.js.map +4 -4
- package/dist/tools/verify/src/primitives/index.d.ts +19 -0
- package/dist/tools/visualize/src/cli.js +446 -33
- package/dist/tools/visualize/src/cli.js.map +7 -7
- package/package.json +1 -1
|
@@ -39,6 +39,12 @@ class ProjectDetector {
|
|
|
39
39
|
const packageJsonPath = path3.join(this.projectRoot, "package.json");
|
|
40
40
|
if (fs3.existsSync(packageJsonPath)) {
|
|
41
41
|
const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
|
|
42
|
+
if (packageJson.workspaces) {
|
|
43
|
+
const monorepoConfig = this.detectMonorepo(packageJson);
|
|
44
|
+
if (monorepoConfig) {
|
|
45
|
+
return monorepoConfig;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
42
48
|
if (packageJson.dependencies?.electron || packageJson.devDependencies?.electron) {
|
|
43
49
|
return this.detectElectron(packageJson);
|
|
44
50
|
}
|
|
@@ -268,10 +274,11 @@ class ProjectDetector {
|
|
|
268
274
|
}
|
|
269
275
|
};
|
|
270
276
|
}
|
|
271
|
-
scoreServerCandidates(candidates) {
|
|
277
|
+
scoreServerCandidates(candidates, baseDir) {
|
|
278
|
+
const root = baseDir || this.projectRoot;
|
|
272
279
|
const scored = [];
|
|
273
280
|
for (const candidate of candidates) {
|
|
274
|
-
const fullPath = path3.join(
|
|
281
|
+
const fullPath = path3.join(root, candidate);
|
|
275
282
|
if (!fs3.existsSync(fullPath))
|
|
276
283
|
continue;
|
|
277
284
|
try {
|
|
@@ -426,6 +433,135 @@ class ProjectDetector {
|
|
|
426
433
|
}
|
|
427
434
|
return score;
|
|
428
435
|
}
|
|
436
|
+
detectMonorepo(packageJson) {
|
|
437
|
+
const workspacesRaw = packageJson.workspaces;
|
|
438
|
+
const patterns = Array.isArray(workspacesRaw) ? workspacesRaw : Array.isArray(workspacesRaw?.packages) ? workspacesRaw.packages : [];
|
|
439
|
+
if (patterns.length === 0)
|
|
440
|
+
return null;
|
|
441
|
+
const workspaceDirs = this.resolveWorkspaceGlobs(patterns);
|
|
442
|
+
if (workspaceDirs.length === 0)
|
|
443
|
+
return null;
|
|
444
|
+
const entryPoints = {};
|
|
445
|
+
const contextMapping = {};
|
|
446
|
+
const workspacePackages = [];
|
|
447
|
+
for (const wsDir of workspaceDirs) {
|
|
448
|
+
const wsPkgPath = path3.join(wsDir, "package.json");
|
|
449
|
+
if (!fs3.existsSync(wsPkgPath))
|
|
450
|
+
continue;
|
|
451
|
+
let wsPkg;
|
|
452
|
+
try {
|
|
453
|
+
wsPkg = JSON.parse(fs3.readFileSync(wsPkgPath, "utf-8"));
|
|
454
|
+
} catch {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const pkgName = wsPkg.name || path3.basename(wsDir);
|
|
458
|
+
const context = this.inferContextFromPackageName(pkgName);
|
|
459
|
+
const serverCandidates = [
|
|
460
|
+
"src/server.ts",
|
|
461
|
+
"src/server.js",
|
|
462
|
+
"src/index.ts",
|
|
463
|
+
"src/index.js",
|
|
464
|
+
"src/main.ts",
|
|
465
|
+
"src/main.js",
|
|
466
|
+
"src/app.ts",
|
|
467
|
+
"src/app.js",
|
|
468
|
+
"server.ts",
|
|
469
|
+
"server.js",
|
|
470
|
+
"index.ts",
|
|
471
|
+
"index.js"
|
|
472
|
+
];
|
|
473
|
+
const scoredServers = this.scoreServerCandidates(serverCandidates, wsDir);
|
|
474
|
+
if (scoredServers.length > 0) {
|
|
475
|
+
const best = scoredServers[0];
|
|
476
|
+
if (best) {
|
|
477
|
+
entryPoints[context] = best.path;
|
|
478
|
+
if (best.hasWebSocket) {
|
|
479
|
+
contextMapping[context] = "WebSocket Server";
|
|
480
|
+
} else if (best.hasHTTP) {
|
|
481
|
+
contextMapping[context] = "HTTP Server";
|
|
482
|
+
} else {
|
|
483
|
+
contextMapping[context] = this.capitalize(context);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
const commonEntries = [
|
|
488
|
+
"src/index.ts",
|
|
489
|
+
"src/index.tsx",
|
|
490
|
+
"src/main.ts",
|
|
491
|
+
"src/main.tsx",
|
|
492
|
+
"src/app.ts",
|
|
493
|
+
"src/app.tsx",
|
|
494
|
+
"index.ts",
|
|
495
|
+
"index.tsx"
|
|
496
|
+
];
|
|
497
|
+
for (const candidate of commonEntries) {
|
|
498
|
+
const fullPath = path3.join(wsDir, candidate);
|
|
499
|
+
if (fs3.existsSync(fullPath)) {
|
|
500
|
+
entryPoints[context] = fullPath;
|
|
501
|
+
contextMapping[context] = this.capitalize(context);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
workspacePackages.push({ name: pkgName, path: wsDir, context });
|
|
507
|
+
}
|
|
508
|
+
if (Object.keys(entryPoints).length === 0)
|
|
509
|
+
return null;
|
|
510
|
+
return {
|
|
511
|
+
type: "monorepo",
|
|
512
|
+
entryPoints,
|
|
513
|
+
contextMapping,
|
|
514
|
+
workspacePackages,
|
|
515
|
+
metadata: {
|
|
516
|
+
name: packageJson.name,
|
|
517
|
+
version: packageJson.version,
|
|
518
|
+
description: packageJson.description
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
resolveWorkspaceGlobs(patterns) {
|
|
523
|
+
const dirs = [];
|
|
524
|
+
for (const pattern of patterns) {
|
|
525
|
+
if (pattern.includes("*")) {
|
|
526
|
+
const base = pattern.replace(/\/?\*.*$/, "");
|
|
527
|
+
const baseDir = path3.join(this.projectRoot, base);
|
|
528
|
+
if (fs3.existsSync(baseDir) && fs3.statSync(baseDir).isDirectory()) {
|
|
529
|
+
try {
|
|
530
|
+
const entries = fs3.readdirSync(baseDir, { withFileTypes: true });
|
|
531
|
+
for (const entry of entries) {
|
|
532
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
533
|
+
dirs.push(path3.join(baseDir, entry.name));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} catch {}
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
const fullPath = path3.join(this.projectRoot, pattern);
|
|
540
|
+
if (fs3.existsSync(fullPath) && fs3.statSync(fullPath).isDirectory()) {
|
|
541
|
+
dirs.push(fullPath);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return dirs;
|
|
546
|
+
}
|
|
547
|
+
inferContextFromPackageName(name) {
|
|
548
|
+
const parts = name.split("/");
|
|
549
|
+
const baseName = name.includes("/") ? parts[parts.length - 1] || name : name;
|
|
550
|
+
const lower = baseName.toLowerCase();
|
|
551
|
+
const serverAliases = ["api", "server", "backend"];
|
|
552
|
+
if (serverAliases.includes(lower))
|
|
553
|
+
return "server";
|
|
554
|
+
const clientAliases = ["web", "app", "frontend", "client", "ui"];
|
|
555
|
+
if (clientAliases.includes(lower))
|
|
556
|
+
return "client";
|
|
557
|
+
const sharedAliases = ["shared", "common", "lib", "core", "utils"];
|
|
558
|
+
if (sharedAliases.includes(lower))
|
|
559
|
+
return "shared";
|
|
560
|
+
return lower;
|
|
561
|
+
}
|
|
562
|
+
capitalize(str) {
|
|
563
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
564
|
+
}
|
|
429
565
|
detectGenericProject() {
|
|
430
566
|
const entryPoints = {};
|
|
431
567
|
const tsConfigPath = path3.join(this.projectRoot, "tsconfig.json");
|
|
@@ -915,11 +1051,13 @@ import { Node as Node2, Project as Project2 } from "ts-morph";
|
|
|
915
1051
|
class FlowAnalyzer {
|
|
916
1052
|
project;
|
|
917
1053
|
handlers;
|
|
918
|
-
|
|
1054
|
+
contextOverrides;
|
|
1055
|
+
constructor(tsConfigPath, handlers, contextOverrides) {
|
|
919
1056
|
this.project = new Project2({
|
|
920
1057
|
tsConfigFilePath: tsConfigPath
|
|
921
1058
|
});
|
|
922
1059
|
this.handlers = handlers;
|
|
1060
|
+
this.contextOverrides = contextOverrides || new Map;
|
|
923
1061
|
}
|
|
924
1062
|
analyzeFlows() {
|
|
925
1063
|
const flows = [];
|
|
@@ -1155,6 +1293,10 @@ class FlowAnalyzer {
|
|
|
1155
1293
|
return { trigger, flowName, description };
|
|
1156
1294
|
}
|
|
1157
1295
|
inferContext(filePath) {
|
|
1296
|
+
for (const [pathPrefix, context] of this.contextOverrides.entries()) {
|
|
1297
|
+
if (filePath.startsWith(pathPrefix))
|
|
1298
|
+
return context;
|
|
1299
|
+
}
|
|
1158
1300
|
const path2 = filePath.toLowerCase();
|
|
1159
1301
|
const contextPatterns = [
|
|
1160
1302
|
{ context: "background", patterns: ["/background/", "\\background\\"] },
|
|
@@ -1585,7 +1727,8 @@ class HandlerExtractor {
|
|
|
1585
1727
|
packageRoot;
|
|
1586
1728
|
warnings;
|
|
1587
1729
|
currentFunctionParams = [];
|
|
1588
|
-
|
|
1730
|
+
contextOverrides;
|
|
1731
|
+
constructor(tsConfigPath, contextOverrides) {
|
|
1589
1732
|
this.project = new Project3({
|
|
1590
1733
|
tsConfigFilePath: tsConfigPath
|
|
1591
1734
|
});
|
|
@@ -1593,6 +1736,7 @@ class HandlerExtractor {
|
|
|
1593
1736
|
this.relationshipExtractor = new RelationshipExtractor;
|
|
1594
1737
|
this.analyzedFiles = new Set;
|
|
1595
1738
|
this.warnings = [];
|
|
1739
|
+
this.contextOverrides = contextOverrides || new Map;
|
|
1596
1740
|
this.packageRoot = this.findPackageRoot(tsConfigPath);
|
|
1597
1741
|
}
|
|
1598
1742
|
warnUnsupportedPattern(pattern, location, suggestion) {
|
|
@@ -1638,13 +1782,14 @@ class HandlerExtractor {
|
|
|
1638
1782
|
const messageTypes = new Set;
|
|
1639
1783
|
const invalidMessageTypes = new Set;
|
|
1640
1784
|
const stateConstraints = [];
|
|
1785
|
+
const globalStateConstraints = [];
|
|
1641
1786
|
const verifiedStates = [];
|
|
1642
1787
|
this.warnings = [];
|
|
1643
1788
|
const allSourceFiles = this.project.getSourceFiles();
|
|
1644
1789
|
const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
|
|
1645
1790
|
this.debugLogSourceFiles(allSourceFiles, entryPoints);
|
|
1646
1791
|
for (const entryPoint of entryPoints) {
|
|
1647
|
-
this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
|
|
1792
|
+
this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates);
|
|
1648
1793
|
}
|
|
1649
1794
|
if (verifiedStates.length > 0) {
|
|
1650
1795
|
if (process.env["POLLY_DEBUG"]) {
|
|
@@ -1674,11 +1819,12 @@ class HandlerExtractor {
|
|
|
1674
1819
|
handlers,
|
|
1675
1820
|
messageTypes,
|
|
1676
1821
|
stateConstraints,
|
|
1822
|
+
globalStateConstraints,
|
|
1677
1823
|
verifiedStates,
|
|
1678
1824
|
warnings: this.warnings
|
|
1679
1825
|
};
|
|
1680
1826
|
}
|
|
1681
|
-
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates) {
|
|
1827
|
+
analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates) {
|
|
1682
1828
|
const filePath = sourceFile.getFilePath();
|
|
1683
1829
|
if (this.analyzedFiles.has(filePath)) {
|
|
1684
1830
|
return;
|
|
@@ -1692,6 +1838,8 @@ class HandlerExtractor {
|
|
|
1692
1838
|
this.categorizeHandlerMessageTypes(fileHandlers, messageTypes, invalidMessageTypes);
|
|
1693
1839
|
const fileConstraints = this.extractStateConstraintsFromFile(sourceFile);
|
|
1694
1840
|
stateConstraints.push(...fileConstraints);
|
|
1841
|
+
const fileGlobalConstraints = this.extractGlobalStateConstraintsFromFile(sourceFile);
|
|
1842
|
+
globalStateConstraints.push(...fileGlobalConstraints);
|
|
1695
1843
|
const fileVerifiedStates = this.extractVerifiedStatesFromFile(sourceFile);
|
|
1696
1844
|
verifiedStates.push(...fileVerifiedStates);
|
|
1697
1845
|
const importDeclarations = sourceFile.getImportDeclarations();
|
|
@@ -1705,7 +1853,7 @@ class HandlerExtractor {
|
|
|
1705
1853
|
}
|
|
1706
1854
|
continue;
|
|
1707
1855
|
}
|
|
1708
|
-
this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, verifiedStates);
|
|
1856
|
+
this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints, globalStateConstraints, verifiedStates);
|
|
1709
1857
|
} else if (process.env["POLLY_DEBUG"]) {
|
|
1710
1858
|
const specifier = importDecl.getModuleSpecifierValue();
|
|
1711
1859
|
if (!specifier.startsWith("node:") && !this.isNodeModuleImport(specifier)) {
|
|
@@ -1806,8 +1954,20 @@ class HandlerExtractor {
|
|
|
1806
1954
|
handlers.push(handler);
|
|
1807
1955
|
}
|
|
1808
1956
|
}
|
|
1957
|
+
if (methodName === "ws") {
|
|
1958
|
+
this.extractElysiaWsHandlers(node, context, filePath, handlers);
|
|
1959
|
+
}
|
|
1960
|
+
if (this.isRestMethod(methodName) && this.isWebFrameworkFile(node.getSourceFile())) {
|
|
1961
|
+
const restHandler = this.extractRestHandler(node, methodName, context, filePath);
|
|
1962
|
+
if (restHandler) {
|
|
1963
|
+
handlers.push(restHandler);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1809
1966
|
}
|
|
1810
1967
|
}
|
|
1968
|
+
isRestMethod(name) {
|
|
1969
|
+
return ["get", "post", "put", "delete", "patch"].includes(name);
|
|
1970
|
+
}
|
|
1811
1971
|
isElseIfStatement(node) {
|
|
1812
1972
|
const parent = node.getParent();
|
|
1813
1973
|
return parent !== undefined && Node4.isIfStatement(parent);
|
|
@@ -1873,6 +2033,135 @@ class HandlerExtractor {
|
|
|
1873
2033
|
parameters
|
|
1874
2034
|
};
|
|
1875
2035
|
}
|
|
2036
|
+
extractElysiaWsHandlers(node, context, filePath, handlers) {
|
|
2037
|
+
const args = node.getArguments();
|
|
2038
|
+
if (args.length < 2)
|
|
2039
|
+
return;
|
|
2040
|
+
const routeArg = args[0];
|
|
2041
|
+
if (!routeArg || !Node4.isStringLiteral(routeArg))
|
|
2042
|
+
return;
|
|
2043
|
+
const routePath = routeArg.getLiteralValue();
|
|
2044
|
+
const configArg = args[1];
|
|
2045
|
+
if (!configArg || !Node4.isObjectLiteralExpression(configArg))
|
|
2046
|
+
return;
|
|
2047
|
+
const callbacks = ["message", "open", "close"];
|
|
2048
|
+
for (const cbName of callbacks) {
|
|
2049
|
+
const prop = configArg.getProperty(cbName);
|
|
2050
|
+
if (!prop)
|
|
2051
|
+
continue;
|
|
2052
|
+
let funcBody = null;
|
|
2053
|
+
if (Node4.isMethodDeclaration(prop)) {
|
|
2054
|
+
funcBody = prop;
|
|
2055
|
+
} else if (Node4.isPropertyAssignment(prop)) {
|
|
2056
|
+
const init = prop.getInitializer();
|
|
2057
|
+
if (init && (Node4.isArrowFunction(init) || Node4.isFunctionExpression(init))) {
|
|
2058
|
+
funcBody = init;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (!funcBody)
|
|
2062
|
+
continue;
|
|
2063
|
+
if (cbName === "message") {
|
|
2064
|
+
const body = funcBody.getBody();
|
|
2065
|
+
if (!body)
|
|
2066
|
+
continue;
|
|
2067
|
+
const subHandlers = this.extractSubHandlersFromBody(body, context, filePath);
|
|
2068
|
+
if (subHandlers.length > 0) {
|
|
2069
|
+
handlers.push(...subHandlers);
|
|
2070
|
+
} else {
|
|
2071
|
+
handlers.push(this.buildWsHandler(`ws_message`, routePath, context, filePath, funcBody, node.getStartLineNumber()));
|
|
2072
|
+
}
|
|
2073
|
+
} else {
|
|
2074
|
+
handlers.push(this.buildWsHandler(`ws_${cbName}`, routePath, context, filePath, funcBody, node.getStartLineNumber()));
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
extractSubHandlersFromBody(body, context, filePath) {
|
|
2079
|
+
const subHandlers = [];
|
|
2080
|
+
body.forEachDescendant((child) => {
|
|
2081
|
+
if (Node4.isIfStatement(child) && !this.isElseIfStatement(child)) {
|
|
2082
|
+
const typeGuardHandlers = this.extractTypeGuardHandlers(child, context, filePath);
|
|
2083
|
+
subHandlers.push(...typeGuardHandlers);
|
|
2084
|
+
}
|
|
2085
|
+
if (Node4.isSwitchStatement(child)) {
|
|
2086
|
+
const switchHandlers = this.extractSwitchCaseHandlers(child, context, filePath);
|
|
2087
|
+
subHandlers.push(...switchHandlers);
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
return subHandlers;
|
|
2091
|
+
}
|
|
2092
|
+
buildWsHandler(messageType, _routePath, context, filePath, funcBody, line) {
|
|
2093
|
+
const assignments = [];
|
|
2094
|
+
const preconditions = [];
|
|
2095
|
+
const postconditions = [];
|
|
2096
|
+
this.currentFunctionParams = this.extractParameterNames(funcBody);
|
|
2097
|
+
this.extractAssignments(funcBody, assignments);
|
|
2098
|
+
this.extractVerificationConditions(funcBody, preconditions, postconditions);
|
|
2099
|
+
this.currentFunctionParams = [];
|
|
2100
|
+
return {
|
|
2101
|
+
messageType,
|
|
2102
|
+
node: context,
|
|
2103
|
+
assignments,
|
|
2104
|
+
preconditions,
|
|
2105
|
+
postconditions,
|
|
2106
|
+
location: { file: filePath, line },
|
|
2107
|
+
origin: "event"
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
extractRestHandler(node, methodName, context, filePath) {
|
|
2111
|
+
const args = node.getArguments();
|
|
2112
|
+
if (args.length < 2)
|
|
2113
|
+
return null;
|
|
2114
|
+
const routeArg = args[0];
|
|
2115
|
+
if (!routeArg || !Node4.isStringLiteral(routeArg))
|
|
2116
|
+
return null;
|
|
2117
|
+
const routePath = routeArg.getLiteralValue();
|
|
2118
|
+
const httpMethod = methodName.toUpperCase();
|
|
2119
|
+
const messageType = `${httpMethod} ${routePath}`;
|
|
2120
|
+
const handlerArg = args[1];
|
|
2121
|
+
const assignments = [];
|
|
2122
|
+
const preconditions = [];
|
|
2123
|
+
const postconditions = [];
|
|
2124
|
+
let actualHandler = null;
|
|
2125
|
+
if (Node4.isArrowFunction(handlerArg) || Node4.isFunctionExpression(handlerArg)) {
|
|
2126
|
+
actualHandler = handlerArg;
|
|
2127
|
+
} else if (Node4.isIdentifier(handlerArg)) {
|
|
2128
|
+
actualHandler = this.resolveFunctionReference(handlerArg);
|
|
2129
|
+
}
|
|
2130
|
+
let parameters;
|
|
2131
|
+
if (actualHandler) {
|
|
2132
|
+
this.currentFunctionParams = this.extractParameterNames(actualHandler);
|
|
2133
|
+
parameters = this.currentFunctionParams.length > 0 ? [...this.currentFunctionParams] : undefined;
|
|
2134
|
+
this.extractAssignments(actualHandler, assignments);
|
|
2135
|
+
this.extractVerificationConditions(actualHandler, preconditions, postconditions);
|
|
2136
|
+
this.currentFunctionParams = [];
|
|
2137
|
+
}
|
|
2138
|
+
return {
|
|
2139
|
+
messageType,
|
|
2140
|
+
node: context,
|
|
2141
|
+
assignments,
|
|
2142
|
+
preconditions,
|
|
2143
|
+
postconditions,
|
|
2144
|
+
location: {
|
|
2145
|
+
file: filePath,
|
|
2146
|
+
line: node.getStartLineNumber()
|
|
2147
|
+
},
|
|
2148
|
+
origin: "event",
|
|
2149
|
+
parameters,
|
|
2150
|
+
handlerKind: "rest",
|
|
2151
|
+
httpMethod,
|
|
2152
|
+
routePath
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
isWebFrameworkFile(sourceFile) {
|
|
2156
|
+
const frameworks = ["elysia", "express", "hono", "fastify", "koa", "@elysiajs/eden"];
|
|
2157
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
2158
|
+
const specifier = importDecl.getModuleSpecifierValue();
|
|
2159
|
+
if (frameworks.some((fw) => specifier === fw || specifier.startsWith(`${fw}/`))) {
|
|
2160
|
+
return true;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return false;
|
|
2164
|
+
}
|
|
1876
2165
|
extractAssignments(funcNode, assignments) {
|
|
1877
2166
|
funcNode.forEachDescendant((node) => {
|
|
1878
2167
|
if (Node4.isBinaryExpression(node)) {
|
|
@@ -2707,32 +2996,64 @@ class HandlerExtractor {
|
|
|
2707
2996
|
try {
|
|
2708
2997
|
const ifStmt = ifNode;
|
|
2709
2998
|
const condition = ifStmt.getExpression();
|
|
2710
|
-
if (
|
|
2711
|
-
|
|
2999
|
+
if (Node4.isCallExpression(condition)) {
|
|
3000
|
+
const funcExpr = condition.getExpression();
|
|
3001
|
+
const funcName = Node4.isIdentifier(funcExpr) ? funcExpr.getText() : undefined;
|
|
3002
|
+
this.debugLogProcessingFunction(funcName);
|
|
3003
|
+
const messageType = this.resolveMessageType(funcExpr, funcName, typeGuards);
|
|
3004
|
+
if (!messageType) {
|
|
3005
|
+
this.debugLogUnresolvedMessageType(funcName);
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
3008
|
+
const line = ifStmt.getStartLineNumber();
|
|
3009
|
+
const relationships = this.extractRelationshipsFromIfBlock(ifStmt, messageType);
|
|
3010
|
+
return {
|
|
3011
|
+
messageType,
|
|
3012
|
+
node: context,
|
|
3013
|
+
assignments: [],
|
|
3014
|
+
preconditions: [],
|
|
3015
|
+
postconditions: [],
|
|
3016
|
+
location: { file: filePath, line },
|
|
3017
|
+
relationships
|
|
3018
|
+
};
|
|
2712
3019
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
3020
|
+
if (Node4.isBinaryExpression(condition)) {
|
|
3021
|
+
const messageType = this.extractMessageTypeFromEqualityCheck(condition);
|
|
3022
|
+
if (messageType) {
|
|
3023
|
+
const line = ifStmt.getStartLineNumber();
|
|
3024
|
+
const relationships = this.extractRelationshipsFromIfBlock(ifStmt, messageType);
|
|
3025
|
+
return {
|
|
3026
|
+
messageType,
|
|
3027
|
+
node: context,
|
|
3028
|
+
assignments: [],
|
|
3029
|
+
preconditions: [],
|
|
3030
|
+
postconditions: [],
|
|
3031
|
+
location: { file: filePath, line },
|
|
3032
|
+
relationships
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
2720
3035
|
}
|
|
2721
|
-
|
|
2722
|
-
const relationships = this.extractRelationshipsFromIfBlock(ifStmt, messageType);
|
|
2723
|
-
return {
|
|
2724
|
-
messageType,
|
|
2725
|
-
node: context,
|
|
2726
|
-
assignments: [],
|
|
2727
|
-
preconditions: [],
|
|
2728
|
-
postconditions: [],
|
|
2729
|
-
location: { file: filePath, line },
|
|
2730
|
-
relationships
|
|
2731
|
-
};
|
|
3036
|
+
return null;
|
|
2732
3037
|
} catch (_error) {
|
|
2733
3038
|
return null;
|
|
2734
3039
|
}
|
|
2735
3040
|
}
|
|
3041
|
+
extractMessageTypeFromEqualityCheck(expr) {
|
|
3042
|
+
const operator = expr.getOperatorToken().getText();
|
|
3043
|
+
if (operator !== "===" && operator !== "==")
|
|
3044
|
+
return null;
|
|
3045
|
+
const left = expr.getLeft();
|
|
3046
|
+
const right = expr.getRight();
|
|
3047
|
+
const stringLiteral = Node4.isStringLiteral(right) ? right : Node4.isStringLiteral(left) ? left : null;
|
|
3048
|
+
const propAccess = Node4.isPropertyAccessExpression(left) ? left : Node4.isPropertyAccessExpression(right) ? right : null;
|
|
3049
|
+
if (!stringLiteral || !propAccess)
|
|
3050
|
+
return null;
|
|
3051
|
+
const propName = propAccess.getName();
|
|
3052
|
+
if (propName !== "type" && propName !== "kind" && propName !== "action") {
|
|
3053
|
+
return null;
|
|
3054
|
+
}
|
|
3055
|
+
return stringLiteral.getLiteralValue();
|
|
3056
|
+
}
|
|
2736
3057
|
debugLogProcessingFunction(funcName) {
|
|
2737
3058
|
if (process.env["POLLY_DEBUG"] && funcName) {
|
|
2738
3059
|
console.log(`[DEBUG] Processing if condition with function: ${funcName}`);
|
|
@@ -2942,6 +3263,10 @@ class HandlerExtractor {
|
|
|
2942
3263
|
return messageType;
|
|
2943
3264
|
}
|
|
2944
3265
|
inferContext(filePath) {
|
|
3266
|
+
for (const [pathPrefix, context] of this.contextOverrides.entries()) {
|
|
3267
|
+
if (filePath.startsWith(pathPrefix))
|
|
3268
|
+
return context;
|
|
3269
|
+
}
|
|
2945
3270
|
const path2 = filePath.toLowerCase();
|
|
2946
3271
|
return this.inferElectronContext(path2) || this.inferWorkerContext(path2) || this.inferServerAppContext(path2) || this.inferChromeExtensionContext(path2) || "unknown";
|
|
2947
3272
|
}
|
|
@@ -3040,6 +3365,81 @@ class HandlerExtractor {
|
|
|
3040
3365
|
});
|
|
3041
3366
|
return constraints;
|
|
3042
3367
|
}
|
|
3368
|
+
extractGlobalStateConstraintsFromFile(sourceFile) {
|
|
3369
|
+
const constraints = [];
|
|
3370
|
+
const filePath = sourceFile.getFilePath();
|
|
3371
|
+
sourceFile.forEachDescendant((node) => {
|
|
3372
|
+
const constraint = this.recognizeGlobalStateConstraint(node, filePath);
|
|
3373
|
+
if (constraint) {
|
|
3374
|
+
constraints.push(constraint);
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
return constraints;
|
|
3378
|
+
}
|
|
3379
|
+
recognizeGlobalStateConstraint(node, filePath) {
|
|
3380
|
+
if (!Node4.isCallExpression(node)) {
|
|
3381
|
+
return null;
|
|
3382
|
+
}
|
|
3383
|
+
const expression = node.getExpression();
|
|
3384
|
+
if (!Node4.isIdentifier(expression)) {
|
|
3385
|
+
return null;
|
|
3386
|
+
}
|
|
3387
|
+
const functionName = expression.getText();
|
|
3388
|
+
if (functionName !== "stateConstraint") {
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
const args = node.getArguments();
|
|
3392
|
+
if (args.length < 2) {
|
|
3393
|
+
return null;
|
|
3394
|
+
}
|
|
3395
|
+
const nameArg = args[0];
|
|
3396
|
+
if (!Node4.isStringLiteral(nameArg)) {
|
|
3397
|
+
return null;
|
|
3398
|
+
}
|
|
3399
|
+
const name = nameArg.getLiteralValue();
|
|
3400
|
+
const predicateArg = args[1];
|
|
3401
|
+
if (!Node4.isArrowFunction(predicateArg)) {
|
|
3402
|
+
return null;
|
|
3403
|
+
}
|
|
3404
|
+
const body = predicateArg.getBody();
|
|
3405
|
+
let expressionText;
|
|
3406
|
+
if (Node4.isBlock(body)) {
|
|
3407
|
+
const returnStatement = body.getStatements().find((s) => Node4.isReturnStatement(s));
|
|
3408
|
+
if (!returnStatement || !Node4.isReturnStatement(returnStatement)) {
|
|
3409
|
+
return null;
|
|
3410
|
+
}
|
|
3411
|
+
const returnExpr = returnStatement.getExpression();
|
|
3412
|
+
if (!returnExpr) {
|
|
3413
|
+
return null;
|
|
3414
|
+
}
|
|
3415
|
+
expressionText = returnExpr.getText();
|
|
3416
|
+
} else {
|
|
3417
|
+
expressionText = body.getText();
|
|
3418
|
+
}
|
|
3419
|
+
let message;
|
|
3420
|
+
if (args.length >= 3) {
|
|
3421
|
+
const optionsArg = args[2];
|
|
3422
|
+
if (Node4.isObjectLiteralExpression(optionsArg)) {
|
|
3423
|
+
for (const prop of optionsArg.getProperties()) {
|
|
3424
|
+
if (Node4.isPropertyAssignment(prop) && prop.getName() === "message") {
|
|
3425
|
+
const value = prop.getInitializer();
|
|
3426
|
+
if (value && Node4.isStringLiteral(value)) {
|
|
3427
|
+
message = value.getLiteralValue();
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
return {
|
|
3434
|
+
name,
|
|
3435
|
+
expression: expressionText,
|
|
3436
|
+
message,
|
|
3437
|
+
location: {
|
|
3438
|
+
file: filePath,
|
|
3439
|
+
line: node.getStartLineNumber()
|
|
3440
|
+
}
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3043
3443
|
recognizeStateConstraint(node, filePath) {
|
|
3044
3444
|
if (!Node4.isCallExpression(node)) {
|
|
3045
3445
|
return [];
|
|
@@ -3815,10 +4215,16 @@ class ArchitectureAnalyzer {
|
|
|
3815
4215
|
}
|
|
3816
4216
|
async analyze() {
|
|
3817
4217
|
const { manifest, projectConfig, entryPoints, systemInfo } = await this.initializeSystemInfo();
|
|
3818
|
-
const
|
|
4218
|
+
const contextOverrides = new Map;
|
|
4219
|
+
if (projectConfig?.workspacePackages) {
|
|
4220
|
+
for (const pkg of projectConfig.workspacePackages) {
|
|
4221
|
+
contextOverrides.set(pkg.path, pkg.context);
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
const handlerExtractor = new HandlerExtractor(this.options.tsConfigPath, contextOverrides.size > 0 ? contextOverrides : undefined);
|
|
3819
4225
|
const { handlers } = handlerExtractor.extractHandlers();
|
|
3820
4226
|
const contexts = this.analyzeContexts(entryPoints, handlers);
|
|
3821
|
-
const flowAnalyzer = new FlowAnalyzer(this.options.tsConfigPath, handlers);
|
|
4227
|
+
const flowAnalyzer = new FlowAnalyzer(this.options.tsConfigPath, handlers, contextOverrides.size > 0 ? contextOverrides : undefined);
|
|
3822
4228
|
const messageFlows = flowAnalyzer.analyzeFlows();
|
|
3823
4229
|
const integrationAnalyzer = new IntegrationAnalyzer(this.options.tsConfigPath);
|
|
3824
4230
|
const integrations = integrationAnalyzer.analyzeIntegrations();
|
|
@@ -4150,12 +4556,16 @@ class StructurizrDSLGenerator {
|
|
|
4150
4556
|
handlersByType.get(handler.messageType)?.push(handler);
|
|
4151
4557
|
}
|
|
4152
4558
|
for (const [messageType, handlers] of handlersByType) {
|
|
4153
|
-
const componentName = this.toComponentName(messageType);
|
|
4154
4559
|
const firstHandler = handlers[0];
|
|
4155
4560
|
if (!firstHandler)
|
|
4156
4561
|
continue;
|
|
4562
|
+
const isRest = firstHandler.handlerKind === "rest";
|
|
4563
|
+
const componentName = isRest ? messageType : this.toComponentName(messageType);
|
|
4157
4564
|
const description = this.generateComponentDescription(messageType, firstHandler);
|
|
4158
4565
|
const tags = this.getComponentTags(messageType, firstHandler);
|
|
4566
|
+
if (isRest) {
|
|
4567
|
+
tags.push("REST Endpoint");
|
|
4568
|
+
}
|
|
4159
4569
|
const properties = this.getComponentProperties(messageType, firstHandler, contextType);
|
|
4160
4570
|
componentDefs.push({
|
|
4161
4571
|
id: this.toId(componentName),
|
|
@@ -4987,9 +5397,12 @@ class StructurizrDSLGenerator {
|
|
|
4987
5397
|
popup: "Browser Action Popup",
|
|
4988
5398
|
devtools: "DevTools Panel",
|
|
4989
5399
|
options: "Options Page",
|
|
4990
|
-
offscreen: "Offscreen Document"
|
|
5400
|
+
offscreen: "Offscreen Document",
|
|
5401
|
+
server: "Server Application",
|
|
5402
|
+
client: "Client Application",
|
|
5403
|
+
shared: "Shared Library"
|
|
4991
5404
|
};
|
|
4992
|
-
return technologies[contextType] || "
|
|
5405
|
+
return technologies[contextType] || "Application Context";
|
|
4993
5406
|
}
|
|
4994
5407
|
toComponentName(messageType) {
|
|
4995
5408
|
return `${messageType.split("_").map((part) => this.capitalize(part.toLowerCase())).join(" ")} Handler`;
|
|
@@ -5639,4 +6052,4 @@ main().catch((_error) => {
|
|
|
5639
6052
|
process.exit(1);
|
|
5640
6053
|
});
|
|
5641
6054
|
|
|
5642
|
-
//# debugId=
|
|
6055
|
+
//# debugId=345DE519CB00B03D64756E2164756E21
|