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