@lucasacoutinho/archer 0.2.1 → 0.3.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/README.md +144 -387
- package/dist/analyzer/Analyzer.d.ts +17 -0
- package/dist/analyzer/Analyzer.d.ts.map +1 -0
- package/dist/analyzer/Analyzer.js +15 -0
- package/dist/analyzer/Analyzer.js.map +1 -0
- package/dist/analyzer/bindings.d.ts +26 -0
- package/dist/analyzer/bindings.d.ts.map +1 -0
- package/dist/analyzer/bindings.js +61 -0
- package/dist/analyzer/bindings.js.map +1 -0
- package/dist/analyzer/errors.d.ts +34 -0
- package/dist/analyzer/errors.d.ts.map +1 -0
- package/dist/analyzer/errors.js +20 -0
- package/dist/analyzer/errors.js.map +1 -0
- package/dist/analyzer/graph-file.d.ts +13 -0
- package/dist/analyzer/graph-file.d.ts.map +1 -0
- package/dist/analyzer/graph-file.js +22 -0
- package/dist/analyzer/graph-file.js.map +1 -0
- package/dist/analyzer/paths.d.ts +12 -0
- package/dist/analyzer/paths.d.ts.map +1 -0
- package/dist/analyzer/paths.js +26 -0
- package/dist/analyzer/paths.js.map +1 -0
- package/dist/analyzer/server-exit.d.ts +19 -0
- package/dist/analyzer/server-exit.d.ts.map +1 -0
- package/dist/analyzer/server-exit.js +70 -0
- package/dist/analyzer/server-exit.js.map +1 -0
- package/dist/analyzer/source.d.ts +29 -0
- package/dist/analyzer/source.d.ts.map +1 -0
- package/dist/analyzer/source.js +58 -0
- package/dist/analyzer/source.js.map +1 -0
- package/dist/analyzer/syntax.d.ts +29 -0
- package/dist/analyzer/syntax.d.ts.map +1 -0
- package/dist/analyzer/syntax.js +226 -0
- package/dist/analyzer/syntax.js.map +1 -0
- package/dist/analyzer/targets.d.ts +26 -0
- package/dist/analyzer/targets.d.ts.map +1 -0
- package/dist/analyzer/targets.js +84 -0
- package/dist/analyzer/targets.js.map +1 -0
- package/dist/analyzer/typescript.d.ts +5 -0
- package/dist/analyzer/typescript.d.ts.map +1 -0
- package/dist/analyzer/typescript.js +221 -0
- package/dist/analyzer/typescript.js.map +1 -0
- package/dist/analyzer/workspace.d.ts +24 -0
- package/dist/analyzer/workspace.d.ts.map +1 -0
- package/dist/analyzer/workspace.js +9 -0
- package/dist/analyzer/workspace.js.map +1 -0
- package/dist/cli/explain.d.ts +3 -0
- package/dist/cli/explain.d.ts.map +1 -0
- package/dist/cli/explain.js +21 -0
- package/dist/cli/explain.js.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +159 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/config/input.d.ts +59 -0
- package/dist/config/input.d.ts.map +1 -0
- package/dist/config/input.js +115 -0
- package/dist/config/input.js.map +1 -0
- package/dist/config/rules.d.ts +94 -0
- package/dist/config/rules.d.ts.map +1 -0
- package/dist/config/rules.js +130 -0
- package/dist/config/rules.js.map +1 -0
- package/dist/core/baseline.d.ts +16 -0
- package/dist/core/baseline.d.ts.map +1 -0
- package/dist/core/baseline.js +25 -0
- package/dist/core/baseline.js.map +1 -0
- package/dist/core/check.d.ts +16 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +271 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/config.d.ts +72 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +78 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/fingerprint.d.ts +54 -0
- package/dist/core/fingerprint.d.ts.map +1 -0
- package/dist/core/fingerprint.js +182 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/keys.d.ts +10 -0
- package/dist/core/keys.d.ts.map +1 -0
- package/dist/core/keys.js +16 -0
- package/dist/core/keys.js.map +1 -0
- package/dist/core/lower.d.ts +13 -0
- package/dist/core/lower.d.ts.map +1 -0
- package/dist/core/lower.js +82 -0
- package/dist/core/lower.js.map +1 -0
- package/dist/core/model.d.ts +155 -0
- package/dist/core/model.d.ts.map +1 -0
- package/dist/core/model.js +116 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/pattern.d.ts +13 -0
- package/dist/core/pattern.d.ts.map +1 -0
- package/dist/core/pattern.js +29 -0
- package/dist/core/pattern.js.map +1 -0
- package/dist/core/reach.d.ts +10 -0
- package/dist/core/reach.d.ts.map +1 -0
- package/dist/core/reach.js +81 -0
- package/dist/core/reach.js.map +1 -0
- package/dist/core/rule.d.ts +200 -0
- package/dist/core/rule.d.ts.map +1 -0
- package/dist/core/rule.js +162 -0
- package/dist/core/rule.js.map +1 -0
- package/dist/core/selector.d.ts +7 -0
- package/dist/core/selector.d.ts.map +1 -0
- package/dist/core/selector.js +27 -0
- package/dist/core/selector.js.map +1 -0
- package/dist/core/structural.d.ts +10 -0
- package/dist/core/structural.d.ts.map +1 -0
- package/dist/core/structural.js +24 -0
- package/dist/core/structural.js.map +1 -0
- package/dist/core/text.d.ts +26 -0
- package/dist/core/text.d.ts.map +1 -0
- package/dist/core/text.js +144 -0
- package/dist/core/text.js.map +1 -0
- package/dist/effect.d.ts +19 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +21 -0
- package/dist/effect.js.map +1 -0
- package/dist/index.d.ts +24 -44
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -52
- package/dist/index.js.map +1 -0
- package/dist/report/agent.d.ts +3 -0
- package/dist/report/agent.d.ts.map +1 -0
- package/dist/report/agent.js +35 -0
- package/dist/report/agent.js.map +1 -0
- package/dist/report/dot.d.ts +4 -0
- package/dist/report/dot.d.ts.map +1 -0
- package/dist/report/dot.js +15 -0
- package/dist/report/dot.js.map +1 -0
- package/dist/report/github.d.ts +3 -0
- package/dist/report/github.d.ts.map +1 -0
- package/dist/report/github.js +18 -0
- package/dist/report/github.js.map +1 -0
- package/dist/report/json.d.ts +3 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +4 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/message.d.ts +20 -0
- package/dist/report/message.d.ts.map +1 -0
- package/dist/report/message.js +73 -0
- package/dist/report/message.js.map +1 -0
- package/dist/report/pretty.d.ts +8 -0
- package/dist/report/pretty.d.ts.map +1 -0
- package/dist/report/pretty.js +89 -0
- package/dist/report/pretty.js.map +1 -0
- package/dist/report/render.d.ts +9 -0
- package/dist/report/render.d.ts.map +1 -0
- package/dist/report/render.js +10 -0
- package/dist/report/render.js.map +1 -0
- package/dist/report/report.d.ts +44 -0
- package/dist/report/report.d.ts.map +1 -0
- package/dist/report/report.js +49 -0
- package/dist/report/report.js.map +1 -0
- package/dist/report/sarif.d.ts +3 -0
- package/dist/report/sarif.d.ts.map +1 -0
- package/dist/report/sarif.js +68 -0
- package/dist/report/sarif.js.map +1 -0
- package/dist/run.d.ts +49 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +87 -0
- package/dist/run.js.map +1 -0
- package/dist/shell/baseline-file.d.ts +20 -0
- package/dist/shell/baseline-file.d.ts.map +1 -0
- package/dist/shell/baseline-file.js +46 -0
- package/dist/shell/baseline-file.js.map +1 -0
- package/dist/shell/load-config.d.ts +19 -0
- package/dist/shell/load-config.d.ts.map +1 -0
- package/dist/shell/load-config.js +39 -0
- package/dist/shell/load-config.js.map +1 -0
- package/package.json +29 -177
- package/skills/archer/SKILL.md +127 -45
- package/skills/archer/agents/openai.yaml +2 -2
- package/skills/archer/references/rules.md +267 -54
- package/dist/bun/plugin.cjs +0 -85
- package/dist/bun/plugin.d.cts +0 -7
- package/dist/bun/plugin.d.ts +0 -7
- package/dist/bun/plugin.js +0 -11
- package/dist/bun.cjs +0 -2238
- package/dist/bun.d.cts +0 -13
- package/dist/bun.d.ts +0 -13
- package/dist/bun.js +0 -28
- package/dist/chunk-2AKMBLHG.js +0 -2165
- package/dist/chunk-67YEOTXP.js +0 -25
- package/dist/chunk-IDVLHNL2.js +0 -46
- package/dist/chunk-ZZ6TR4PO.js +0 -201
- package/dist/cli.js +0 -2643
- package/dist/index.cjs +0 -2484
- package/dist/index.d.cts +0 -45
- package/dist/jest/plugin.cjs +0 -85
- package/dist/jest/plugin.d.cts +0 -7
- package/dist/jest/plugin.d.ts +0 -7
- package/dist/jest/plugin.js +0 -11
- package/dist/jest.cjs +0 -2237
- package/dist/jest.d.cts +0 -13
- package/dist/jest.d.ts +0 -13
- package/dist/jest.js +0 -27
- package/dist/matchers-Cqnbsyxe.d.cts +0 -11
- package/dist/matchers-Cqnbsyxe.d.ts +0 -11
- package/dist/node.cjs +0 -2237
- package/dist/node.d.cts +0 -13
- package/dist/node.d.ts +0 -13
- package/dist/node.js +0 -27
- package/dist/reporters-BbAXMpuW.d.cts +0 -6
- package/dist/reporters-xh-HQneS.d.ts +0 -6
- package/dist/suite-BwnPDCvU.d.cts +0 -337
- package/dist/suite-BwnPDCvU.d.ts +0 -337
- package/dist/vitest/plugin.cjs +0 -85
- package/dist/vitest/plugin.d.cts +0 -7
- package/dist/vitest/plugin.d.ts +0 -7
- package/dist/vitest/plugin.js +0 -11
- package/dist/vitest.cjs +0 -2531
- package/dist/vitest.d.cts +0 -37
- package/dist/vitest.d.ts +0 -37
- package/dist/vitest.js +0 -139
package/dist/index.cjs
DELETED
|
@@ -1,2484 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var src_exports = {};
|
|
32
|
-
__export(src_exports, {
|
|
33
|
-
ArchExpectation: () => ArchExpectation,
|
|
34
|
-
ArchitectureError: () => ArchitectureError,
|
|
35
|
-
ArchitectureLayers: () => ArchitectureLayers,
|
|
36
|
-
ArchitectureSuite: () => ArchitectureSuite,
|
|
37
|
-
LayerExpectation: () => LayerExpectation,
|
|
38
|
-
analyze: () => analyze,
|
|
39
|
-
applyBaseline: () => applyBaseline,
|
|
40
|
-
applyPreset: () => applyPreset,
|
|
41
|
-
archerMatchers: () => archerMatchers,
|
|
42
|
-
availablePresets: () => availablePresets,
|
|
43
|
-
clearAnalysisCache: () => clearAnalysisCache,
|
|
44
|
-
createArcher: () => createArcher,
|
|
45
|
-
createBaseline: () => createBaseline,
|
|
46
|
-
defineConfig: () => defineConfig,
|
|
47
|
-
formatResult: () => formatResult,
|
|
48
|
-
readBaseline: () => readBaseline,
|
|
49
|
-
registerPreset: () => registerPreset,
|
|
50
|
-
report: () => report,
|
|
51
|
-
violationFingerprint: () => violationFingerprint,
|
|
52
|
-
writeBaseline: () => writeBaseline
|
|
53
|
-
});
|
|
54
|
-
module.exports = __toCommonJS(src_exports);
|
|
55
|
-
|
|
56
|
-
// src/analysis/analyzer.ts
|
|
57
|
-
var import_promises2 = require("fs/promises");
|
|
58
|
-
var import_node_path3 = __toESM(require("path"), 1);
|
|
59
|
-
var import_tinyglobby2 = require("tinyglobby");
|
|
60
|
-
|
|
61
|
-
// src/analysis/projects.ts
|
|
62
|
-
var import_node_fs = require("fs");
|
|
63
|
-
var import_node_module = require("module");
|
|
64
|
-
var import_node_path = __toESM(require("path"), 1);
|
|
65
|
-
var import_tinyglobby = require("tinyglobby");
|
|
66
|
-
var import_typescript = __toESM(require("typescript"), 1);
|
|
67
|
-
function normalize(value) {
|
|
68
|
-
return value.replaceAll("\\", "/");
|
|
69
|
-
}
|
|
70
|
-
function canonical(value) {
|
|
71
|
-
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
72
|
-
}
|
|
73
|
-
function defaultCompilerOptions() {
|
|
74
|
-
return {
|
|
75
|
-
allowJs: true,
|
|
76
|
-
module: import_typescript.default.ModuleKind.NodeNext,
|
|
77
|
-
moduleResolution: import_typescript.default.ModuleResolutionKind.NodeNext
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
function configPathFrom(value, root) {
|
|
81
|
-
const resolved = import_node_path.default.resolve(root, value);
|
|
82
|
-
if (import_node_path.default.extname(resolved).toLowerCase() === ".json") {
|
|
83
|
-
return resolved;
|
|
84
|
-
}
|
|
85
|
-
return import_node_path.default.join(resolved, "tsconfig.json");
|
|
86
|
-
}
|
|
87
|
-
function parseProject(configPath) {
|
|
88
|
-
const readResult = import_typescript.default.readConfigFile(configPath, import_typescript.default.sys.readFile);
|
|
89
|
-
if (readResult.error !== void 0) {
|
|
90
|
-
throw new Error(import_typescript.default.flattenDiagnosticMessageText(readResult.error.messageText, "\n"));
|
|
91
|
-
}
|
|
92
|
-
const root = import_node_path.default.dirname(configPath);
|
|
93
|
-
const parsed = import_typescript.default.parseJsonConfigFileContent(
|
|
94
|
-
readResult.config,
|
|
95
|
-
import_typescript.default.sys,
|
|
96
|
-
root,
|
|
97
|
-
void 0,
|
|
98
|
-
configPath
|
|
99
|
-
);
|
|
100
|
-
if (parsed.errors.length > 0) {
|
|
101
|
-
const errors = parsed.errors.map(
|
|
102
|
-
(error) => import_typescript.default.flattenDiagnosticMessageText(error.messageText, "\n")
|
|
103
|
-
);
|
|
104
|
-
throw new Error(`Could not read ${configPath}:
|
|
105
|
-
${errors.join("\n")}`);
|
|
106
|
-
}
|
|
107
|
-
const compilerOptions = { ...parsed.options, allowJs: true };
|
|
108
|
-
const program = import_typescript.default.createProgram({
|
|
109
|
-
options: compilerOptions,
|
|
110
|
-
...parsed.projectReferences === void 0 ? {} : { projectReferences: parsed.projectReferences },
|
|
111
|
-
rootNames: parsed.fileNames
|
|
112
|
-
});
|
|
113
|
-
return {
|
|
114
|
-
compilerOptions,
|
|
115
|
-
configPath,
|
|
116
|
-
files: new Set(parsed.fileNames.map((file) => canonical(import_node_path.default.resolve(file)))),
|
|
117
|
-
info: {
|
|
118
|
-
name: normalize(import_node_path.default.basename(root)),
|
|
119
|
-
root: normalize(root),
|
|
120
|
-
tsconfig: normalize(configPath)
|
|
121
|
-
},
|
|
122
|
-
moduleResolutionCache: import_typescript.default.createModuleResolutionCache(root, canonical, compilerOptions),
|
|
123
|
-
program,
|
|
124
|
-
root
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function fallbackProject(root) {
|
|
128
|
-
const compilerOptions = defaultCompilerOptions();
|
|
129
|
-
const configPath = import_node_path.default.join(root, "tsconfig.json");
|
|
130
|
-
return {
|
|
131
|
-
compilerOptions,
|
|
132
|
-
configPath,
|
|
133
|
-
files: /* @__PURE__ */ new Set(),
|
|
134
|
-
info: { name: import_node_path.default.basename(root), root: normalize(root), tsconfig: normalize(configPath) },
|
|
135
|
-
moduleResolutionCache: import_typescript.default.createModuleResolutionCache(root, canonical, compilerOptions),
|
|
136
|
-
program: import_typescript.default.createProgram({ options: compilerOptions, rootNames: [] }),
|
|
137
|
-
root
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function referencedConfigs(project) {
|
|
141
|
-
return (project.program.getResolvedProjectReferences() ?? []).flatMap((reference) => {
|
|
142
|
-
if (reference === void 0) {
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
return [reference.sourceFile.fileName];
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
function readWorkspacePatterns(root) {
|
|
149
|
-
const packagePath = import_node_path.default.join(root, "package.json");
|
|
150
|
-
const patterns = [];
|
|
151
|
-
if ((0, import_node_fs.existsSync)(packagePath)) {
|
|
152
|
-
const packageJson = JSON.parse((0, import_node_fs.readFileSync)(packagePath, "utf8"));
|
|
153
|
-
if (Array.isArray(packageJson.workspaces)) {
|
|
154
|
-
patterns.push(...packageJson.workspaces);
|
|
155
|
-
} else {
|
|
156
|
-
patterns.push(
|
|
157
|
-
...packageJson.workspaces?.packages ?? []
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const pnpmWorkspace = import_node_path.default.join(root, "pnpm-workspace.yaml");
|
|
162
|
-
if ((0, import_node_fs.existsSync)(pnpmWorkspace)) {
|
|
163
|
-
const yaml = (0, import_node_fs.readFileSync)(pnpmWorkspace, "utf8");
|
|
164
|
-
const packageBlock = yaml.match(/^packages:\s*\n((?:^[ \t]+-.*\n?)*)/mu)?.[1] ?? "";
|
|
165
|
-
for (const match of packageBlock.matchAll(/^[ \t]+-[ \t]+["']?([^"'\n]+?)["']?[ \t]*$/gmu)) {
|
|
166
|
-
const pattern = match[1]?.trim();
|
|
167
|
-
if (pattern !== void 0) patterns.push(pattern);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return [...new Set(patterns)];
|
|
171
|
-
}
|
|
172
|
-
async function workspaceConfigPaths(root) {
|
|
173
|
-
const patterns = readWorkspacePatterns(root);
|
|
174
|
-
if (patterns.length === 0) {
|
|
175
|
-
return [];
|
|
176
|
-
}
|
|
177
|
-
return (0, import_tinyglobby.glob)(
|
|
178
|
-
patterns.map((pattern) => `${pattern}/tsconfig.json`),
|
|
179
|
-
{
|
|
180
|
-
absolute: true,
|
|
181
|
-
cwd: root,
|
|
182
|
-
onlyFiles: true
|
|
183
|
-
}
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
async function discoverProjects(root, options) {
|
|
187
|
-
if (options.tsconfig === false && options.projects === void 0) {
|
|
188
|
-
return [fallbackProject(root)];
|
|
189
|
-
}
|
|
190
|
-
const requested = options.projects?.map(
|
|
191
|
-
(project) => typeof project === "string" ? { tsconfig: project } : project
|
|
192
|
-
) ?? [];
|
|
193
|
-
const primaryPath = configPathFrom(
|
|
194
|
-
options.tsconfig === false ? "tsconfig.json" : options.tsconfig ?? "tsconfig.json",
|
|
195
|
-
root
|
|
196
|
-
);
|
|
197
|
-
const initialPaths = /* @__PURE__ */ new Set();
|
|
198
|
-
if ((0, import_node_fs.existsSync)(primaryPath)) {
|
|
199
|
-
initialPaths.add(primaryPath);
|
|
200
|
-
}
|
|
201
|
-
const requestedMatches = await Promise.all(
|
|
202
|
-
requested.map(async (project) => ({
|
|
203
|
-
matches: await (0, import_tinyglobby.glob)(project.tsconfig, { absolute: true, cwd: root, onlyFiles: true }),
|
|
204
|
-
project
|
|
205
|
-
}))
|
|
206
|
-
);
|
|
207
|
-
for (const { matches, project } of requestedMatches) {
|
|
208
|
-
if (matches.length === 0) {
|
|
209
|
-
initialPaths.add(configPathFrom(project.tsconfig, root));
|
|
210
|
-
} else {
|
|
211
|
-
matches.forEach((match) => initialPaths.add(import_node_path.default.resolve(match)));
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (options.workspaces !== false) {
|
|
215
|
-
for (const configPath of await workspaceConfigPaths(root)) {
|
|
216
|
-
initialPaths.add(import_node_path.default.resolve(configPath));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (initialPaths.size === 0) {
|
|
220
|
-
return [fallbackProject(root)];
|
|
221
|
-
}
|
|
222
|
-
const projects = /* @__PURE__ */ new Map();
|
|
223
|
-
const queue = [...initialPaths];
|
|
224
|
-
while (queue.length > 0) {
|
|
225
|
-
const configPath = import_node_path.default.resolve(queue.shift() ?? "");
|
|
226
|
-
if (projects.has(canonical(configPath)) || !(0, import_node_fs.existsSync)(configPath)) {
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
const project = parseProject(configPath);
|
|
230
|
-
projects.set(canonical(configPath), project);
|
|
231
|
-
for (const reference of referencedConfigs(project)) {
|
|
232
|
-
queue.push(reference);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return [...projects.values()].toSorted((left, right) => right.root.length - left.root.length);
|
|
236
|
-
}
|
|
237
|
-
function projectForFile(fileName, projects) {
|
|
238
|
-
const resolved = canonical(import_node_path.default.resolve(fileName));
|
|
239
|
-
return projects.find((project) => project.files.has(resolved)) ?? projects.find((project) => {
|
|
240
|
-
const relative = import_node_path.default.relative(project.root, fileName);
|
|
241
|
-
return relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
242
|
-
}) ?? projects[0] ?? fallbackProject(import_node_path.default.dirname(fileName));
|
|
243
|
-
}
|
|
244
|
-
async function discoverWorkspacePackages(root) {
|
|
245
|
-
const patterns = readWorkspacePatterns(root);
|
|
246
|
-
const packagePaths = patterns.length === 0 ? [] : await (0, import_tinyglobby.glob)(
|
|
247
|
-
patterns.map((pattern) => `${pattern}/package.json`),
|
|
248
|
-
{
|
|
249
|
-
absolute: true,
|
|
250
|
-
cwd: root,
|
|
251
|
-
onlyFiles: true
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
return packagePaths.flatMap((packagePath) => {
|
|
255
|
-
const packageJson = JSON.parse((0, import_node_fs.readFileSync)(packagePath, "utf8"));
|
|
256
|
-
return packageJson.name === void 0 ? [] : [{ name: packageJson.name, root: import_node_path.default.dirname(packagePath) }];
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
function packageNameOf(specifier) {
|
|
260
|
-
if (specifier.startsWith("@")) {
|
|
261
|
-
return specifier.split("/").slice(0, 2).join("/");
|
|
262
|
-
}
|
|
263
|
-
return specifier.split("/")[0] ?? specifier;
|
|
264
|
-
}
|
|
265
|
-
function within(root, fileName) {
|
|
266
|
-
const relative = import_node_path.default.relative(root, fileName);
|
|
267
|
-
return relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
268
|
-
}
|
|
269
|
-
function realPath(fileName, followSymlinks) {
|
|
270
|
-
if (!followSymlinks) {
|
|
271
|
-
return import_node_path.default.resolve(fileName);
|
|
272
|
-
}
|
|
273
|
-
try {
|
|
274
|
-
return import_node_fs.realpathSync.native(fileName);
|
|
275
|
-
} catch {
|
|
276
|
-
return import_node_path.default.resolve(fileName);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
async function createDependencyResolver(root, options) {
|
|
280
|
-
const workspaces = await discoverWorkspacePackages(root);
|
|
281
|
-
const followSymlinks = options.followSymlinks !== false;
|
|
282
|
-
const builtins = new Set(
|
|
283
|
-
import_node_module.builtinModules.map((name) => name.startsWith("node:") ? name.slice(5) : name)
|
|
284
|
-
);
|
|
285
|
-
return (reference, from, sourceFile, project) => {
|
|
286
|
-
if (reference.target !== void 0) {
|
|
287
|
-
return {
|
|
288
|
-
external: true,
|
|
289
|
-
resolution: "dynamic",
|
|
290
|
-
resolved: false,
|
|
291
|
-
target: reference.target
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
const specifier = reference.specifier;
|
|
295
|
-
if (specifier.startsWith("bun:")) {
|
|
296
|
-
return { external: true, resolution: "bun", resolved: true, target: specifier };
|
|
297
|
-
}
|
|
298
|
-
const withoutNode = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
|
299
|
-
const builtinRoot = withoutNode.split("/")[0];
|
|
300
|
-
if (specifier.startsWith("node:") || builtinRoot !== void 0 && builtins.has(builtinRoot)) {
|
|
301
|
-
return {
|
|
302
|
-
external: true,
|
|
303
|
-
resolution: "node",
|
|
304
|
-
resolved: true,
|
|
305
|
-
target: specifier.startsWith("node:") ? specifier : `node:${specifier}`
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
const mode = import_typescript.default.getModeForUsageLocation(sourceFile, reference.node, project.compilerOptions);
|
|
309
|
-
const resolved = import_typescript.default.resolveModuleName(
|
|
310
|
-
specifier,
|
|
311
|
-
from,
|
|
312
|
-
project.compilerOptions,
|
|
313
|
-
import_typescript.default.sys,
|
|
314
|
-
project.moduleResolutionCache,
|
|
315
|
-
void 0,
|
|
316
|
-
mode
|
|
317
|
-
).resolvedModule;
|
|
318
|
-
if (resolved === void 0) {
|
|
319
|
-
return {
|
|
320
|
-
external: specifier.startsWith(".") === false && import_node_path.default.isAbsolute(specifier) === false,
|
|
321
|
-
resolution: "unresolved",
|
|
322
|
-
resolved: false,
|
|
323
|
-
target: `unresolved:${specifier}`
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
const resolvedPath = realPath(resolved.resolvedFileName, followSymlinks);
|
|
327
|
-
const workspace = workspaces.find((candidate) => within(candidate.root, resolvedPath));
|
|
328
|
-
if (workspace !== void 0) {
|
|
329
|
-
return {
|
|
330
|
-
external: false,
|
|
331
|
-
packageName: workspace.name,
|
|
332
|
-
resolution: "local",
|
|
333
|
-
resolved: true,
|
|
334
|
-
target: normalize(import_node_path.default.relative(root, resolvedPath))
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
const rootRelative = normalize(import_node_path.default.relative(root, resolvedPath));
|
|
338
|
-
if (within(root, resolvedPath) && !rootRelative.split("/").includes("node_modules")) {
|
|
339
|
-
return {
|
|
340
|
-
external: false,
|
|
341
|
-
resolution: "local",
|
|
342
|
-
resolved: true,
|
|
343
|
-
target: rootRelative
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
if (specifier.startsWith(".") || import_node_path.default.isAbsolute(specifier)) {
|
|
347
|
-
return {
|
|
348
|
-
external: true,
|
|
349
|
-
resolution: "file",
|
|
350
|
-
resolved: true,
|
|
351
|
-
target: `file:${normalize(import_node_path.default.relative(root, resolvedPath))}`
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
const packageName = packageNameOf(specifier);
|
|
355
|
-
return {
|
|
356
|
-
external: true,
|
|
357
|
-
packageName,
|
|
358
|
-
resolution: "npm",
|
|
359
|
-
resolved: true,
|
|
360
|
-
target: `npm:${specifier}`
|
|
361
|
-
};
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// src/analysis/source.ts
|
|
366
|
-
var import_promises = require("fs/promises");
|
|
367
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
368
|
-
var import_typescript2 = __toESM(require("typescript"), 1);
|
|
369
|
-
function isStringLiteralLike(node) {
|
|
370
|
-
return node !== void 0 && (import_typescript2.default.isStringLiteral(node) || import_typescript2.default.isNoSubstitutionTemplateLiteral(node));
|
|
371
|
-
}
|
|
372
|
-
function importsOnlyTypes(node) {
|
|
373
|
-
const clause = node.importClause;
|
|
374
|
-
if (clause === void 0) {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
if (clause.isTypeOnly) {
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
if (clause.name !== void 0 || clause.namedBindings === void 0) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
if (import_typescript2.default.isNamespaceImport(clause.namedBindings)) {
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
return clause.namedBindings.elements.length > 0 && clause.namedBindings.elements.every((element) => element.isTypeOnly);
|
|
387
|
-
}
|
|
388
|
-
function exportOnlyTypes(node) {
|
|
389
|
-
if (node.isTypeOnly) return true;
|
|
390
|
-
return node.exportClause !== void 0 && import_typescript2.default.isNamedExports(node.exportClause) && node.exportClause.elements.length > 0 && node.exportClause.elements.every((element) => element.isTypeOnly);
|
|
391
|
-
}
|
|
392
|
-
function collectImports(sourceFile, checker) {
|
|
393
|
-
const imports = [];
|
|
394
|
-
for (const statement of sourceFile.statements) {
|
|
395
|
-
if (import_typescript2.default.isImportDeclaration(statement) && isStringLiteralLike(statement.moduleSpecifier)) {
|
|
396
|
-
imports.push({
|
|
397
|
-
kind: importsOnlyTypes(statement) ? "type" : "static",
|
|
398
|
-
node: statement.moduleSpecifier,
|
|
399
|
-
specifier: statement.moduleSpecifier.text
|
|
400
|
-
});
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (import_typescript2.default.isExportDeclaration(statement) && isStringLiteralLike(statement.moduleSpecifier)) {
|
|
404
|
-
imports.push({
|
|
405
|
-
kind: exportOnlyTypes(statement) ? "type" : "static",
|
|
406
|
-
node: statement.moduleSpecifier,
|
|
407
|
-
specifier: statement.moduleSpecifier.text
|
|
408
|
-
});
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
if (import_typescript2.default.isImportEqualsDeclaration(statement) && import_typescript2.default.isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) {
|
|
412
|
-
imports.push({
|
|
413
|
-
kind: statement.isTypeOnly ? "type" : "require",
|
|
414
|
-
node: statement.moduleReference.expression,
|
|
415
|
-
specifier: statement.moduleReference.expression.text
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
function visit(node) {
|
|
420
|
-
if (import_typescript2.default.isCallExpression(node) && node.arguments.length === 1) {
|
|
421
|
-
const argument = node.arguments[0];
|
|
422
|
-
if (node.expression.kind === import_typescript2.default.SyntaxKind.ImportKeyword && argument !== void 0) {
|
|
423
|
-
if (isStringLiteralLike(argument)) {
|
|
424
|
-
imports.push({ kind: "dynamic", node: argument, specifier: argument.text });
|
|
425
|
-
} else {
|
|
426
|
-
const synthetic = import_typescript2.default.factory.createStringLiteral(argument.getText(sourceFile));
|
|
427
|
-
import_typescript2.default.setTextRange(synthetic, argument);
|
|
428
|
-
imports.push({
|
|
429
|
-
kind: "dynamic",
|
|
430
|
-
node: synthetic,
|
|
431
|
-
specifier: argument.getText(sourceFile),
|
|
432
|
-
target: "dynamic:unknown"
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
} else if (import_typescript2.default.isIdentifier(node.expression) && node.expression.text === "require") {
|
|
436
|
-
const symbol = checker?.getSymbolAtLocation(node.expression);
|
|
437
|
-
const isGlobal = checker === void 0 || (symbol?.declarations?.some(
|
|
438
|
-
(declaration) => declaration.getSourceFile() !== sourceFile
|
|
439
|
-
) ?? symbol === void 0);
|
|
440
|
-
if (isGlobal && isStringLiteralLike(argument)) {
|
|
441
|
-
imports.push({ kind: "require", node: argument, specifier: argument.text });
|
|
442
|
-
} else if (isGlobal && argument !== void 0) {
|
|
443
|
-
const synthetic = import_typescript2.default.factory.createStringLiteral(argument.getText(sourceFile));
|
|
444
|
-
import_typescript2.default.setTextRange(synthetic, argument);
|
|
445
|
-
imports.push({
|
|
446
|
-
kind: "require",
|
|
447
|
-
node: synthetic,
|
|
448
|
-
specifier: argument.getText(sourceFile),
|
|
449
|
-
target: "dynamic:unknown"
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
} else if (import_typescript2.default.isImportTypeNode(node) && import_typescript2.default.isLiteralTypeNode(node.argument) && isStringLiteralLike(node.argument.literal)) {
|
|
454
|
-
imports.push({
|
|
455
|
-
kind: "type",
|
|
456
|
-
node: node.argument.literal,
|
|
457
|
-
specifier: node.argument.literal.text
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
import_typescript2.default.forEachChild(node, visit);
|
|
461
|
-
}
|
|
462
|
-
import_typescript2.default.forEachChild(sourceFile, visit);
|
|
463
|
-
return imports;
|
|
464
|
-
}
|
|
465
|
-
function hasModifier(node, kind) {
|
|
466
|
-
return import_typescript2.default.canHaveModifiers(node) && (import_typescript2.default.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
|
|
467
|
-
}
|
|
468
|
-
function decoratorNames(node) {
|
|
469
|
-
if (!import_typescript2.default.canHaveDecorators(node)) {
|
|
470
|
-
return [];
|
|
471
|
-
}
|
|
472
|
-
return (import_typescript2.default.getDecorators(node) ?? []).map((decorator) => decorator.expression.getText());
|
|
473
|
-
}
|
|
474
|
-
function visibility(node) {
|
|
475
|
-
if (hasModifier(node, import_typescript2.default.SyntaxKind.PrivateKeyword)) {
|
|
476
|
-
return "private";
|
|
477
|
-
}
|
|
478
|
-
if (hasModifier(node, import_typescript2.default.SyntaxKind.ProtectedKeyword)) {
|
|
479
|
-
return "protected";
|
|
480
|
-
}
|
|
481
|
-
return "public";
|
|
482
|
-
}
|
|
483
|
-
function methodsOf(node) {
|
|
484
|
-
return node.members.flatMap((member) => {
|
|
485
|
-
if (!import_typescript2.default.isMethodDeclaration(member) && !import_typescript2.default.isMethodSignature(member)) {
|
|
486
|
-
return [];
|
|
487
|
-
}
|
|
488
|
-
return [
|
|
489
|
-
{
|
|
490
|
-
name: member.name.getText(),
|
|
491
|
-
static: hasModifier(member, import_typescript2.default.SyntaxKind.StaticKeyword),
|
|
492
|
-
visibility: visibility(member)
|
|
493
|
-
}
|
|
494
|
-
];
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
function declarationBase(node, sourceFile) {
|
|
498
|
-
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
499
|
-
return {
|
|
500
|
-
abstract: hasModifier(node, import_typescript2.default.SyntaxKind.AbstractKeyword),
|
|
501
|
-
column: position.character + 1,
|
|
502
|
-
decorators: decoratorNames(node),
|
|
503
|
-
defaultExport: hasModifier(node, import_typescript2.default.SyntaxKind.DefaultKeyword),
|
|
504
|
-
exported: hasModifier(node, import_typescript2.default.SyntaxKind.ExportKeyword),
|
|
505
|
-
line: position.line + 1
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
function heritage(node, token, checker) {
|
|
509
|
-
const types = node.heritageClauses?.filter((clause) => clause.token === token).flatMap((clause) => clause.types) ?? [];
|
|
510
|
-
return [
|
|
511
|
-
...new Set(
|
|
512
|
-
types.flatMap((type) => {
|
|
513
|
-
const text = type.expression.getText();
|
|
514
|
-
const symbol = checker?.getSymbolAtLocation(type.expression);
|
|
515
|
-
const resolvedSymbol = symbol !== void 0 && (symbol.flags & import_typescript2.default.SymbolFlags.Alias) !== 0 ? checker?.getAliasedSymbol(symbol) : symbol;
|
|
516
|
-
const resolved = resolvedSymbol?.getName();
|
|
517
|
-
return resolved === void 0 || resolved === "unknown" || resolved === text ? [text] : [text, resolved];
|
|
518
|
-
})
|
|
519
|
-
)
|
|
520
|
-
];
|
|
521
|
-
}
|
|
522
|
-
function collectDeclarations(sourceFile, checker) {
|
|
523
|
-
return sourceFile.statements.flatMap((statement) => {
|
|
524
|
-
if (import_typescript2.default.isClassDeclaration(statement) || import_typescript2.default.isInterfaceDeclaration(statement)) {
|
|
525
|
-
const name = statement.name?.text ?? "default";
|
|
526
|
-
return [
|
|
527
|
-
{
|
|
528
|
-
...declarationBase(statement, sourceFile),
|
|
529
|
-
extends: heritage(statement, import_typescript2.default.SyntaxKind.ExtendsKeyword, checker),
|
|
530
|
-
implements: import_typescript2.default.isClassDeclaration(statement) ? heritage(statement, import_typescript2.default.SyntaxKind.ImplementsKeyword, checker) : [],
|
|
531
|
-
kind: import_typescript2.default.isClassDeclaration(statement) ? "class" : "interface",
|
|
532
|
-
methods: methodsOf(statement),
|
|
533
|
-
name,
|
|
534
|
-
readonlyProperties: statement.members.filter((member) => import_typescript2.default.isPropertyDeclaration(member) || import_typescript2.default.isPropertySignature(member)).every((member) => hasModifier(member, import_typescript2.default.SyntaxKind.ReadonlyKeyword))
|
|
535
|
-
}
|
|
536
|
-
];
|
|
537
|
-
}
|
|
538
|
-
if (import_typescript2.default.isEnumDeclaration(statement)) {
|
|
539
|
-
return [
|
|
540
|
-
{
|
|
541
|
-
...declarationBase(statement, sourceFile),
|
|
542
|
-
extends: [],
|
|
543
|
-
implements: [],
|
|
544
|
-
kind: "enum",
|
|
545
|
-
methods: [],
|
|
546
|
-
name: statement.name.text,
|
|
547
|
-
readonlyProperties: true
|
|
548
|
-
}
|
|
549
|
-
];
|
|
550
|
-
}
|
|
551
|
-
if (import_typescript2.default.isFunctionDeclaration(statement)) {
|
|
552
|
-
return [
|
|
553
|
-
{
|
|
554
|
-
...declarationBase(statement, sourceFile),
|
|
555
|
-
extends: [],
|
|
556
|
-
implements: [],
|
|
557
|
-
kind: "function",
|
|
558
|
-
methods: [],
|
|
559
|
-
name: statement.name?.text ?? "default",
|
|
560
|
-
readonlyProperties: true
|
|
561
|
-
}
|
|
562
|
-
];
|
|
563
|
-
}
|
|
564
|
-
if (import_typescript2.default.isTypeAliasDeclaration(statement)) {
|
|
565
|
-
return [
|
|
566
|
-
{
|
|
567
|
-
...declarationBase(statement, sourceFile),
|
|
568
|
-
extends: [],
|
|
569
|
-
implements: [],
|
|
570
|
-
kind: "type",
|
|
571
|
-
methods: [],
|
|
572
|
-
name: statement.name.text,
|
|
573
|
-
readonlyProperties: true
|
|
574
|
-
}
|
|
575
|
-
];
|
|
576
|
-
}
|
|
577
|
-
if (import_typescript2.default.isVariableStatement(statement)) {
|
|
578
|
-
const base = declarationBase(statement, sourceFile);
|
|
579
|
-
return statement.declarationList.declarations.map((declaration) => ({
|
|
580
|
-
...base,
|
|
581
|
-
extends: [],
|
|
582
|
-
implements: [],
|
|
583
|
-
kind: "variable",
|
|
584
|
-
methods: [],
|
|
585
|
-
name: declaration.name.getText(),
|
|
586
|
-
readonlyProperties: true
|
|
587
|
-
}));
|
|
588
|
-
}
|
|
589
|
-
return [];
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
function collectImportedBindings(sourceFile) {
|
|
593
|
-
const bindings = /* @__PURE__ */ new Map();
|
|
594
|
-
function add(localName, importedFrom, importedName, namespace) {
|
|
595
|
-
bindings.set(localName, { importedFrom, importedName, namespace });
|
|
596
|
-
}
|
|
597
|
-
for (const statement of sourceFile.statements) {
|
|
598
|
-
if (import_typescript2.default.isImportDeclaration(statement) && isStringLiteralLike(statement.moduleSpecifier)) {
|
|
599
|
-
const importedFrom = statement.moduleSpecifier.text;
|
|
600
|
-
const clause = statement.importClause;
|
|
601
|
-
if (clause?.name !== void 0) add(clause.name.text, importedFrom, "default", true);
|
|
602
|
-
if (clause?.namedBindings !== void 0) {
|
|
603
|
-
if (import_typescript2.default.isNamespaceImport(clause.namedBindings)) {
|
|
604
|
-
add(clause.namedBindings.name.text, importedFrom, "*", true);
|
|
605
|
-
} else {
|
|
606
|
-
for (const element of clause.namedBindings.elements) {
|
|
607
|
-
add(
|
|
608
|
-
element.name.text,
|
|
609
|
-
importedFrom,
|
|
610
|
-
element.propertyName?.text ?? element.name.text,
|
|
611
|
-
false
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
if (import_typescript2.default.isImportEqualsDeclaration(statement) && import_typescript2.default.isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) {
|
|
619
|
-
add(statement.name.text, statement.moduleReference.expression.text, "*", true);
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
if (!import_typescript2.default.isVariableStatement(statement)) continue;
|
|
623
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
624
|
-
const initializer = declaration.initializer;
|
|
625
|
-
if (initializer === void 0 || !import_typescript2.default.isCallExpression(initializer) || !import_typescript2.default.isIdentifier(initializer.expression) || initializer.expression.text !== "require" || !isStringLiteralLike(initializer.arguments[0])) {
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
const importedFrom = initializer.arguments[0].text;
|
|
629
|
-
if (import_typescript2.default.isIdentifier(declaration.name)) {
|
|
630
|
-
add(declaration.name.text, importedFrom, "*", true);
|
|
631
|
-
} else if (import_typescript2.default.isObjectBindingPattern(declaration.name)) {
|
|
632
|
-
for (const element of declaration.name.elements) {
|
|
633
|
-
if (!import_typescript2.default.isIdentifier(element.name)) continue;
|
|
634
|
-
const importedName = element.propertyName?.getText(sourceFile) ?? element.name.text;
|
|
635
|
-
add(element.name.text, importedFrom, importedName, false);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return bindings;
|
|
641
|
-
}
|
|
642
|
-
function importedCall(expression, bindings) {
|
|
643
|
-
if (import_typescript2.default.isIdentifier(expression)) return bindings.get(expression.text);
|
|
644
|
-
if (import_typescript2.default.isPropertyAccessExpression(expression) && import_typescript2.default.isIdentifier(expression.expression)) {
|
|
645
|
-
const namespace = bindings.get(expression.expression.text);
|
|
646
|
-
if (namespace?.namespace === true) {
|
|
647
|
-
return { ...namespace, importedName: expression.name.text, namespace: false };
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
if (import_typescript2.default.isElementAccessExpression(expression) && import_typescript2.default.isIdentifier(expression.expression) && isStringLiteralLike(expression.argumentExpression)) {
|
|
651
|
-
const namespace = bindings.get(expression.expression.text);
|
|
652
|
-
if (namespace?.namespace === true) {
|
|
653
|
-
return {
|
|
654
|
-
...namespace,
|
|
655
|
-
importedName: expression.argumentExpression.text,
|
|
656
|
-
namespace: false
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
return void 0;
|
|
661
|
-
}
|
|
662
|
-
function collectUses(sourceFile, checker) {
|
|
663
|
-
const uses = [];
|
|
664
|
-
const importedBindings = collectImportedBindings(sourceFile);
|
|
665
|
-
function add(node, kind, name, imported) {
|
|
666
|
-
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
667
|
-
uses.push({
|
|
668
|
-
column: position.character + 1,
|
|
669
|
-
...imported === void 0 ? {} : { importedFrom: imported.importedFrom, importedName: imported.importedName },
|
|
670
|
-
kind,
|
|
671
|
-
line: position.line + 1,
|
|
672
|
-
name
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
function visit(node) {
|
|
676
|
-
if (import_typescript2.default.isCallExpression(node)) {
|
|
677
|
-
const imported = importedCall(node.expression, importedBindings);
|
|
678
|
-
if (imported !== void 0) {
|
|
679
|
-
add(node.expression, "call", node.expression.getText(sourceFile), imported);
|
|
680
|
-
} else if (import_typescript2.default.isIdentifier(node.expression)) {
|
|
681
|
-
const symbol = checker?.getSymbolAtLocation(node.expression);
|
|
682
|
-
const isGlobal = checker === void 0 || (symbol?.declarations?.some(
|
|
683
|
-
(declaration) => declaration.getSourceFile() !== sourceFile
|
|
684
|
-
) ?? false);
|
|
685
|
-
if (isGlobal) add(node.expression, "call", node.expression.text);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
if (import_typescript2.default.isBinaryExpression(node) && (node.operatorToken.kind === import_typescript2.default.SyntaxKind.EqualsEqualsToken || node.operatorToken.kind === import_typescript2.default.SyntaxKind.ExclamationEqualsToken)) {
|
|
689
|
-
add(node.operatorToken, "loose-equality", node.operatorToken.getText(sourceFile));
|
|
690
|
-
}
|
|
691
|
-
import_typescript2.default.forEachChild(node, visit);
|
|
692
|
-
}
|
|
693
|
-
import_typescript2.default.forEachChild(sourceFile, visit);
|
|
694
|
-
return uses;
|
|
695
|
-
}
|
|
696
|
-
function collectExports(sourceFile, checker) {
|
|
697
|
-
const moduleSymbol = checker?.getSymbolAtLocation(sourceFile);
|
|
698
|
-
if (moduleSymbol !== void 0) {
|
|
699
|
-
return checker?.getExportsOfModule(moduleSymbol).map((symbol) => symbol.getName()).toSorted() ?? [];
|
|
700
|
-
}
|
|
701
|
-
return collectDeclarations(sourceFile).filter((declaration) => declaration.exported || declaration.defaultExport).map((declaration) => declaration.defaultExport ? "default" : declaration.name);
|
|
702
|
-
}
|
|
703
|
-
function scriptKind(fileName) {
|
|
704
|
-
switch (import_node_path2.default.extname(fileName).toLowerCase()) {
|
|
705
|
-
case ".js":
|
|
706
|
-
case ".cjs":
|
|
707
|
-
case ".mjs":
|
|
708
|
-
return import_typescript2.default.ScriptKind.JS;
|
|
709
|
-
case ".jsx":
|
|
710
|
-
return import_typescript2.default.ScriptKind.JSX;
|
|
711
|
-
case ".json":
|
|
712
|
-
return import_typescript2.default.ScriptKind.JSON;
|
|
713
|
-
case ".tsx":
|
|
714
|
-
return import_typescript2.default.ScriptKind.TSX;
|
|
715
|
-
default:
|
|
716
|
-
return import_typescript2.default.ScriptKind.TS;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
async function analyzeSource(absolutePath, root, context) {
|
|
720
|
-
const source = await (0, import_promises.readFile)(absolutePath, "utf8");
|
|
721
|
-
const sourceFile = context.sourceFile ?? import_typescript2.default.createSourceFile(
|
|
722
|
-
absolutePath,
|
|
723
|
-
source,
|
|
724
|
-
import_typescript2.default.ScriptTarget.Latest,
|
|
725
|
-
true,
|
|
726
|
-
scriptKind(absolutePath)
|
|
727
|
-
);
|
|
728
|
-
const modulePath = import_node_path2.default.relative(root, absolutePath).replaceAll("\\", "/");
|
|
729
|
-
const dependencies = [];
|
|
730
|
-
for (const reference of collectImports(sourceFile, context.checker)) {
|
|
731
|
-
const position = sourceFile.getLineAndCharacterOfPosition(reference.node.getStart(sourceFile));
|
|
732
|
-
dependencies.push({
|
|
733
|
-
...context.resolve(reference, sourceFile),
|
|
734
|
-
column: position.character + 1,
|
|
735
|
-
from: modulePath,
|
|
736
|
-
kind: reference.kind,
|
|
737
|
-
line: position.line + 1,
|
|
738
|
-
specifier: reference.specifier
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
const sourceLines = source.length === 0 ? [] : source.split(/\r?\n/u);
|
|
742
|
-
return {
|
|
743
|
-
absolutePath,
|
|
744
|
-
declarations: collectDeclarations(sourceFile, context.checker),
|
|
745
|
-
dependencies,
|
|
746
|
-
exports: collectExports(sourceFile, context.checker),
|
|
747
|
-
lines: sourceLines.length,
|
|
748
|
-
path: modulePath,
|
|
749
|
-
...context.project === void 0 ? {} : { project: context.project },
|
|
750
|
-
sourceLines,
|
|
751
|
-
uses: collectUses(sourceFile, context.checker)
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// src/analysis/analyzer.ts
|
|
756
|
-
var DEFAULT_INCLUDE = [
|
|
757
|
-
"src/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
758
|
-
"apps/*/src/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
|
|
759
|
-
"packages/*/src/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"
|
|
760
|
-
];
|
|
761
|
-
var DEFAULT_EXCLUDE = [
|
|
762
|
-
"**/node_modules/**",
|
|
763
|
-
"**/dist/**",
|
|
764
|
-
"**/build/**",
|
|
765
|
-
"**/coverage/**"
|
|
766
|
-
];
|
|
767
|
-
var graphCache = /* @__PURE__ */ new Map();
|
|
768
|
-
function stableOptions(options, root) {
|
|
769
|
-
return JSON.stringify({
|
|
770
|
-
cache: options.cache,
|
|
771
|
-
exclude: options.exclude,
|
|
772
|
-
followSymlinks: options.followSymlinks,
|
|
773
|
-
include: options.include,
|
|
774
|
-
includeTypeDeclarations: options.includeTypeDeclarations,
|
|
775
|
-
projects: options.projects,
|
|
776
|
-
root,
|
|
777
|
-
tsconfig: options.tsconfig,
|
|
778
|
-
workspaces: options.workspaces
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
async function cacheKey(files, options, root) {
|
|
782
|
-
const configurationFiles = await (0, import_tinyglobby2.glob)(
|
|
783
|
-
["**/tsconfig*.json", "**/package.json", "pnpm-workspace.yaml"],
|
|
784
|
-
{
|
|
785
|
-
absolute: true,
|
|
786
|
-
cwd: root,
|
|
787
|
-
ignore: DEFAULT_EXCLUDE,
|
|
788
|
-
onlyFiles: true
|
|
789
|
-
}
|
|
790
|
-
);
|
|
791
|
-
const signatures = await Promise.all(
|
|
792
|
-
[.../* @__PURE__ */ new Set([...files, ...configurationFiles])].toSorted().map(async (file) => {
|
|
793
|
-
const details = await (0, import_promises2.stat)(file);
|
|
794
|
-
return `${file}:${details.size}:${details.mtimeMs}`;
|
|
795
|
-
})
|
|
796
|
-
);
|
|
797
|
-
return `${stableOptions(options, root)}
|
|
798
|
-
${signatures.join("\n")}`;
|
|
799
|
-
}
|
|
800
|
-
async function discoverFiles(root, options) {
|
|
801
|
-
const ignore = [
|
|
802
|
-
...DEFAULT_EXCLUDE,
|
|
803
|
-
...options.includeTypeDeclarations === true ? [] : ["**/*.d.ts"],
|
|
804
|
-
...options.exclude ?? []
|
|
805
|
-
];
|
|
806
|
-
const overrides = (options.projects ?? []).flatMap(
|
|
807
|
-
(project) => typeof project === "string" || project.include === void 0 ? [] : [project]
|
|
808
|
-
);
|
|
809
|
-
const overridePatterns = [];
|
|
810
|
-
const overrideRoots = [];
|
|
811
|
-
const resolvedOverrides = await Promise.all(
|
|
812
|
-
overrides.map(async (project) => {
|
|
813
|
-
const configPaths = await (0, import_tinyglobby2.glob)(project.tsconfig, {
|
|
814
|
-
absolute: true,
|
|
815
|
-
cwd: root,
|
|
816
|
-
onlyFiles: true
|
|
817
|
-
});
|
|
818
|
-
return {
|
|
819
|
-
includes: project.include ?? [],
|
|
820
|
-
paths: configPaths.length > 0 ? configPaths : [import_node_path3.default.resolve(root, project.tsconfig)]
|
|
821
|
-
};
|
|
822
|
-
})
|
|
823
|
-
);
|
|
824
|
-
for (const { includes, paths } of resolvedOverrides) {
|
|
825
|
-
for (const configPath of paths) {
|
|
826
|
-
const projectRoot = import_node_path3.default.dirname(configPath);
|
|
827
|
-
const relativeRoot = import_node_path3.default.relative(root, projectRoot).replaceAll("\\", "/");
|
|
828
|
-
overrideRoots.push(relativeRoot === "" ? "**" : `${relativeRoot}/**`);
|
|
829
|
-
overridePatterns.push(
|
|
830
|
-
...includes.map(
|
|
831
|
-
(pattern) => relativeRoot === "" ? pattern : `${relativeRoot}/${pattern}`
|
|
832
|
-
)
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
const files = await (0, import_tinyglobby2.glob)(options.include ?? DEFAULT_INCLUDE, {
|
|
837
|
-
absolute: true,
|
|
838
|
-
cwd: root,
|
|
839
|
-
dot: true,
|
|
840
|
-
followSymbolicLinks: options.followSymlinks !== false,
|
|
841
|
-
ignore: [...ignore, ...overrideRoots],
|
|
842
|
-
onlyFiles: true
|
|
843
|
-
});
|
|
844
|
-
if (overridePatterns.length === 0) {
|
|
845
|
-
return files;
|
|
846
|
-
}
|
|
847
|
-
const projectFiles = await (0, import_tinyglobby2.glob)(overridePatterns, {
|
|
848
|
-
absolute: true,
|
|
849
|
-
cwd: root,
|
|
850
|
-
dot: true,
|
|
851
|
-
followSymbolicLinks: options.followSymlinks !== false,
|
|
852
|
-
ignore,
|
|
853
|
-
onlyFiles: true
|
|
854
|
-
});
|
|
855
|
-
return [.../* @__PURE__ */ new Set([...files, ...projectFiles])];
|
|
856
|
-
}
|
|
857
|
-
async function buildGraph(files, root, options) {
|
|
858
|
-
const projects = await discoverProjects(root, options);
|
|
859
|
-
const resolve = await createDependencyResolver(root, options);
|
|
860
|
-
const modules = await Promise.all(
|
|
861
|
-
files.toSorted().map((file) => {
|
|
862
|
-
const absolutePath = import_node_path3.default.resolve(file);
|
|
863
|
-
const project = projectForFile(absolutePath, projects);
|
|
864
|
-
const sourceFile = project.program.getSourceFile(absolutePath);
|
|
865
|
-
return analyzeSource(absolutePath, root, {
|
|
866
|
-
checker: project.program.getTypeChecker(),
|
|
867
|
-
project: project.info.name,
|
|
868
|
-
resolve: (reference, resolvedSourceFile) => resolve(reference, absolutePath, resolvedSourceFile, project),
|
|
869
|
-
...sourceFile === void 0 ? {} : { sourceFile }
|
|
870
|
-
});
|
|
871
|
-
})
|
|
872
|
-
);
|
|
873
|
-
return {
|
|
874
|
-
dependencies: modules.flatMap((sourceModule) => sourceModule.dependencies),
|
|
875
|
-
modules,
|
|
876
|
-
projects: projects.map((project) => project.info),
|
|
877
|
-
root
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
function clearAnalysisCache() {
|
|
881
|
-
graphCache.clear();
|
|
882
|
-
}
|
|
883
|
-
async function analyze(options = {}) {
|
|
884
|
-
const root = import_node_path3.default.resolve(options.root ?? process.cwd());
|
|
885
|
-
const files = await discoverFiles(root, options);
|
|
886
|
-
if (options.cache === false) {
|
|
887
|
-
return buildGraph(files, root, options);
|
|
888
|
-
}
|
|
889
|
-
const key = await cacheKey(files, options, root);
|
|
890
|
-
const cached = graphCache.get(key);
|
|
891
|
-
if (cached !== void 0) {
|
|
892
|
-
return cached;
|
|
893
|
-
}
|
|
894
|
-
const graph = buildGraph(files, root, options);
|
|
895
|
-
if (graphCache.size >= 10) graphCache.clear();
|
|
896
|
-
graphCache.set(key, graph);
|
|
897
|
-
try {
|
|
898
|
-
return await graph;
|
|
899
|
-
} catch (error) {
|
|
900
|
-
graphCache.delete(key);
|
|
901
|
-
throw error;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
// src/reporting/baseline.ts
|
|
906
|
-
var import_node_crypto = require("crypto");
|
|
907
|
-
var import_promises3 = require("fs/promises");
|
|
908
|
-
var import_node_path4 = __toESM(require("path"), 1);
|
|
909
|
-
function violationFingerprint(violation2) {
|
|
910
|
-
const stableDetail = violation2.dependency ?? violation2.sourceLine;
|
|
911
|
-
const identity = [
|
|
912
|
-
violation2.ruleId ?? violation2.rule,
|
|
913
|
-
violation2.file ?? "",
|
|
914
|
-
stableDetail ?? (violation2.ruleId === void 0 ? violation2.message : "")
|
|
915
|
-
].join("\0");
|
|
916
|
-
return (0, import_node_crypto.createHash)("sha256").update(identity).digest("hex").slice(0, 20);
|
|
917
|
-
}
|
|
918
|
-
function createBaseline(result) {
|
|
919
|
-
return {
|
|
920
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
921
|
-
version: 1,
|
|
922
|
-
violations: result.violations.map((violation2) => ({
|
|
923
|
-
...violation2.file === void 0 ? {} : { file: violation2.file },
|
|
924
|
-
fingerprint: violationFingerprint(violation2),
|
|
925
|
-
message: violation2.message,
|
|
926
|
-
rule: violation2.ruleId ?? violation2.rule
|
|
927
|
-
}))
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
async function readBaseline(fileName) {
|
|
931
|
-
const parsed = JSON.parse(await (0, import_promises3.readFile)(fileName, "utf8"));
|
|
932
|
-
if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || parsed.version !== 1 || !("violations" in parsed) || !Array.isArray(parsed.violations) || parsed.violations.some(
|
|
933
|
-
(entry) => typeof entry !== "object" || entry === null || !("fingerprint" in entry) || typeof entry.fingerprint !== "string"
|
|
934
|
-
)) {
|
|
935
|
-
throw new TypeError(`Unsupported Archer baseline '${fileName}'.`);
|
|
936
|
-
}
|
|
937
|
-
return parsed;
|
|
938
|
-
}
|
|
939
|
-
async function writeBaseline(fileName, baseline) {
|
|
940
|
-
await (0, import_promises3.mkdir)(import_node_path4.default.dirname(fileName), { recursive: true });
|
|
941
|
-
await (0, import_promises3.writeFile)(fileName, `${JSON.stringify(baseline, void 0, 2)}
|
|
942
|
-
`, "utf8");
|
|
943
|
-
}
|
|
944
|
-
function applyBaseline(result, baseline) {
|
|
945
|
-
const known = new Set(baseline.violations.map((entry) => entry.fingerprint));
|
|
946
|
-
const violations = result.violations.filter(
|
|
947
|
-
(violation2) => !known.has(violationFingerprint(violation2))
|
|
948
|
-
);
|
|
949
|
-
const errors = violations.filter((violation2) => violation2.severity === "error").length;
|
|
950
|
-
const warnings = violations.filter((violation2) => violation2.severity === "warning").length;
|
|
951
|
-
const infos = violations.filter((violation2) => violation2.severity === "info").length;
|
|
952
|
-
return {
|
|
953
|
-
...result,
|
|
954
|
-
errors,
|
|
955
|
-
infos,
|
|
956
|
-
ok: errors === 0,
|
|
957
|
-
violations,
|
|
958
|
-
warnings
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// src/config/index.ts
|
|
963
|
-
function defineConfig(config) {
|
|
964
|
-
return config;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// src/core/matchers.ts
|
|
968
|
-
var import_picomatch = __toESM(require("picomatch"), 1);
|
|
969
|
-
var GLOB_CHARACTERS = /[*?{[(]/u;
|
|
970
|
-
function normalize2(value) {
|
|
971
|
-
if (value.startsWith("./")) {
|
|
972
|
-
return value.slice(2).replaceAll("\\", "/");
|
|
973
|
-
}
|
|
974
|
-
return value.replaceAll("\\", "/");
|
|
975
|
-
}
|
|
976
|
-
function matchesOne(candidate, rawSelector) {
|
|
977
|
-
const selector = normalize2(rawSelector);
|
|
978
|
-
if (/^(?:dynamic|file|node|npm|unresolved):\*$/u.test(selector)) {
|
|
979
|
-
return candidate.startsWith(selector.slice(0, -1));
|
|
980
|
-
}
|
|
981
|
-
if (!GLOB_CHARACTERS.test(selector)) {
|
|
982
|
-
if (candidate === selector || candidate.startsWith(`${selector}/`)) {
|
|
983
|
-
return true;
|
|
984
|
-
}
|
|
985
|
-
if (selector.startsWith("npm:") && candidate.startsWith(`${selector}/`)) {
|
|
986
|
-
return true;
|
|
987
|
-
}
|
|
988
|
-
return false;
|
|
989
|
-
}
|
|
990
|
-
return (0, import_picomatch.default)(selector, { dot: true })(candidate);
|
|
991
|
-
}
|
|
992
|
-
function selectorValues(selector) {
|
|
993
|
-
return typeof selector === "string" ? [selector] : selector;
|
|
994
|
-
}
|
|
995
|
-
function matchesSelector(candidate, selector) {
|
|
996
|
-
return selectorValues(selector).some((value) => matchesOne(candidate, value));
|
|
997
|
-
}
|
|
998
|
-
function formatSelector(selector) {
|
|
999
|
-
return selectorValues(selector).map((value) => `'${value}'`).join(", ");
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// src/core/layers.ts
|
|
1003
|
-
function violation(rule, message, source, line, column) {
|
|
1004
|
-
return {
|
|
1005
|
-
...column === void 0 ? {} : { column },
|
|
1006
|
-
...source === void 0 ? {} : { file: source.path },
|
|
1007
|
-
...line === void 0 ? {} : { line },
|
|
1008
|
-
message,
|
|
1009
|
-
rule,
|
|
1010
|
-
severity: "error"
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
var ArchitectureLayers = class {
|
|
1014
|
-
#definitions;
|
|
1015
|
-
#suite;
|
|
1016
|
-
constructor(suite, definitions) {
|
|
1017
|
-
if (Object.keys(definitions).length === 0) {
|
|
1018
|
-
throw new TypeError("layers() needs at least one named layer.");
|
|
1019
|
-
}
|
|
1020
|
-
this.#suite = suite;
|
|
1021
|
-
this.#definitions = definitions;
|
|
1022
|
-
}
|
|
1023
|
-
where(name) {
|
|
1024
|
-
return new LayerExpectation(this.#suite, this.#definitions, name);
|
|
1025
|
-
}
|
|
1026
|
-
toBeFreeOfCycles() {
|
|
1027
|
-
const definitions = this.#definitions;
|
|
1028
|
-
this.#suite.rule("layers-free-of-cycles", ({ graph }) => {
|
|
1029
|
-
const adjacency = new Map(Object.keys(definitions).map((name) => [name, /* @__PURE__ */ new Set()]));
|
|
1030
|
-
for (const dependency of graph.dependencies.filter((item) => item.resolution === "local")) {
|
|
1031
|
-
const from = Object.entries(definitions).find(
|
|
1032
|
-
([, selector]) => matchesSelector(dependency.from, selector)
|
|
1033
|
-
)?.[0];
|
|
1034
|
-
const target = Object.entries(definitions).find(
|
|
1035
|
-
([, selector]) => matchesSelector(dependency.target, selector)
|
|
1036
|
-
)?.[0];
|
|
1037
|
-
if (from !== void 0 && target !== void 0 && from !== target) {
|
|
1038
|
-
adjacency.get(from)?.add(target);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
1042
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1043
|
-
const stack = [];
|
|
1044
|
-
const found = [];
|
|
1045
|
-
function visit(name) {
|
|
1046
|
-
visiting.add(name);
|
|
1047
|
-
stack.push(name);
|
|
1048
|
-
for (const target of adjacency.get(name) ?? []) {
|
|
1049
|
-
if (visiting.has(target)) {
|
|
1050
|
-
found.push([...stack.slice(stack.indexOf(target)), target]);
|
|
1051
|
-
} else if (!visited.has(target)) {
|
|
1052
|
-
visit(target);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
stack.pop();
|
|
1056
|
-
visiting.delete(name);
|
|
1057
|
-
visited.add(name);
|
|
1058
|
-
}
|
|
1059
|
-
for (const name of adjacency.keys()) {
|
|
1060
|
-
if (!visited.has(name)) visit(name);
|
|
1061
|
-
}
|
|
1062
|
-
return found.map(
|
|
1063
|
-
(cycle) => violation("named layers must be free of cycles", `Layer cycle: ${cycle.join(" -> ")}.`)
|
|
1064
|
-
);
|
|
1065
|
-
});
|
|
1066
|
-
return this;
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
var LayerExpectation = class {
|
|
1070
|
-
#definitions;
|
|
1071
|
-
#name;
|
|
1072
|
-
#selector;
|
|
1073
|
-
#suite;
|
|
1074
|
-
constructor(suite, definitions, name) {
|
|
1075
|
-
const selector = definitions[name];
|
|
1076
|
-
if (selector === void 0) {
|
|
1077
|
-
throw new TypeError(`Unknown architecture layer '${name}'.`);
|
|
1078
|
-
}
|
|
1079
|
-
this.#suite = suite;
|
|
1080
|
-
this.#definitions = definitions;
|
|
1081
|
-
this.#name = name;
|
|
1082
|
-
this.#selector = selector;
|
|
1083
|
-
}
|
|
1084
|
-
mayDependOn(...names) {
|
|
1085
|
-
this.#suite.expect(this.#selector).localDependencies().toOnlyDependOn([...this.selectors(names), ...this.selectors([this.#name])]);
|
|
1086
|
-
return this;
|
|
1087
|
-
}
|
|
1088
|
-
mayOnlyDependOn(...names) {
|
|
1089
|
-
return this.mayDependOn(...names);
|
|
1090
|
-
}
|
|
1091
|
-
mayNotDependOn(...names) {
|
|
1092
|
-
this.#suite.expect(this.#selector).localDependencies().not.toDependOn(this.selectors(names));
|
|
1093
|
-
return this;
|
|
1094
|
-
}
|
|
1095
|
-
mayOnlyBeUsedBy(...names) {
|
|
1096
|
-
this.#suite.expect(this.#selector).localDependencies().toOnlyBeDependedOnBy([...this.selectors(names), ...this.selectors([this.#name])]);
|
|
1097
|
-
return this;
|
|
1098
|
-
}
|
|
1099
|
-
exposeThrough(...barrels) {
|
|
1100
|
-
if (barrels.length === 0) {
|
|
1101
|
-
throw new TypeError("exposeThrough() needs at least one public API selector.");
|
|
1102
|
-
}
|
|
1103
|
-
const selector = this.#selector;
|
|
1104
|
-
const layerName = this.#name;
|
|
1105
|
-
this.#suite.expect(selector).toSatisfy(`layer-${layerName}-public-api`, ({ graph }) => {
|
|
1106
|
-
return graph.dependencies.flatMap((dependency) => {
|
|
1107
|
-
if (dependency.resolution !== "local" || matchesSelector(dependency.from, selector) || !matchesSelector(dependency.target, selector) || matchesSelector(dependency.target, barrels)) {
|
|
1108
|
-
return [];
|
|
1109
|
-
}
|
|
1110
|
-
const source = graph.modules.find((item) => item.path === dependency.from);
|
|
1111
|
-
return [
|
|
1112
|
-
violation(
|
|
1113
|
-
`layer '${layerName}' must be exposed through ${barrels.join(", ")}`,
|
|
1114
|
-
`Imports internal module '${dependency.target}' instead of a public API.`,
|
|
1115
|
-
source,
|
|
1116
|
-
dependency.line,
|
|
1117
|
-
dependency.column
|
|
1118
|
-
)
|
|
1119
|
-
];
|
|
1120
|
-
});
|
|
1121
|
-
});
|
|
1122
|
-
return this;
|
|
1123
|
-
}
|
|
1124
|
-
selectors(names) {
|
|
1125
|
-
return names.flatMap((name) => {
|
|
1126
|
-
const selector = this.#definitions[name];
|
|
1127
|
-
if (selector === void 0) {
|
|
1128
|
-
throw new TypeError(`Unknown architecture layer '${name}'.`);
|
|
1129
|
-
}
|
|
1130
|
-
return selectorValues(selector);
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
};
|
|
1134
|
-
|
|
1135
|
-
// src/analysis/evaluation/cycles.ts
|
|
1136
|
-
var import_node_path5 = __toESM(require("path"), 1);
|
|
1137
|
-
|
|
1138
|
-
// src/analysis/evaluation/shared.ts
|
|
1139
|
-
function selectedModules(graph, selection) {
|
|
1140
|
-
return graph.modules.filter(
|
|
1141
|
-
(sourceModule) => matchesSelector(sourceModule.path, selection.selector) && !selection.ignored.some((ignored) => matchesSelector(sourceModule.path, ignored))
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
function describeRule(rule) {
|
|
1145
|
-
const subject = formatSelector(rule.selection.selector);
|
|
1146
|
-
switch (rule.kind) {
|
|
1147
|
-
case "depend-on":
|
|
1148
|
-
return `${subject} must${rule.negated ? " not" : ""} ${rule.transitive ? "transitively " : ""}depend on ${formatSelector(rule.target)}`;
|
|
1149
|
-
case "only-depend-on":
|
|
1150
|
-
return `${subject} must${rule.negated ? " not" : ""} only depend on ${formatSelector(rule.target)}`;
|
|
1151
|
-
case "depended-on-by":
|
|
1152
|
-
return `${subject} must${rule.negated ? " not" : ""} be depended on by ${formatSelector(rule.target)}`;
|
|
1153
|
-
case "only-depended-on-by":
|
|
1154
|
-
return `${subject} must${rule.negated ? " not" : ""} only be depended on by ${formatSelector(rule.target)}`;
|
|
1155
|
-
case "free-of-cycles":
|
|
1156
|
-
return rule.collapseToFolderDepth === void 0 ? `${subject} must be free of cycles` : `${subject} must be free of folder cycles`;
|
|
1157
|
-
case "prefix":
|
|
1158
|
-
return `${subject} must${rule.negated ? " not" : ""} have prefix '${rule.value}'`;
|
|
1159
|
-
case "suffix":
|
|
1160
|
-
return `${subject} must${rule.negated ? " not" : ""} have suffix '${rule.value}'`;
|
|
1161
|
-
case "line-count-less-than":
|
|
1162
|
-
return `${subject} must${rule.negated ? " not" : ""} have fewer than ${rule.value} lines`;
|
|
1163
|
-
case "declaration-kind":
|
|
1164
|
-
return `${subject} must${rule.negated ? " not" : ""} contain only ${rule.declarationKind} declarations`;
|
|
1165
|
-
case "abstract":
|
|
1166
|
-
return `${subject} must${rule.negated ? " not" : ""} contain abstract classes`;
|
|
1167
|
-
case "exported":
|
|
1168
|
-
return `${subject} must${rule.negated ? " not" : ""} contain exported declarations`;
|
|
1169
|
-
case "extend":
|
|
1170
|
-
return `${subject} must${rule.negated ? " not" : ""} extend '${rule.value}'`;
|
|
1171
|
-
case "implement":
|
|
1172
|
-
return `${subject} must${rule.negated ? " not" : ""} implement '${rule.value}'`;
|
|
1173
|
-
case "decorator":
|
|
1174
|
-
return `${subject} must${rule.negated ? " not" : ""} use decorator '${rule.value}'`;
|
|
1175
|
-
case "only-export":
|
|
1176
|
-
return `${subject} must${rule.negated ? " not" : ""} only export ${rule.names.join(", ")}`;
|
|
1177
|
-
case "readonly-properties":
|
|
1178
|
-
return `${subject} must${rule.negated ? " not" : ""} have readonly properties`;
|
|
1179
|
-
case "strict-equality":
|
|
1180
|
-
return `${subject} must use strict equality`;
|
|
1181
|
-
case "use-globals":
|
|
1182
|
-
return `${subject} must${rule.negated ? " not" : ""} use globals ${rule.globals.join(", ")}`;
|
|
1183
|
-
case "use-imported-symbols":
|
|
1184
|
-
return `${subject} must${rule.negated ? " not" : ""} call ${rule.symbols.join(", ")} from '${rule.importedFrom}'`;
|
|
1185
|
-
case "have-methods":
|
|
1186
|
-
return `${subject} must${rule.negated ? " not" : ""} have methods ${rule.names.join(", ")}`;
|
|
1187
|
-
case "have-visible-methods":
|
|
1188
|
-
return `${subject} must${rule.negated ? " not" : ""} have ${rule.visibility ?? "public"} methods ${rule.names.join(", ")}`;
|
|
1189
|
-
case "only-public-methods":
|
|
1190
|
-
return `${subject} must${rule.negated ? " not" : ""} only have public methods ${rule.names.join(", ")}`;
|
|
1191
|
-
case "resolvable-dependencies":
|
|
1192
|
-
return `${subject} must have resolvable dependencies`;
|
|
1193
|
-
case "public-api":
|
|
1194
|
-
return `${subject} must${rule.negated ? " not" : ""} be public API files`;
|
|
1195
|
-
case "custom":
|
|
1196
|
-
return rule.id ?? "custom architecture rule";
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
function sourceLine(sourceModule, line) {
|
|
1200
|
-
if (sourceModule === void 0 || line === void 0) {
|
|
1201
|
-
return void 0;
|
|
1202
|
-
}
|
|
1203
|
-
return sourceModule.sourceLines[line - 1];
|
|
1204
|
-
}
|
|
1205
|
-
function makeViolation(rule, message, sourceModule, dependency, location, importChain) {
|
|
1206
|
-
const line = dependency?.line ?? location?.line;
|
|
1207
|
-
const column = dependency?.column ?? location?.column;
|
|
1208
|
-
const content = sourceLine(sourceModule, line);
|
|
1209
|
-
return {
|
|
1210
|
-
...column === void 0 ? {} : { column },
|
|
1211
|
-
...dependency === void 0 ? {} : { dependency: dependency.target },
|
|
1212
|
-
...sourceModule === void 0 ? {} : { file: sourceModule.path },
|
|
1213
|
-
...importChain === void 0 ? {} : { importChain },
|
|
1214
|
-
...line === void 0 ? {} : { line },
|
|
1215
|
-
message,
|
|
1216
|
-
...rule.reason === void 0 ? {} : { reason: rule.reason },
|
|
1217
|
-
rule: describeRule(rule),
|
|
1218
|
-
...rule.id === void 0 ? {} : { ruleId: rule.id },
|
|
1219
|
-
severity: rule.severity,
|
|
1220
|
-
...content === void 0 ? {} : { sourceLine: content }
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
function noMatchViolation(rule) {
|
|
1224
|
-
return makeViolation(rule, `No files matched ${formatSelector(rule.selection.selector)}.`);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
// src/analysis/evaluation/cycles.ts
|
|
1228
|
-
function collapsedPath(fileName, depth) {
|
|
1229
|
-
if (depth === void 0) {
|
|
1230
|
-
return fileName;
|
|
1231
|
-
}
|
|
1232
|
-
return fileName.split("/").slice(0, depth).join("/");
|
|
1233
|
-
}
|
|
1234
|
-
function adjacencyFor(modules, rule) {
|
|
1235
|
-
const sourcePaths = new Set(modules.map((sourceModule) => sourceModule.path));
|
|
1236
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1237
|
-
for (const sourceModule of modules) {
|
|
1238
|
-
const from = collapsedPath(sourceModule.path, rule.collapseToFolderDepth);
|
|
1239
|
-
const targets = adjacency.get(from) ?? /* @__PURE__ */ new Set();
|
|
1240
|
-
for (const dependency of sourceModule.dependencies) {
|
|
1241
|
-
if (sourcePaths.has(dependency.target) && (rule.dependencyKinds === void 0 || rule.dependencyKinds.includes(dependency.kind))) {
|
|
1242
|
-
const target = collapsedPath(dependency.target, rule.collapseToFolderDepth);
|
|
1243
|
-
if (target !== from || dependency.target === sourceModule.path) {
|
|
1244
|
-
targets.add(target);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
adjacency.set(from, targets);
|
|
1249
|
-
}
|
|
1250
|
-
return new Map([...adjacency].map(([key, values]) => [key, [...values]]));
|
|
1251
|
-
}
|
|
1252
|
-
function stronglyConnectedComponents(adjacency) {
|
|
1253
|
-
const indexes = /* @__PURE__ */ new Map();
|
|
1254
|
-
const lowLinks = /* @__PURE__ */ new Map();
|
|
1255
|
-
const stack = [];
|
|
1256
|
-
const onStack = /* @__PURE__ */ new Set();
|
|
1257
|
-
const components = [];
|
|
1258
|
-
let index = 0;
|
|
1259
|
-
function connect(node) {
|
|
1260
|
-
indexes.set(node, index);
|
|
1261
|
-
lowLinks.set(node, index);
|
|
1262
|
-
index += 1;
|
|
1263
|
-
stack.push(node);
|
|
1264
|
-
onStack.add(node);
|
|
1265
|
-
for (const target of adjacency.get(node) ?? []) {
|
|
1266
|
-
if (!indexes.has(target)) {
|
|
1267
|
-
connect(target);
|
|
1268
|
-
lowLinks.set(node, Math.min(lowLinks.get(node) ?? 0, lowLinks.get(target) ?? 0));
|
|
1269
|
-
} else if (onStack.has(target)) {
|
|
1270
|
-
lowLinks.set(node, Math.min(lowLinks.get(node) ?? 0, indexes.get(target) ?? 0));
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
if (lowLinks.get(node) === indexes.get(node)) {
|
|
1274
|
-
const component = [];
|
|
1275
|
-
let current;
|
|
1276
|
-
do {
|
|
1277
|
-
current = stack.pop();
|
|
1278
|
-
if (current !== void 0) {
|
|
1279
|
-
onStack.delete(current);
|
|
1280
|
-
component.push(current);
|
|
1281
|
-
}
|
|
1282
|
-
} while (current !== node);
|
|
1283
|
-
components.push(component);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
for (const node of adjacency.keys()) {
|
|
1287
|
-
if (!indexes.has(node)) {
|
|
1288
|
-
connect(node);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
return components.filter((component) => {
|
|
1292
|
-
if (component.length > 1) {
|
|
1293
|
-
return true;
|
|
1294
|
-
}
|
|
1295
|
-
const only = component[0];
|
|
1296
|
-
return only !== void 0 && (adjacency.get(only) ?? []).includes(only);
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
function findCycle(component, adjacency) {
|
|
1300
|
-
const members = new Set(component);
|
|
1301
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
1302
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1303
|
-
const stack = [];
|
|
1304
|
-
function visit(node) {
|
|
1305
|
-
visiting.add(node);
|
|
1306
|
-
stack.push(node);
|
|
1307
|
-
for (const target of adjacency.get(node) ?? []) {
|
|
1308
|
-
if (!members.has(target)) {
|
|
1309
|
-
continue;
|
|
1310
|
-
}
|
|
1311
|
-
if (visiting.has(target)) {
|
|
1312
|
-
return [...stack.slice(stack.indexOf(target)), target];
|
|
1313
|
-
}
|
|
1314
|
-
if (!visited.has(target)) {
|
|
1315
|
-
const cycle = visit(target);
|
|
1316
|
-
if (cycle !== void 0) {
|
|
1317
|
-
return cycle;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
stack.pop();
|
|
1322
|
-
visiting.delete(node);
|
|
1323
|
-
visited.add(node);
|
|
1324
|
-
return void 0;
|
|
1325
|
-
}
|
|
1326
|
-
for (const node of component.toSorted()) {
|
|
1327
|
-
const cycle = visit(node);
|
|
1328
|
-
if (cycle !== void 0) {
|
|
1329
|
-
return cycle;
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
return component;
|
|
1333
|
-
}
|
|
1334
|
-
function evaluateCycleRule(rule, subjects) {
|
|
1335
|
-
const adjacency = adjacencyFor(subjects, rule);
|
|
1336
|
-
return stronglyConnectedComponents(adjacency).map((component) => {
|
|
1337
|
-
const cycle = findCycle(component, adjacency);
|
|
1338
|
-
const first = cycle[0];
|
|
1339
|
-
const source = first === void 0 ? void 0 : subjects.find(
|
|
1340
|
-
(subject) => rule.collapseToFolderDepth === void 0 ? subject.path === first : collapsedPath(subject.path, rule.collapseToFolderDepth) === first
|
|
1341
|
-
);
|
|
1342
|
-
return makeViolation(rule, `Dependency cycle: ${cycle.join(" -> ")}.`, source);
|
|
1343
|
-
});
|
|
1344
|
-
}
|
|
1345
|
-
function publicApiFile(fileName) {
|
|
1346
|
-
const baseName = import_node_path5.default.basename(fileName);
|
|
1347
|
-
return /^(?:index|public-api)\.[cm]?[jt]sx?$/u.test(baseName);
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
// src/analysis/evaluation/dependencies.ts
|
|
1351
|
-
function dependencyAllowed(rule, dependency) {
|
|
1352
|
-
return (rule.dependencyKinds === void 0 || rule.dependencyKinds.includes(dependency.kind)) && (!rule.localOnly || dependency.resolution === "local");
|
|
1353
|
-
}
|
|
1354
|
-
function directDependencies(subject, rule) {
|
|
1355
|
-
return subject.dependencies.filter((dependency) => dependencyAllowed(rule, dependency)).map((dependency) => ({ dependency, path: [subject.path, dependency.target] }));
|
|
1356
|
-
}
|
|
1357
|
-
function reachableDependencies(graph, subject, rule) {
|
|
1358
|
-
if (!rule.transitive) {
|
|
1359
|
-
return directDependencies(subject, rule);
|
|
1360
|
-
}
|
|
1361
|
-
const moduleByPath = new Map(
|
|
1362
|
-
graph.modules.map((sourceModule) => [sourceModule.path, sourceModule])
|
|
1363
|
-
);
|
|
1364
|
-
const queue = [
|
|
1365
|
-
{
|
|
1366
|
-
module: subject,
|
|
1367
|
-
path: [subject.path]
|
|
1368
|
-
}
|
|
1369
|
-
];
|
|
1370
|
-
const visited = /* @__PURE__ */ new Set([subject.path]);
|
|
1371
|
-
const traversed = [];
|
|
1372
|
-
while (queue.length > 0) {
|
|
1373
|
-
const current = queue.shift();
|
|
1374
|
-
if (current === void 0) {
|
|
1375
|
-
continue;
|
|
1376
|
-
}
|
|
1377
|
-
for (const dependency of current.module.dependencies.filter(
|
|
1378
|
-
(item) => dependencyAllowed(rule, item)
|
|
1379
|
-
)) {
|
|
1380
|
-
const dependencyPath = [...current.path, dependency.target];
|
|
1381
|
-
traversed.push({ dependency, path: dependencyPath });
|
|
1382
|
-
const target = moduleByPath.get(dependency.target);
|
|
1383
|
-
if (target !== void 0 && !visited.has(target.path)) {
|
|
1384
|
-
visited.add(target.path);
|
|
1385
|
-
queue.push({ module: target, path: dependencyPath });
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
return traversed;
|
|
1390
|
-
}
|
|
1391
|
-
function incomingDependencies(graph, subject, rule) {
|
|
1392
|
-
const direct = graph.dependencies.filter(
|
|
1393
|
-
(dependency) => dependency.target === subject.path && dependencyAllowed(rule, dependency)
|
|
1394
|
-
).map((dependency) => ({ dependency, path: [dependency.from, subject.path] }));
|
|
1395
|
-
if (!rule.transitive) {
|
|
1396
|
-
return direct;
|
|
1397
|
-
}
|
|
1398
|
-
const incomingByTarget = /* @__PURE__ */ new Map();
|
|
1399
|
-
for (const dependency of graph.dependencies.filter((item) => dependencyAllowed(rule, item))) {
|
|
1400
|
-
const list = incomingByTarget.get(dependency.target) ?? [];
|
|
1401
|
-
list.push(dependency);
|
|
1402
|
-
incomingByTarget.set(dependency.target, list);
|
|
1403
|
-
}
|
|
1404
|
-
const queue = [
|
|
1405
|
-
{ path: [subject.path], target: subject.path }
|
|
1406
|
-
];
|
|
1407
|
-
const visited = /* @__PURE__ */ new Set([subject.path]);
|
|
1408
|
-
const traversed = [];
|
|
1409
|
-
while (queue.length > 0) {
|
|
1410
|
-
const current = queue.shift();
|
|
1411
|
-
if (current === void 0) {
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
for (const dependency of incomingByTarget.get(current.target) ?? []) {
|
|
1415
|
-
const dependencyPath = [dependency.from, ...current.path];
|
|
1416
|
-
traversed.push({ dependency, path: dependencyPath });
|
|
1417
|
-
if (!visited.has(dependency.from)) {
|
|
1418
|
-
visited.add(dependency.from);
|
|
1419
|
-
queue.push({ path: dependencyPath, target: dependency.from });
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
return traversed;
|
|
1424
|
-
}
|
|
1425
|
-
function sourceFor(graph, dependency) {
|
|
1426
|
-
return graph.modules.find((candidate) => candidate.path === dependency.from);
|
|
1427
|
-
}
|
|
1428
|
-
function evaluateDependOn(graph, rule, subjects) {
|
|
1429
|
-
const violations = [];
|
|
1430
|
-
for (const subject of subjects) {
|
|
1431
|
-
const traversed = reachableDependencies(graph, subject, rule);
|
|
1432
|
-
const matching = traversed.filter(
|
|
1433
|
-
({ dependency }) => matchesSelector(dependency.target, rule.target) || rule.negated && dependency.target === "dynamic:unknown"
|
|
1434
|
-
);
|
|
1435
|
-
if (rule.negated) {
|
|
1436
|
-
for (const item of matching) {
|
|
1437
|
-
const message = item.dependency.target === "dynamic:unknown" ? `Cannot resolve computed dynamic import '${item.dependency.specifier}' to prove this boundary.` : `Imports '${item.dependency.specifier}', resolved as '${item.dependency.target}'.`;
|
|
1438
|
-
violations.push(
|
|
1439
|
-
makeViolation(
|
|
1440
|
-
rule,
|
|
1441
|
-
message,
|
|
1442
|
-
sourceFor(graph, item.dependency),
|
|
1443
|
-
item.dependency,
|
|
1444
|
-
void 0,
|
|
1445
|
-
item.path
|
|
1446
|
-
)
|
|
1447
|
-
);
|
|
1448
|
-
}
|
|
1449
|
-
} else if (matching.length === 0) {
|
|
1450
|
-
violations.push(
|
|
1451
|
-
makeViolation(rule, `Does not depend on ${formatSelector(rule.target)}.`, subject)
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
return violations;
|
|
1456
|
-
}
|
|
1457
|
-
function evaluateOnlyDependOn(graph, rule, subjects) {
|
|
1458
|
-
const violations = [];
|
|
1459
|
-
for (const subject of subjects) {
|
|
1460
|
-
const outside = reachableDependencies(graph, subject, rule).filter(
|
|
1461
|
-
({ dependency }) => !matchesSelector(dependency.target, rule.target)
|
|
1462
|
-
);
|
|
1463
|
-
if (rule.negated) {
|
|
1464
|
-
if (outside.length === 0) {
|
|
1465
|
-
violations.push(
|
|
1466
|
-
makeViolation(rule, `Every dependency matches ${formatSelector(rule.target)}.`, subject)
|
|
1467
|
-
);
|
|
1468
|
-
}
|
|
1469
|
-
} else {
|
|
1470
|
-
for (const item of outside) {
|
|
1471
|
-
violations.push(
|
|
1472
|
-
makeViolation(
|
|
1473
|
-
rule,
|
|
1474
|
-
`Imports '${item.dependency.specifier}', resolved as '${item.dependency.target}'.`,
|
|
1475
|
-
sourceFor(graph, item.dependency),
|
|
1476
|
-
item.dependency,
|
|
1477
|
-
void 0,
|
|
1478
|
-
item.path
|
|
1479
|
-
)
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
return violations;
|
|
1485
|
-
}
|
|
1486
|
-
function evaluateDependedOnBy(graph, rule, subjects) {
|
|
1487
|
-
const violations = [];
|
|
1488
|
-
for (const subject of subjects) {
|
|
1489
|
-
const matching = incomingDependencies(graph, subject, rule).filter(
|
|
1490
|
-
({ dependency }) => matchesSelector(dependency.from, rule.target)
|
|
1491
|
-
);
|
|
1492
|
-
if (rule.negated) {
|
|
1493
|
-
for (const item of matching) {
|
|
1494
|
-
violations.push(
|
|
1495
|
-
makeViolation(
|
|
1496
|
-
rule,
|
|
1497
|
-
`'${item.dependency.from}' imports '${subject.path}'.`,
|
|
1498
|
-
sourceFor(graph, item.dependency),
|
|
1499
|
-
item.dependency,
|
|
1500
|
-
void 0,
|
|
1501
|
-
item.path
|
|
1502
|
-
)
|
|
1503
|
-
);
|
|
1504
|
-
}
|
|
1505
|
-
} else if (matching.length === 0) {
|
|
1506
|
-
violations.push(
|
|
1507
|
-
makeViolation(rule, `Is not depended on by ${formatSelector(rule.target)}.`, subject)
|
|
1508
|
-
);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
return violations;
|
|
1512
|
-
}
|
|
1513
|
-
function evaluateOnlyDependedOnBy(graph, rule, subjects) {
|
|
1514
|
-
const violations = [];
|
|
1515
|
-
for (const subject of subjects) {
|
|
1516
|
-
const outside = incomingDependencies(graph, subject, rule).filter(
|
|
1517
|
-
({ dependency }) => !matchesSelector(dependency.from, rule.target)
|
|
1518
|
-
);
|
|
1519
|
-
if (rule.negated) {
|
|
1520
|
-
if (outside.length === 0) {
|
|
1521
|
-
violations.push(
|
|
1522
|
-
makeViolation(
|
|
1523
|
-
rule,
|
|
1524
|
-
`Every incoming dependency is from ${formatSelector(rule.target)}.`,
|
|
1525
|
-
subject
|
|
1526
|
-
)
|
|
1527
|
-
);
|
|
1528
|
-
}
|
|
1529
|
-
} else {
|
|
1530
|
-
for (const item of outside) {
|
|
1531
|
-
violations.push(
|
|
1532
|
-
makeViolation(
|
|
1533
|
-
rule,
|
|
1534
|
-
`'${item.dependency.from}' imports '${subject.path}'.`,
|
|
1535
|
-
sourceFor(graph, item.dependency),
|
|
1536
|
-
item.dependency,
|
|
1537
|
-
void 0,
|
|
1538
|
-
item.path
|
|
1539
|
-
)
|
|
1540
|
-
);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
return violations;
|
|
1545
|
-
}
|
|
1546
|
-
function evaluateDependencyRule(graph, rule, subjects) {
|
|
1547
|
-
switch (rule.kind) {
|
|
1548
|
-
case "depend-on":
|
|
1549
|
-
return evaluateDependOn(graph, rule, subjects);
|
|
1550
|
-
case "only-depend-on":
|
|
1551
|
-
return evaluateOnlyDependOn(graph, rule, subjects);
|
|
1552
|
-
case "depended-on-by":
|
|
1553
|
-
return evaluateDependedOnBy(graph, rule, subjects);
|
|
1554
|
-
case "only-depended-on-by":
|
|
1555
|
-
return evaluateOnlyDependedOnBy(graph, rule, subjects);
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
// src/analysis/evaluation/semantic.ts
|
|
1560
|
-
var import_node_path6 = __toESM(require("path"), 1);
|
|
1561
|
-
function namedDeclarationViolation(rule, sourceModule, declaration, message) {
|
|
1562
|
-
return makeViolation(rule, message, sourceModule, void 0, declaration);
|
|
1563
|
-
}
|
|
1564
|
-
function evaluateNameRule(rule, subjects) {
|
|
1565
|
-
return subjects.flatMap((subject) => {
|
|
1566
|
-
const fileName = import_node_path6.default.basename(subject.path);
|
|
1567
|
-
const matches = rule.kind === "prefix" ? fileName.startsWith(rule.value) : fileName.endsWith(rule.value);
|
|
1568
|
-
return matches !== rule.negated ? [] : [makeViolation(rule, `File name is '${fileName}'.`, subject)];
|
|
1569
|
-
});
|
|
1570
|
-
}
|
|
1571
|
-
function evaluateLineCountRule(rule, subjects) {
|
|
1572
|
-
return subjects.flatMap((subject) => {
|
|
1573
|
-
const matches = subject.lines < rule.value;
|
|
1574
|
-
return matches !== rule.negated ? [] : [makeViolation(rule, `File has ${subject.lines} lines.`, subject)];
|
|
1575
|
-
});
|
|
1576
|
-
}
|
|
1577
|
-
function evaluateDeclarationKindRule(rule, subjects) {
|
|
1578
|
-
return subjects.flatMap((subject) => {
|
|
1579
|
-
if (subject.declarations.length === 0) {
|
|
1580
|
-
return [makeViolation(rule, "File has no top-level declarations.", subject)];
|
|
1581
|
-
}
|
|
1582
|
-
return subject.declarations.flatMap((declaration) => {
|
|
1583
|
-
const matches = declaration.kind === rule.declarationKind;
|
|
1584
|
-
return matches !== rule.negated ? [] : [
|
|
1585
|
-
namedDeclarationViolation(
|
|
1586
|
-
rule,
|
|
1587
|
-
subject,
|
|
1588
|
-
declaration,
|
|
1589
|
-
`'${declaration.name}' is a ${declaration.kind}, not a ${rule.declarationKind}.`
|
|
1590
|
-
)
|
|
1591
|
-
];
|
|
1592
|
-
});
|
|
1593
|
-
});
|
|
1594
|
-
}
|
|
1595
|
-
function evaluateDeclarationModifierRule(rule, subjects) {
|
|
1596
|
-
return subjects.flatMap((subject) => {
|
|
1597
|
-
const declarations = subject.declarations.filter(
|
|
1598
|
-
(declaration) => rule.kind === "exported" || declaration.kind === "class"
|
|
1599
|
-
);
|
|
1600
|
-
if (declarations.length === 0) {
|
|
1601
|
-
return [
|
|
1602
|
-
makeViolation(rule, `File has no declarations applicable to '${rule.kind}'.`, subject)
|
|
1603
|
-
];
|
|
1604
|
-
}
|
|
1605
|
-
return declarations.flatMap((declaration) => {
|
|
1606
|
-
const matches = rule.kind === "abstract" ? declaration.abstract : subject.exports.includes(declaration.name) || declaration.defaultExport && subject.exports.includes("default");
|
|
1607
|
-
return matches !== rule.negated ? [] : [
|
|
1608
|
-
namedDeclarationViolation(
|
|
1609
|
-
rule,
|
|
1610
|
-
subject,
|
|
1611
|
-
declaration,
|
|
1612
|
-
`'${declaration.name}' is${matches ? "" : " not"} ${rule.kind}.`
|
|
1613
|
-
)
|
|
1614
|
-
];
|
|
1615
|
-
});
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
function evaluateHeritageRule(rule, subjects) {
|
|
1619
|
-
return subjects.flatMap((subject) => {
|
|
1620
|
-
const declarations = subject.declarations.filter(
|
|
1621
|
-
(declaration) => declaration.kind === "class" || declaration.kind === "interface"
|
|
1622
|
-
);
|
|
1623
|
-
if (declarations.length === 0) {
|
|
1624
|
-
return [makeViolation(rule, "File has no class or interface declarations.", subject)];
|
|
1625
|
-
}
|
|
1626
|
-
return declarations.flatMap((declaration) => {
|
|
1627
|
-
const values = rule.kind === "extend" ? declaration.extends : declaration.implements;
|
|
1628
|
-
const matches = values.some(
|
|
1629
|
-
(value) => value === rule.value || value.endsWith(`.${rule.value}`)
|
|
1630
|
-
);
|
|
1631
|
-
return matches !== rule.negated ? [] : [
|
|
1632
|
-
namedDeclarationViolation(
|
|
1633
|
-
rule,
|
|
1634
|
-
subject,
|
|
1635
|
-
declaration,
|
|
1636
|
-
`'${declaration.name}' ${rule.kind === "extend" ? "extends" : "implements"} ${values.join(", ") || "nothing"}.`
|
|
1637
|
-
)
|
|
1638
|
-
];
|
|
1639
|
-
});
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
function evaluateDecoratorRule(rule, subjects) {
|
|
1643
|
-
return subjects.flatMap((subject) => {
|
|
1644
|
-
const declarations = subject.declarations.filter((declaration) => declaration.kind === "class");
|
|
1645
|
-
if (declarations.length === 0) {
|
|
1646
|
-
return [makeViolation(rule, "File has no class declarations.", subject)];
|
|
1647
|
-
}
|
|
1648
|
-
return declarations.flatMap((declaration) => {
|
|
1649
|
-
const matches = declaration.decorators.some(
|
|
1650
|
-
(decorator) => decorator === rule.value || decorator.startsWith(`${rule.value}(`)
|
|
1651
|
-
);
|
|
1652
|
-
return matches !== rule.negated ? [] : [
|
|
1653
|
-
namedDeclarationViolation(
|
|
1654
|
-
rule,
|
|
1655
|
-
subject,
|
|
1656
|
-
declaration,
|
|
1657
|
-
`'${declaration.name}' has decorators ${declaration.decorators.join(", ") || "none"}.`
|
|
1658
|
-
)
|
|
1659
|
-
];
|
|
1660
|
-
});
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
function evaluateExportRule(rule, subjects) {
|
|
1664
|
-
return subjects.flatMap((subject) => {
|
|
1665
|
-
const outside = subject.exports.filter((name) => !rule.names.includes(name));
|
|
1666
|
-
if (rule.negated) {
|
|
1667
|
-
return outside.length === 0 ? [
|
|
1668
|
-
makeViolation(
|
|
1669
|
-
rule,
|
|
1670
|
-
`Every export matches ${rule.names.join(", ") || "nothing"}.`,
|
|
1671
|
-
subject
|
|
1672
|
-
)
|
|
1673
|
-
] : [];
|
|
1674
|
-
}
|
|
1675
|
-
return outside.map((name) => makeViolation(rule, `'${name}' is exported.`, subject));
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
function evaluateReadonlyRule(rule, subjects) {
|
|
1679
|
-
return subjects.flatMap((subject) => {
|
|
1680
|
-
const declarations = subject.declarations.filter((declaration) => declaration.kind === "class");
|
|
1681
|
-
if (declarations.length === 0) {
|
|
1682
|
-
return [makeViolation(rule, "File has no class declarations.", subject)];
|
|
1683
|
-
}
|
|
1684
|
-
return declarations.flatMap(
|
|
1685
|
-
(declaration) => declaration.readonlyProperties !== rule.negated ? [] : [
|
|
1686
|
-
namedDeclarationViolation(
|
|
1687
|
-
rule,
|
|
1688
|
-
subject,
|
|
1689
|
-
declaration,
|
|
1690
|
-
`'${declaration.name}' has mutable properties.`
|
|
1691
|
-
)
|
|
1692
|
-
]
|
|
1693
|
-
);
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
function evaluateStrictEqualityRule(rule, subjects) {
|
|
1697
|
-
return subjects.flatMap(
|
|
1698
|
-
(subject) => subject.uses.filter((use) => use.kind === "loose-equality").map(
|
|
1699
|
-
(use) => makeViolation(rule, `Uses loose equality operator '${use.name}'.`, subject, void 0, use)
|
|
1700
|
-
)
|
|
1701
|
-
);
|
|
1702
|
-
}
|
|
1703
|
-
function evaluateGlobalUseRule(rule, subjects) {
|
|
1704
|
-
return subjects.flatMap((subject) => {
|
|
1705
|
-
const matching = subject.uses.filter(
|
|
1706
|
-
(use) => use.kind === "call" && rule.globals.includes(use.name)
|
|
1707
|
-
);
|
|
1708
|
-
if (rule.negated) {
|
|
1709
|
-
return matching.map(
|
|
1710
|
-
(use) => makeViolation(rule, `Calls '${use.name}()'.`, subject, void 0, use)
|
|
1711
|
-
);
|
|
1712
|
-
}
|
|
1713
|
-
const present = new Set(matching.map((use) => use.name));
|
|
1714
|
-
return rule.globals.filter((name) => !present.has(name)).map((name) => makeViolation(rule, `Does not call '${name}()'.`, subject));
|
|
1715
|
-
});
|
|
1716
|
-
}
|
|
1717
|
-
function matchesImportedFrom(actual, expected) {
|
|
1718
|
-
return actual === expected || actual?.startsWith(`${expected}/`) === true;
|
|
1719
|
-
}
|
|
1720
|
-
function evaluateImportedSymbolUseRule(rule, subjects) {
|
|
1721
|
-
return subjects.flatMap((subject) => {
|
|
1722
|
-
const matching = subject.uses.filter(
|
|
1723
|
-
(use) => use.kind === "call" && matchesImportedFrom(use.importedFrom, rule.importedFrom) && use.importedName !== void 0 && rule.symbols.includes(use.importedName)
|
|
1724
|
-
);
|
|
1725
|
-
if (rule.negated) {
|
|
1726
|
-
return matching.map(
|
|
1727
|
-
(use) => makeViolation(
|
|
1728
|
-
rule,
|
|
1729
|
-
`Calls '${use.name}()' imported as '${use.importedName}' from '${use.importedFrom}'.`,
|
|
1730
|
-
subject,
|
|
1731
|
-
void 0,
|
|
1732
|
-
use
|
|
1733
|
-
)
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
1736
|
-
const present = new Set(matching.map((use) => use.importedName));
|
|
1737
|
-
return rule.symbols.filter((symbol) => !present.has(symbol)).map(
|
|
1738
|
-
(symbol) => makeViolation(
|
|
1739
|
-
rule,
|
|
1740
|
-
`Does not call '${symbol}()' imported from '${rule.importedFrom}'.`,
|
|
1741
|
-
subject
|
|
1742
|
-
)
|
|
1743
|
-
);
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
function evaluateMethodRule(rule, subjects) {
|
|
1747
|
-
return subjects.flatMap((subject) => {
|
|
1748
|
-
const declarations = subject.declarations.filter(
|
|
1749
|
-
(declaration) => declaration.kind === "class" || declaration.kind === "interface"
|
|
1750
|
-
);
|
|
1751
|
-
if (declarations.length === 0) {
|
|
1752
|
-
return [makeViolation(rule, "File has no class or interface declarations.", subject)];
|
|
1753
|
-
}
|
|
1754
|
-
return declarations.flatMap((declaration) => {
|
|
1755
|
-
const methods = declaration.methods.map((method) => method.name);
|
|
1756
|
-
const matches = rule.kind === "only-public-methods" ? declaration.methods.every((method) => method.visibility === "public") && (rule.names.length === 0 || methods.every((method) => rule.names.includes(method))) : rule.kind === "have-visible-methods" ? rule.names.every(
|
|
1757
|
-
(name) => declaration.methods.some(
|
|
1758
|
-
(method) => method.name === name && method.visibility === rule.visibility
|
|
1759
|
-
)
|
|
1760
|
-
) : rule.names.every((method) => methods.includes(method));
|
|
1761
|
-
return matches !== rule.negated ? [] : [
|
|
1762
|
-
namedDeclarationViolation(
|
|
1763
|
-
rule,
|
|
1764
|
-
subject,
|
|
1765
|
-
declaration,
|
|
1766
|
-
`'${declaration.name}' has methods ${methods.join(", ") || "none"}.`
|
|
1767
|
-
)
|
|
1768
|
-
];
|
|
1769
|
-
});
|
|
1770
|
-
});
|
|
1771
|
-
}
|
|
1772
|
-
function evaluateResolutionRule(rule, subjects) {
|
|
1773
|
-
return subjects.flatMap(
|
|
1774
|
-
(subject) => subject.dependencies.filter(
|
|
1775
|
-
(dependency) => !dependency.resolved && (!rule.allowDynamic || dependency.resolution !== "dynamic")
|
|
1776
|
-
).map(
|
|
1777
|
-
(dependency) => makeViolation(rule, `Cannot resolve '${dependency.specifier}'.`, subject, dependency)
|
|
1778
|
-
)
|
|
1779
|
-
);
|
|
1780
|
-
}
|
|
1781
|
-
function evaluatePublicApiRule(rule, subjects) {
|
|
1782
|
-
return subjects.flatMap((subject) => {
|
|
1783
|
-
const matches = publicApiFile(subject.path);
|
|
1784
|
-
return matches !== rule.negated ? [] : [makeViolation(rule, `'${import_node_path6.default.basename(subject.path)}' is not a public API file.`, subject)];
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
// src/analysis/evaluation/index.ts
|
|
1789
|
-
async function evaluateRule(graph, rule) {
|
|
1790
|
-
const subjects = selectedModules(graph, rule.selection);
|
|
1791
|
-
if (subjects.length === 0) {
|
|
1792
|
-
return [noMatchViolation(rule)];
|
|
1793
|
-
}
|
|
1794
|
-
switch (rule.kind) {
|
|
1795
|
-
case "depend-on":
|
|
1796
|
-
case "only-depend-on":
|
|
1797
|
-
case "depended-on-by":
|
|
1798
|
-
case "only-depended-on-by":
|
|
1799
|
-
return evaluateDependencyRule(graph, rule, subjects);
|
|
1800
|
-
case "free-of-cycles":
|
|
1801
|
-
return evaluateCycleRule(rule, subjects);
|
|
1802
|
-
case "prefix":
|
|
1803
|
-
case "suffix":
|
|
1804
|
-
return evaluateNameRule(rule, subjects);
|
|
1805
|
-
case "line-count-less-than":
|
|
1806
|
-
return evaluateLineCountRule(rule, subjects);
|
|
1807
|
-
case "declaration-kind":
|
|
1808
|
-
return evaluateDeclarationKindRule(rule, subjects);
|
|
1809
|
-
case "abstract":
|
|
1810
|
-
case "exported":
|
|
1811
|
-
return evaluateDeclarationModifierRule(rule, subjects);
|
|
1812
|
-
case "extend":
|
|
1813
|
-
case "implement":
|
|
1814
|
-
return evaluateHeritageRule(rule, subjects);
|
|
1815
|
-
case "decorator":
|
|
1816
|
-
return evaluateDecoratorRule(rule, subjects);
|
|
1817
|
-
case "only-export":
|
|
1818
|
-
return evaluateExportRule(rule, subjects);
|
|
1819
|
-
case "readonly-properties":
|
|
1820
|
-
return evaluateReadonlyRule(rule, subjects);
|
|
1821
|
-
case "strict-equality":
|
|
1822
|
-
return evaluateStrictEqualityRule(rule, subjects);
|
|
1823
|
-
case "use-globals":
|
|
1824
|
-
return evaluateGlobalUseRule(rule, subjects);
|
|
1825
|
-
case "use-imported-symbols":
|
|
1826
|
-
return evaluateImportedSymbolUseRule(rule, subjects);
|
|
1827
|
-
case "have-methods":
|
|
1828
|
-
case "have-visible-methods":
|
|
1829
|
-
case "only-public-methods":
|
|
1830
|
-
return evaluateMethodRule(rule, subjects);
|
|
1831
|
-
case "resolvable-dependencies":
|
|
1832
|
-
return evaluateResolutionRule(rule, subjects);
|
|
1833
|
-
case "public-api":
|
|
1834
|
-
return evaluatePublicApiRule(rule, subjects);
|
|
1835
|
-
case "custom": {
|
|
1836
|
-
const result = await rule.evaluator({ graph, modules: subjects });
|
|
1837
|
-
if (result === void 0) {
|
|
1838
|
-
return [];
|
|
1839
|
-
}
|
|
1840
|
-
const violations = Array.isArray(result) ? [...result] : [result];
|
|
1841
|
-
const normalized = [];
|
|
1842
|
-
for (const violation2 of violations) {
|
|
1843
|
-
normalized.push({
|
|
1844
|
-
...violation2,
|
|
1845
|
-
...rule.reason === void 0 ? {} : { reason: rule.reason },
|
|
1846
|
-
...rule.id === void 0 ? {} : { ruleId: rule.id },
|
|
1847
|
-
severity: rule.severity
|
|
1848
|
-
});
|
|
1849
|
-
}
|
|
1850
|
-
return normalized;
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// src/engine/suite.ts
|
|
1856
|
-
var presets = /* @__PURE__ */ new Map();
|
|
1857
|
-
function metadata(values = {}) {
|
|
1858
|
-
return {
|
|
1859
|
-
...values.id === void 0 ? {} : { id: values.id },
|
|
1860
|
-
...values.reason === void 0 ? {} : { reason: values.reason },
|
|
1861
|
-
severity: values.severity ?? "error"
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
function countResult(graph, violations) {
|
|
1865
|
-
const errors = violations.filter((item) => item.severity === "error").length;
|
|
1866
|
-
const warnings = violations.filter((item) => item.severity === "warning").length;
|
|
1867
|
-
const infos = violations.filter((item) => item.severity === "info").length;
|
|
1868
|
-
return {
|
|
1869
|
-
dependenciesAnalyzed: graph.dependencies.length,
|
|
1870
|
-
errors,
|
|
1871
|
-
filesAnalyzed: graph.modules.length,
|
|
1872
|
-
infos,
|
|
1873
|
-
ok: errors === 0,
|
|
1874
|
-
violations,
|
|
1875
|
-
warnings
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
|
-
var ArchitectureSuite = class {
|
|
1879
|
-
options;
|
|
1880
|
-
#rules = [];
|
|
1881
|
-
#lastRule;
|
|
1882
|
-
constructor(options = {}) {
|
|
1883
|
-
this.options = options;
|
|
1884
|
-
}
|
|
1885
|
-
expect(selector) {
|
|
1886
|
-
if (selectorValues(selector).length === 0) {
|
|
1887
|
-
throw new TypeError("expect() needs at least one selector.");
|
|
1888
|
-
}
|
|
1889
|
-
return new ArchExpectation(this, { ignored: [], selector });
|
|
1890
|
-
}
|
|
1891
|
-
layers(definitions) {
|
|
1892
|
-
return new ArchitectureLayers(this, definitions);
|
|
1893
|
-
}
|
|
1894
|
-
preset(name, options) {
|
|
1895
|
-
applyPreset(name, this, options);
|
|
1896
|
-
return this;
|
|
1897
|
-
}
|
|
1898
|
-
rule(id, evaluator, ruleMetadata = {}) {
|
|
1899
|
-
this.add({
|
|
1900
|
-
...metadata({ ...ruleMetadata, id }),
|
|
1901
|
-
evaluator,
|
|
1902
|
-
kind: "custom",
|
|
1903
|
-
selection: { ignored: [], selector: "**" }
|
|
1904
|
-
});
|
|
1905
|
-
return this;
|
|
1906
|
-
}
|
|
1907
|
-
add(rule) {
|
|
1908
|
-
this.#rules.push(rule);
|
|
1909
|
-
this.#lastRule = rule;
|
|
1910
|
-
}
|
|
1911
|
-
decorateLast(values) {
|
|
1912
|
-
if (this.#lastRule === void 0) {
|
|
1913
|
-
throw new TypeError("Declare a rule before adding metadata.");
|
|
1914
|
-
}
|
|
1915
|
-
if (values.id !== void 0) {
|
|
1916
|
-
this.#lastRule.id = values.id;
|
|
1917
|
-
}
|
|
1918
|
-
if (values.reason !== void 0) {
|
|
1919
|
-
this.#lastRule.reason = values.reason;
|
|
1920
|
-
}
|
|
1921
|
-
if (values.severity !== void 0) {
|
|
1922
|
-
this.#lastRule.severity = values.severity;
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
async check(graph) {
|
|
1926
|
-
const analyzed = graph ?? await analyze(this.options);
|
|
1927
|
-
if (this.#rules.length === 0) {
|
|
1928
|
-
return countResult(analyzed, [
|
|
1929
|
-
{
|
|
1930
|
-
message: "No architecture rules were declared.",
|
|
1931
|
-
rule: "an architecture test must declare at least one rule",
|
|
1932
|
-
severity: "error"
|
|
1933
|
-
}
|
|
1934
|
-
]);
|
|
1935
|
-
}
|
|
1936
|
-
const groups = await Promise.all(this.#rules.map(async (rule) => evaluateRule(analyzed, rule)));
|
|
1937
|
-
const violations = groups.flat().toSorted((left, right) => {
|
|
1938
|
-
const fileComparison = (left.file ?? "").localeCompare(right.file ?? "");
|
|
1939
|
-
if (fileComparison !== 0) {
|
|
1940
|
-
return fileComparison;
|
|
1941
|
-
}
|
|
1942
|
-
const lineComparison = (left.line ?? 0) - (right.line ?? 0);
|
|
1943
|
-
return lineComparison === 0 ? (left.ruleId ?? left.rule).localeCompare(right.ruleId ?? right.rule) : lineComparison;
|
|
1944
|
-
});
|
|
1945
|
-
return countResult(analyzed, violations);
|
|
1946
|
-
}
|
|
1947
|
-
};
|
|
1948
|
-
var ArchExpectation = class {
|
|
1949
|
-
#suite;
|
|
1950
|
-
#selection;
|
|
1951
|
-
#dependencyKinds;
|
|
1952
|
-
#localOnly = false;
|
|
1953
|
-
#negated = false;
|
|
1954
|
-
#transitive = false;
|
|
1955
|
-
constructor(suite, selection) {
|
|
1956
|
-
this.#suite = suite;
|
|
1957
|
-
this.#selection = selection;
|
|
1958
|
-
}
|
|
1959
|
-
get and() {
|
|
1960
|
-
return this;
|
|
1961
|
-
}
|
|
1962
|
-
get not() {
|
|
1963
|
-
this.#negated = !this.#negated;
|
|
1964
|
-
return this;
|
|
1965
|
-
}
|
|
1966
|
-
expect(selector) {
|
|
1967
|
-
return this.#suite.expect(selector);
|
|
1968
|
-
}
|
|
1969
|
-
ignoring(...selectors) {
|
|
1970
|
-
this.#selection.ignored.push(...selectors);
|
|
1971
|
-
return this;
|
|
1972
|
-
}
|
|
1973
|
-
includingDependencyKinds(...kinds) {
|
|
1974
|
-
if (kinds.length === 0) {
|
|
1975
|
-
throw new TypeError("includingDependencyKinds() needs at least one dependency kind.");
|
|
1976
|
-
}
|
|
1977
|
-
this.#dependencyKinds = [...new Set(kinds)];
|
|
1978
|
-
return this;
|
|
1979
|
-
}
|
|
1980
|
-
runtimeDependencies() {
|
|
1981
|
-
return this.includingDependencyKinds("static", "dynamic", "require");
|
|
1982
|
-
}
|
|
1983
|
-
typeDependencies() {
|
|
1984
|
-
return this.includingDependencyKinds("type");
|
|
1985
|
-
}
|
|
1986
|
-
dynamicDependencies() {
|
|
1987
|
-
return this.includingDependencyKinds("dynamic");
|
|
1988
|
-
}
|
|
1989
|
-
localDependencies() {
|
|
1990
|
-
this.#localOnly = true;
|
|
1991
|
-
return this;
|
|
1992
|
-
}
|
|
1993
|
-
transitively() {
|
|
1994
|
-
this.#transitive = true;
|
|
1995
|
-
return this;
|
|
1996
|
-
}
|
|
1997
|
-
toDependOn(target) {
|
|
1998
|
-
this.addDependencyRule("depend-on", target);
|
|
1999
|
-
return this;
|
|
2000
|
-
}
|
|
2001
|
-
toTransitivelyDependOn(target) {
|
|
2002
|
-
return this.transitively().toDependOn(target);
|
|
2003
|
-
}
|
|
2004
|
-
toUse(target) {
|
|
2005
|
-
return this.toDependOn(target);
|
|
2006
|
-
}
|
|
2007
|
-
toOnlyDependOn(target) {
|
|
2008
|
-
this.addDependencyRule("only-depend-on", target);
|
|
2009
|
-
return this;
|
|
2010
|
-
}
|
|
2011
|
-
toOnlyUse(target) {
|
|
2012
|
-
return this.toOnlyDependOn(target);
|
|
2013
|
-
}
|
|
2014
|
-
toUseNothing() {
|
|
2015
|
-
return this.toOnlyDependOn([]);
|
|
2016
|
-
}
|
|
2017
|
-
toBeDependedOnBy(target) {
|
|
2018
|
-
this.addDependencyRule("depended-on-by", target);
|
|
2019
|
-
return this;
|
|
2020
|
-
}
|
|
2021
|
-
toTransitivelyBeDependedOnBy(target) {
|
|
2022
|
-
return this.transitively().toBeDependedOnBy(target);
|
|
2023
|
-
}
|
|
2024
|
-
toBeUsedIn(target) {
|
|
2025
|
-
return this.toBeDependedOnBy(target);
|
|
2026
|
-
}
|
|
2027
|
-
toBeUsed() {
|
|
2028
|
-
return this.toBeDependedOnBy("**");
|
|
2029
|
-
}
|
|
2030
|
-
toOnlyBeDependedOnBy(target) {
|
|
2031
|
-
this.addDependencyRule("only-depended-on-by", target);
|
|
2032
|
-
return this;
|
|
2033
|
-
}
|
|
2034
|
-
toOnlyBeUsedIn(target) {
|
|
2035
|
-
return this.toOnlyBeDependedOnBy(target);
|
|
2036
|
-
}
|
|
2037
|
-
toBeFreeOfCycles(options = {}) {
|
|
2038
|
-
if (this.consumeNegation()) {
|
|
2039
|
-
throw new TypeError("toBeFreeOfCycles() cannot be negated.");
|
|
2040
|
-
}
|
|
2041
|
-
if (options.collapseToFolderDepth !== void 0 && options.collapseToFolderDepth < 1) {
|
|
2042
|
-
throw new TypeError("collapseToFolderDepth must be a positive integer.");
|
|
2043
|
-
}
|
|
2044
|
-
this.#suite.add({
|
|
2045
|
-
...metadata(),
|
|
2046
|
-
...options,
|
|
2047
|
-
kind: "free-of-cycles",
|
|
2048
|
-
selection: this.#selection
|
|
2049
|
-
});
|
|
2050
|
-
return this;
|
|
2051
|
-
}
|
|
2052
|
-
toBeFreeOfFolderCycles(depth = 1) {
|
|
2053
|
-
return this.toBeFreeOfCycles({ collapseToFolderDepth: depth });
|
|
2054
|
-
}
|
|
2055
|
-
toHavePrefix(value) {
|
|
2056
|
-
this.addSimple({ kind: "prefix", negated: this.consumeNegation(), value });
|
|
2057
|
-
return this;
|
|
2058
|
-
}
|
|
2059
|
-
toHaveSuffix(value) {
|
|
2060
|
-
this.addSimple({ kind: "suffix", negated: this.consumeNegation(), value });
|
|
2061
|
-
return this;
|
|
2062
|
-
}
|
|
2063
|
-
toHaveLineCountLessThan(value) {
|
|
2064
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
2065
|
-
throw new TypeError("toHaveLineCountLessThan() needs a positive integer.");
|
|
2066
|
-
}
|
|
2067
|
-
this.addSimple({ kind: "line-count-less-than", negated: this.consumeNegation(), value });
|
|
2068
|
-
return this;
|
|
2069
|
-
}
|
|
2070
|
-
toBeClasses() {
|
|
2071
|
-
return this.addDeclarationKind("class");
|
|
2072
|
-
}
|
|
2073
|
-
toBeEnums() {
|
|
2074
|
-
return this.addDeclarationKind("enum");
|
|
2075
|
-
}
|
|
2076
|
-
toBeFunctions() {
|
|
2077
|
-
return this.addDeclarationKind("function");
|
|
2078
|
-
}
|
|
2079
|
-
toBeInterfaces() {
|
|
2080
|
-
return this.addDeclarationKind("interface");
|
|
2081
|
-
}
|
|
2082
|
-
toBeTypes() {
|
|
2083
|
-
return this.addDeclarationKind("type");
|
|
2084
|
-
}
|
|
2085
|
-
toBeAbstract() {
|
|
2086
|
-
this.addSimple({ kind: "abstract", negated: this.consumeNegation() });
|
|
2087
|
-
return this;
|
|
2088
|
-
}
|
|
2089
|
-
toBeExported() {
|
|
2090
|
-
this.addSimple({ kind: "exported", negated: this.consumeNegation() });
|
|
2091
|
-
return this;
|
|
2092
|
-
}
|
|
2093
|
-
toExtend(value) {
|
|
2094
|
-
this.addSimple({ kind: "extend", negated: this.consumeNegation(), value });
|
|
2095
|
-
return this;
|
|
2096
|
-
}
|
|
2097
|
-
toImplement(value) {
|
|
2098
|
-
this.addSimple({ kind: "implement", negated: this.consumeNegation(), value });
|
|
2099
|
-
return this;
|
|
2100
|
-
}
|
|
2101
|
-
toHaveDecorator(value) {
|
|
2102
|
-
this.addSimple({ kind: "decorator", negated: this.consumeNegation(), value });
|
|
2103
|
-
return this;
|
|
2104
|
-
}
|
|
2105
|
-
toOnlyExport(...names) {
|
|
2106
|
-
this.addSimple({ kind: "only-export", names, negated: this.consumeNegation() });
|
|
2107
|
-
return this;
|
|
2108
|
-
}
|
|
2109
|
-
toHaveReadonlyProperties() {
|
|
2110
|
-
this.addSimple({ kind: "readonly-properties", negated: this.consumeNegation() });
|
|
2111
|
-
return this;
|
|
2112
|
-
}
|
|
2113
|
-
toUseStrictEquality() {
|
|
2114
|
-
if (this.consumeNegation()) {
|
|
2115
|
-
throw new TypeError("toUseStrictEquality() cannot be negated.");
|
|
2116
|
-
}
|
|
2117
|
-
this.addSimple({ kind: "strict-equality" });
|
|
2118
|
-
return this;
|
|
2119
|
-
}
|
|
2120
|
-
toUseGlobals(...globals) {
|
|
2121
|
-
if (globals.length === 0) {
|
|
2122
|
-
throw new TypeError("toUseGlobals() needs at least one global name.");
|
|
2123
|
-
}
|
|
2124
|
-
this.addSimple({ globals, kind: "use-globals", negated: this.consumeNegation() });
|
|
2125
|
-
return this;
|
|
2126
|
-
}
|
|
2127
|
-
toUseImportedSymbols(importedFrom, ...symbols) {
|
|
2128
|
-
if (importedFrom.trim() === "") {
|
|
2129
|
-
throw new TypeError("toUseImportedSymbols() needs a module specifier.");
|
|
2130
|
-
}
|
|
2131
|
-
if (symbols.length === 0) {
|
|
2132
|
-
throw new TypeError("toUseImportedSymbols() needs at least one imported symbol.");
|
|
2133
|
-
}
|
|
2134
|
-
this.addSimple({
|
|
2135
|
-
importedFrom,
|
|
2136
|
-
kind: "use-imported-symbols",
|
|
2137
|
-
negated: this.consumeNegation(),
|
|
2138
|
-
symbols
|
|
2139
|
-
});
|
|
2140
|
-
return this;
|
|
2141
|
-
}
|
|
2142
|
-
toHaveMethods(...names) {
|
|
2143
|
-
if (names.length === 0) {
|
|
2144
|
-
throw new TypeError("toHaveMethods() needs at least one method name.");
|
|
2145
|
-
}
|
|
2146
|
-
this.addSimple({ kind: "have-methods", names, negated: this.consumeNegation() });
|
|
2147
|
-
return this;
|
|
2148
|
-
}
|
|
2149
|
-
toHavePublicMethods(...names) {
|
|
2150
|
-
return this.addVisibleMethods("public", names);
|
|
2151
|
-
}
|
|
2152
|
-
toHaveProtectedMethods(...names) {
|
|
2153
|
-
return this.addVisibleMethods("protected", names);
|
|
2154
|
-
}
|
|
2155
|
-
toHavePrivateMethods(...names) {
|
|
2156
|
-
return this.addVisibleMethods("private", names);
|
|
2157
|
-
}
|
|
2158
|
-
toOnlyHavePublicMethods(...names) {
|
|
2159
|
-
this.addSimple({ kind: "only-public-methods", names, negated: this.consumeNegation() });
|
|
2160
|
-
return this;
|
|
2161
|
-
}
|
|
2162
|
-
toHaveResolvableDependencies(options = {}) {
|
|
2163
|
-
if (this.consumeNegation()) {
|
|
2164
|
-
throw new TypeError("toHaveResolvableDependencies() cannot be negated.");
|
|
2165
|
-
}
|
|
2166
|
-
this.addSimple({ allowDynamic: options.allowDynamic ?? false, kind: "resolvable-dependencies" });
|
|
2167
|
-
return this;
|
|
2168
|
-
}
|
|
2169
|
-
toBePublicApi() {
|
|
2170
|
-
this.addSimple({ kind: "public-api", negated: this.consumeNegation() });
|
|
2171
|
-
return this;
|
|
2172
|
-
}
|
|
2173
|
-
toSatisfy(id, evaluator, ruleMetadata = {}) {
|
|
2174
|
-
this.#suite.add({
|
|
2175
|
-
...metadata({ ...ruleMetadata, id }),
|
|
2176
|
-
evaluator,
|
|
2177
|
-
kind: "custom",
|
|
2178
|
-
selection: this.#selection
|
|
2179
|
-
});
|
|
2180
|
-
this.#negated = false;
|
|
2181
|
-
return this;
|
|
2182
|
-
}
|
|
2183
|
-
because(reason) {
|
|
2184
|
-
this.#suite.decorateLast({ reason });
|
|
2185
|
-
return this;
|
|
2186
|
-
}
|
|
2187
|
-
as(id) {
|
|
2188
|
-
this.#suite.decorateLast({ id });
|
|
2189
|
-
return this;
|
|
2190
|
-
}
|
|
2191
|
-
withSeverity(severity) {
|
|
2192
|
-
this.#suite.decorateLast({ severity });
|
|
2193
|
-
return this;
|
|
2194
|
-
}
|
|
2195
|
-
addDeclarationKind(declarationKind) {
|
|
2196
|
-
this.addSimple({
|
|
2197
|
-
declarationKind,
|
|
2198
|
-
kind: "declaration-kind",
|
|
2199
|
-
negated: this.consumeNegation()
|
|
2200
|
-
});
|
|
2201
|
-
return this;
|
|
2202
|
-
}
|
|
2203
|
-
addVisibleMethods(visibility2, names) {
|
|
2204
|
-
if (names.length === 0) {
|
|
2205
|
-
throw new TypeError(
|
|
2206
|
-
`toHave${visibility2[0]?.toUpperCase() ?? ""}${visibility2.slice(1)}Methods() needs at least one method name.`
|
|
2207
|
-
);
|
|
2208
|
-
}
|
|
2209
|
-
this.addSimple({
|
|
2210
|
-
kind: "have-visible-methods",
|
|
2211
|
-
names,
|
|
2212
|
-
negated: this.consumeNegation(),
|
|
2213
|
-
visibility: visibility2
|
|
2214
|
-
});
|
|
2215
|
-
return this;
|
|
2216
|
-
}
|
|
2217
|
-
addSimple(rule) {
|
|
2218
|
-
this.#suite.add({ ...metadata(), ...rule, selection: this.#selection });
|
|
2219
|
-
}
|
|
2220
|
-
consumeNegation() {
|
|
2221
|
-
const negated = this.#negated;
|
|
2222
|
-
this.#negated = false;
|
|
2223
|
-
return negated;
|
|
2224
|
-
}
|
|
2225
|
-
addDependencyRule(kind, target) {
|
|
2226
|
-
this.#suite.add({
|
|
2227
|
-
...metadata(),
|
|
2228
|
-
...this.#dependencyKinds === void 0 ? {} : { dependencyKinds: this.#dependencyKinds },
|
|
2229
|
-
kind,
|
|
2230
|
-
localOnly: this.#localOnly,
|
|
2231
|
-
negated: this.consumeNegation(),
|
|
2232
|
-
selection: this.#selection,
|
|
2233
|
-
target,
|
|
2234
|
-
transitive: this.#transitive
|
|
2235
|
-
});
|
|
2236
|
-
this.#dependencyKinds = void 0;
|
|
2237
|
-
this.#localOnly = false;
|
|
2238
|
-
this.#transitive = false;
|
|
2239
|
-
}
|
|
2240
|
-
};
|
|
2241
|
-
function createArcher(options = {}) {
|
|
2242
|
-
return new ArchitectureSuite(options);
|
|
2243
|
-
}
|
|
2244
|
-
presets.set("strict", (suite) => {
|
|
2245
|
-
suite.expect("**").toBeFreeOfCycles().as("strict/no-cycles").and.toHaveResolvableDependencies().as("strict/resolvable-dependencies").and.toUseStrictEquality().as("strict/strict-equality");
|
|
2246
|
-
});
|
|
2247
|
-
presets.set("node", (suite) => {
|
|
2248
|
-
suite.expect("**").toHaveResolvableDependencies().as("node/resolvable-dependencies");
|
|
2249
|
-
suite.expect("**").not.toDependOn(["node:domain", "node:punycode", "node:sys"]).as("node/no-deprecated-builtins");
|
|
2250
|
-
});
|
|
2251
|
-
presets.set("security", (suite) => {
|
|
2252
|
-
suite.expect("**").not.toUseGlobals("eval", "Function").as("security/no-dynamic-code");
|
|
2253
|
-
});
|
|
2254
|
-
function registerPreset(name, preset) {
|
|
2255
|
-
if (name.trim() === "") {
|
|
2256
|
-
throw new TypeError("registerPreset() needs a non-empty name.");
|
|
2257
|
-
}
|
|
2258
|
-
presets.set(name, preset);
|
|
2259
|
-
}
|
|
2260
|
-
function applyPreset(name, suite, options) {
|
|
2261
|
-
const preset = presets.get(name);
|
|
2262
|
-
if (preset === void 0) {
|
|
2263
|
-
throw new TypeError(`Unknown Archer preset '${name}'.`);
|
|
2264
|
-
}
|
|
2265
|
-
preset(suite, options);
|
|
2266
|
-
}
|
|
2267
|
-
function availablePresets() {
|
|
2268
|
-
return [...presets.keys()].toSorted();
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
// src/reporting/format.ts
|
|
2272
|
-
function formatLocation(violation2) {
|
|
2273
|
-
if (violation2.file === void 0) {
|
|
2274
|
-
return "";
|
|
2275
|
-
}
|
|
2276
|
-
if (violation2.line === void 0) {
|
|
2277
|
-
return violation2.file;
|
|
2278
|
-
}
|
|
2279
|
-
return `${violation2.file}:${violation2.line}:${violation2.column ?? 1}`;
|
|
2280
|
-
}
|
|
2281
|
-
function formatResult(result) {
|
|
2282
|
-
if (result.violations.length === 0) {
|
|
2283
|
-
return `Archer checked ${result.filesAnalyzed} files and ${result.dependenciesAnalyzed} dependencies.`;
|
|
2284
|
-
}
|
|
2285
|
-
const violations = result.violations.map((violation2) => {
|
|
2286
|
-
const location = formatLocation(violation2);
|
|
2287
|
-
const prefix = location.length > 0 ? ` ${location}
|
|
2288
|
-
` : " ";
|
|
2289
|
-
const source = violation2.sourceLine === void 0 ? "" : `
|
|
2290
|
-
${violation2.sourceLine.trim()}
|
|
2291
|
-
${" ".repeat(Math.max(0, (violation2.column ?? 1) - 1))}^`;
|
|
2292
|
-
const chain = violation2.importChain === void 0 || violation2.importChain.length < 3 ? "" : `
|
|
2293
|
-
Import chain: ${violation2.importChain.join(" -> ")}`;
|
|
2294
|
-
const reason = violation2.reason === void 0 ? "" : `
|
|
2295
|
-
Because: ${violation2.reason}`;
|
|
2296
|
-
const id = violation2.ruleId === void 0 ? "" : ` [${violation2.ruleId}]`;
|
|
2297
|
-
return `${prefix} ${violation2.severity.toUpperCase()}${id}: ${violation2.message}${source}
|
|
2298
|
-
Rule: ${violation2.rule}${reason}${chain}`;
|
|
2299
|
-
});
|
|
2300
|
-
return `${result.violations.length} architecture finding${result.violations.length === 1 ? "" : "s"}:
|
|
2301
|
-
|
|
2302
|
-
${violations.join("\n\n")}`;
|
|
2303
|
-
}
|
|
2304
|
-
var ArchitectureError = class extends Error {
|
|
2305
|
-
result;
|
|
2306
|
-
constructor(result) {
|
|
2307
|
-
super(formatResult(result));
|
|
2308
|
-
this.name = "ArchitectureError";
|
|
2309
|
-
this.result = result;
|
|
2310
|
-
}
|
|
2311
|
-
};
|
|
2312
|
-
|
|
2313
|
-
// src/reporting/reporters.ts
|
|
2314
|
-
function singleLine(value) {
|
|
2315
|
-
return value.replaceAll(/\s+/gu, " ").trim();
|
|
2316
|
-
}
|
|
2317
|
-
function agent(result) {
|
|
2318
|
-
const status = result.errors > 0 ? "fail" : result.warnings > 0 ? "warn" : result.infos > 0 ? "info" : "ok";
|
|
2319
|
-
const summary = [
|
|
2320
|
-
`archer: ${status}`,
|
|
2321
|
-
`files=${result.filesAnalyzed}`,
|
|
2322
|
-
`dependencies=${result.dependenciesAnalyzed}`,
|
|
2323
|
-
`errors=${result.errors}`,
|
|
2324
|
-
`warnings=${result.warnings}`,
|
|
2325
|
-
`infos=${result.infos}`
|
|
2326
|
-
].join(" ");
|
|
2327
|
-
const findings = result.violations.map((violation2) => {
|
|
2328
|
-
const location = violation2.file === void 0 ? "architecture" : `${violation2.file}:${violation2.line ?? 1}:${violation2.column ?? 1}`;
|
|
2329
|
-
const fields = [
|
|
2330
|
-
`${location}: ${violation2.severity} ${singleLine(ruleKey(violation2))}: ${singleLine(violation2.message)}`,
|
|
2331
|
-
`rule=${JSON.stringify(singleLine(violation2.rule))}`,
|
|
2332
|
-
...violation2.reason === void 0 ? [] : [`reason=${JSON.stringify(singleLine(violation2.reason))}`],
|
|
2333
|
-
...violation2.dependency === void 0 ? [] : [`dependency=${JSON.stringify(violation2.dependency)}`],
|
|
2334
|
-
...violation2.sourceLine === void 0 ? [] : [`source=${JSON.stringify(singleLine(violation2.sourceLine))}`],
|
|
2335
|
-
...violation2.importChain === void 0 ? [] : [`import_chain=${JSON.stringify(violation2.importChain)}`]
|
|
2336
|
-
];
|
|
2337
|
-
return fields.join(" | ");
|
|
2338
|
-
});
|
|
2339
|
-
return [summary, ...findings].join("\n");
|
|
2340
|
-
}
|
|
2341
|
-
function sarifLevel(severity) {
|
|
2342
|
-
return severity === "info" ? "note" : severity;
|
|
2343
|
-
}
|
|
2344
|
-
function ruleKey(violation2) {
|
|
2345
|
-
return violation2.ruleId ?? violation2.rule;
|
|
2346
|
-
}
|
|
2347
|
-
function sarif(result) {
|
|
2348
|
-
const rules = [
|
|
2349
|
-
...new Map(
|
|
2350
|
-
result.violations.map((violation2) => [
|
|
2351
|
-
ruleKey(violation2),
|
|
2352
|
-
{
|
|
2353
|
-
id: ruleKey(violation2),
|
|
2354
|
-
shortDescription: { text: violation2.rule }
|
|
2355
|
-
}
|
|
2356
|
-
])
|
|
2357
|
-
).values()
|
|
2358
|
-
];
|
|
2359
|
-
const results = result.violations.map((violation2) => ({
|
|
2360
|
-
level: sarifLevel(violation2.severity),
|
|
2361
|
-
message: { text: violation2.message },
|
|
2362
|
-
...violation2.file === void 0 ? {} : {
|
|
2363
|
-
locations: [
|
|
2364
|
-
{
|
|
2365
|
-
physicalLocation: {
|
|
2366
|
-
artifactLocation: { uri: violation2.file },
|
|
2367
|
-
...violation2.line === void 0 ? {} : {
|
|
2368
|
-
region: {
|
|
2369
|
-
startColumn: violation2.column ?? 1,
|
|
2370
|
-
startLine: violation2.line
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
]
|
|
2376
|
-
},
|
|
2377
|
-
ruleId: ruleKey(violation2)
|
|
2378
|
-
}));
|
|
2379
|
-
return JSON.stringify(
|
|
2380
|
-
{
|
|
2381
|
-
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
2382
|
-
runs: [{ results, tool: { driver: { name: "Archer", rules } } }],
|
|
2383
|
-
version: "2.1.0"
|
|
2384
|
-
},
|
|
2385
|
-
void 0,
|
|
2386
|
-
2
|
|
2387
|
-
);
|
|
2388
|
-
}
|
|
2389
|
-
function annotationValue(value) {
|
|
2390
|
-
return value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A").replaceAll(":", "%3A").replaceAll(",", "%2C");
|
|
2391
|
-
}
|
|
2392
|
-
function github(result) {
|
|
2393
|
-
if (result.violations.length === 0) {
|
|
2394
|
-
return formatResult(result);
|
|
2395
|
-
}
|
|
2396
|
-
return result.violations.map((violation2) => {
|
|
2397
|
-
const level = violation2.severity === "info" ? "notice" : violation2.severity;
|
|
2398
|
-
const attributes = [
|
|
2399
|
-
...violation2.file === void 0 ? [] : [`file=${annotationValue(violation2.file)}`],
|
|
2400
|
-
...violation2.line === void 0 ? [] : [`line=${violation2.line}`],
|
|
2401
|
-
...violation2.column === void 0 ? [] : [`col=${violation2.column}`],
|
|
2402
|
-
`title=${annotationValue(ruleKey(violation2))}`
|
|
2403
|
-
];
|
|
2404
|
-
return `::${level} ${attributes.join(",")}::${annotationValue(violation2.message)}`;
|
|
2405
|
-
}).join("\n");
|
|
2406
|
-
}
|
|
2407
|
-
function dotEscape(value) {
|
|
2408
|
-
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
2409
|
-
}
|
|
2410
|
-
function dot(graph) {
|
|
2411
|
-
const lines = ["digraph archer {", " rankdir=LR;"];
|
|
2412
|
-
for (const sourceModule of graph.modules) {
|
|
2413
|
-
lines.push(` "${dotEscape(sourceModule.path)}" [shape=box];`);
|
|
2414
|
-
}
|
|
2415
|
-
for (const dependency of graph.dependencies) {
|
|
2416
|
-
const style = dependency.kind === "type" ? ",style=dashed" : "";
|
|
2417
|
-
lines.push(
|
|
2418
|
-
` "${dotEscape(dependency.from)}" -> "${dotEscape(dependency.target)}" [label="${dependency.kind}"${style}];`
|
|
2419
|
-
);
|
|
2420
|
-
}
|
|
2421
|
-
lines.push("}");
|
|
2422
|
-
return lines.join("\n");
|
|
2423
|
-
}
|
|
2424
|
-
function report(name, result, graph) {
|
|
2425
|
-
switch (name) {
|
|
2426
|
-
case "agent":
|
|
2427
|
-
case "llm":
|
|
2428
|
-
return agent(result);
|
|
2429
|
-
case "pretty":
|
|
2430
|
-
return formatResult(result);
|
|
2431
|
-
case "json":
|
|
2432
|
-
return JSON.stringify(result, void 0, 2);
|
|
2433
|
-
case "sarif":
|
|
2434
|
-
return sarif(result);
|
|
2435
|
-
case "github":
|
|
2436
|
-
return github(result);
|
|
2437
|
-
case "dot":
|
|
2438
|
-
if (graph === void 0) {
|
|
2439
|
-
throw new TypeError("The DOT reporter needs an architecture graph.");
|
|
2440
|
-
}
|
|
2441
|
-
return dot(graph);
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
// src/testing/matchers.ts
|
|
2446
|
-
function isArchitectureCheck(value) {
|
|
2447
|
-
return typeof value === "object" && value !== null && "check" in value && typeof value.check === "function";
|
|
2448
|
-
}
|
|
2449
|
-
async function toRespectArchitecture(received) {
|
|
2450
|
-
if (!isArchitectureCheck(received)) {
|
|
2451
|
-
throw new TypeError(
|
|
2452
|
-
"toRespectArchitecture() expects an Archer suite created by createArcher() or arch()."
|
|
2453
|
-
);
|
|
2454
|
-
}
|
|
2455
|
-
const result = await received.check();
|
|
2456
|
-
return {
|
|
2457
|
-
pass: result.ok,
|
|
2458
|
-
message: () => result.ok ? "Expected the architecture check to report at least one finding." : formatResult(result)
|
|
2459
|
-
};
|
|
2460
|
-
}
|
|
2461
|
-
var archerMatchers = { toRespectArchitecture };
|
|
2462
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2463
|
-
0 && (module.exports = {
|
|
2464
|
-
ArchExpectation,
|
|
2465
|
-
ArchitectureError,
|
|
2466
|
-
ArchitectureLayers,
|
|
2467
|
-
ArchitectureSuite,
|
|
2468
|
-
LayerExpectation,
|
|
2469
|
-
analyze,
|
|
2470
|
-
applyBaseline,
|
|
2471
|
-
applyPreset,
|
|
2472
|
-
archerMatchers,
|
|
2473
|
-
availablePresets,
|
|
2474
|
-
clearAnalysisCache,
|
|
2475
|
-
createArcher,
|
|
2476
|
-
createBaseline,
|
|
2477
|
-
defineConfig,
|
|
2478
|
-
formatResult,
|
|
2479
|
-
readBaseline,
|
|
2480
|
-
registerPreset,
|
|
2481
|
-
report,
|
|
2482
|
-
violationFingerprint,
|
|
2483
|
-
writeBaseline
|
|
2484
|
-
});
|