@ontrails/warden 1.0.0-beta.4 → 1.0.0-beta.42

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 (279) hide show
  1. package/CHANGELOG.md +837 -13
  2. package/README.md +133 -26
  3. package/bin/warden.ts +51 -0
  4. package/package.json +27 -6
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +1570 -105
  7. package/src/command.ts +986 -0
  8. package/src/config.ts +193 -0
  9. package/src/draft.ts +22 -0
  10. package/src/drift.ts +233 -23
  11. package/src/fix.ts +126 -0
  12. package/src/formatters.ts +78 -13
  13. package/src/guide.ts +245 -0
  14. package/src/index.ts +247 -15
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/resolve.ts +531 -0
  18. package/src/rules/activation-orphan.ts +97 -0
  19. package/src/rules/captured-kernel.ts +375 -0
  20. package/src/rules/circular-refs.ts +150 -0
  21. package/src/rules/cli-command-route-coherence.ts +177 -0
  22. package/src/rules/composes-declarations.ts +839 -0
  23. package/src/rules/context-no-surface-types.ts +79 -15
  24. package/src/rules/dead-internal-trail.ts +161 -0
  25. package/src/rules/dead-public-trail.ts +258 -0
  26. package/src/rules/draft-file-marking.ts +155 -0
  27. package/src/rules/draft-visible-debt.ts +82 -0
  28. package/src/rules/duplicate-exported-symbol.ts +211 -0
  29. package/src/rules/duplicate-public-contract.ts +137 -0
  30. package/src/rules/entity-exists.ts +254 -0
  31. package/src/rules/entity-ids.ts +15 -0
  32. package/src/rules/error-mapping-completeness.ts +290 -0
  33. package/src/rules/example-valid.ts +395 -0
  34. package/src/rules/fires-declarations.ts +740 -0
  35. package/src/rules/governed-symbol-residue.ts +438 -0
  36. package/src/rules/implementation-returns-result.ts +1409 -166
  37. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  38. package/src/rules/incomplete-crud.ts +583 -0
  39. package/src/rules/index.ts +277 -10
  40. package/src/rules/intent-propagation.ts +125 -0
  41. package/src/rules/layer-field-name-drift.ts +102 -0
  42. package/src/rules/library-projection-coherence.ts +100 -0
  43. package/src/rules/metadata.ts +871 -0
  44. package/src/rules/missing-reconcile.ts +97 -0
  45. package/src/rules/missing-visibility.ts +111 -0
  46. package/src/rules/no-destructured-compose.ts +196 -0
  47. package/src/rules/no-dev-permit-in-source.ts +99 -0
  48. package/src/rules/no-direct-implementation-call.ts +12 -7
  49. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  50. package/src/rules/no-legacy-layer-imports.ts +211 -0
  51. package/src/rules/no-native-error-result.ts +118 -0
  52. package/src/rules/no-redundant-result-error-wrap.ts +384 -0
  53. package/src/rules/no-retired-cross-vocabulary.ts +204 -0
  54. package/src/rules/no-sync-result-assumption.ts +1141 -98
  55. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  56. package/src/rules/no-throw-in-implementation.ts +15 -8
  57. package/src/rules/no-top-level-surface.ts +371 -0
  58. package/src/rules/on-references-exist.ts +194 -0
  59. package/src/rules/orphaned-signal.ts +149 -0
  60. package/src/rules/owner-projection-parity.ts +143 -0
  61. package/src/rules/permit-governance.ts +25 -0
  62. package/src/rules/public-export-example-coverage.ts +573 -0
  63. package/src/rules/public-internal-deep-imports.ts +456 -0
  64. package/src/rules/public-output-schema.ts +29 -0
  65. package/src/rules/public-union-output-discriminants.ts +150 -0
  66. package/src/rules/read-intent-fires.ts +188 -0
  67. package/src/rules/reference-exists.ts +97 -0
  68. package/src/rules/registry-names.ts +167 -0
  69. package/src/rules/resolved-import-boundary.ts +146 -0
  70. package/src/rules/resource-declarations.ts +697 -0
  71. package/src/rules/resource-exists.ts +181 -0
  72. package/src/rules/resource-id-grammar.ts +65 -0
  73. package/src/rules/resource-mock-coverage.ts +115 -0
  74. package/src/rules/retired-vocabulary.ts +633 -0
  75. package/src/rules/scan.ts +38 -25
  76. package/src/rules/scheduled-destroy-intent.ts +44 -0
  77. package/src/rules/signal-graph-coaching.ts +220 -0
  78. package/src/rules/source/composition.ts +165 -0
  79. package/src/rules/source/drafts.ts +164 -0
  80. package/src/rules/source/entities.ts +618 -0
  81. package/src/rules/source/pragmas.ts +45 -0
  82. package/src/rules/source/resources.ts +64 -0
  83. package/src/rules/source/signals.ts +397 -0
  84. package/src/rules/source/stores.ts +310 -0
  85. package/src/rules/specs.ts +9 -5
  86. package/src/rules/static-resource-accessor-preference.ts +653 -0
  87. package/src/rules/surface-overlay-coherence.ts +262 -0
  88. package/src/rules/surface-trailhead-coherence.ts +366 -0
  89. package/src/rules/trail-fork-coaching.ts +625 -0
  90. package/src/rules/trail-versioning-source.ts +1076 -0
  91. package/src/rules/trail-versioning-topo.ts +172 -0
  92. package/src/rules/trailhead-override-divergence.ts +356 -0
  93. package/src/rules/types.ts +354 -8
  94. package/src/rules/unmaterialized-activation-source.ts +85 -0
  95. package/src/rules/unreachable-detour-shadowing.ts +339 -0
  96. package/src/rules/valid-describe-refs.ts +162 -32
  97. package/src/rules/valid-detour-contract.ts +78 -0
  98. package/src/rules/warden-export-symmetry.ts +540 -0
  99. package/src/rules/warden-rules-use-ast.ts +1109 -0
  100. package/src/rules/webhook-route-collision.ts +306 -0
  101. package/src/trails/activation-orphan.trail.ts +84 -0
  102. package/src/trails/captured-kernel.trail.ts +108 -0
  103. package/src/trails/circular-refs.trail.ts +29 -0
  104. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  105. package/src/trails/composes-declarations.trail.ts +22 -0
  106. package/src/trails/context-no-surface-types.trail.ts +21 -0
  107. package/src/trails/dead-internal-trail.trail.ts +26 -0
  108. package/src/trails/dead-public-trail.trail.ts +31 -0
  109. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  110. package/src/trails/draft-file-marking.trail.ts +16 -0
  111. package/src/trails/draft-visible-debt.trail.ts +16 -0
  112. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  113. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  114. package/src/trails/entity-exists.trail.ts +21 -0
  115. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  116. package/src/trails/example-valid.trail.ts +25 -0
  117. package/src/trails/fires-declarations.trail.ts +23 -0
  118. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  119. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  120. package/src/trails/implementation-returns-result.trail.ts +20 -0
  121. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  122. package/src/trails/incomplete-crud.trail.ts +39 -0
  123. package/src/trails/index.ts +89 -0
  124. package/src/trails/intent-propagation.trail.ts +30 -0
  125. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  126. package/src/trails/library-projection-coherence.trail.ts +43 -0
  127. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  128. package/src/trails/missing-reconcile.trail.ts +33 -0
  129. package/src/trails/missing-visibility.trail.ts +22 -0
  130. package/src/trails/no-destructured-compose.trail.ts +44 -0
  131. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  132. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  133. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  134. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  135. package/src/trails/no-native-error-result.trail.ts +18 -0
  136. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  137. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  138. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  139. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  140. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  141. package/src/trails/no-top-level-surface.trail.ts +43 -0
  142. package/src/trails/on-references-exist.trail.ts +21 -0
  143. package/src/trails/orphaned-signal.trail.ts +36 -0
  144. package/src/trails/owner-projection-parity.trail.ts +26 -0
  145. package/src/trails/pending-force.trail.ts +21 -0
  146. package/src/trails/permit-governance.trail.ts +51 -0
  147. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  148. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  149. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  150. package/src/trails/public-output-schema.trail.ts +55 -0
  151. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  152. package/src/trails/read-intent-fires.trail.ts +20 -0
  153. package/src/trails/reference-exists.trail.ts +25 -0
  154. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  155. package/src/trails/resource-declarations.trail.ts +25 -0
  156. package/src/trails/resource-exists.trail.ts +27 -0
  157. package/src/trails/resource-id-grammar.trail.ts +39 -0
  158. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  159. package/src/trails/run.ts +160 -0
  160. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  161. package/src/trails/schema.ts +237 -0
  162. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  163. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  164. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  165. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  166. package/src/trails/topo.ts +6 -0
  167. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  168. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  169. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  170. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  171. package/src/trails/valid-describe-refs.trail.ts +18 -0
  172. package/src/trails/valid-detour-contract.trail.ts +71 -0
  173. package/src/trails/version-gap.trail.ts +35 -0
  174. package/src/trails/version-pinned-compose.trail.ts +23 -0
  175. package/src/trails/version-without-examples.trail.ts +38 -0
  176. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  177. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  178. package/src/trails/webhook-route-collision.trail.ts +50 -0
  179. package/src/trails/wrap-rule.ts +224 -0
  180. package/src/workspaces.ts +199 -0
  181. package/.turbo/turbo-build.log +0 -1
  182. package/.turbo/turbo-lint.log +0 -3
  183. package/.turbo/turbo-typecheck.log +0 -1
  184. package/dist/cli.d.ts +0 -46
  185. package/dist/cli.d.ts.map +0 -1
  186. package/dist/cli.js +0 -218
  187. package/dist/cli.js.map +0 -1
  188. package/dist/drift.d.ts +0 -26
  189. package/dist/drift.d.ts.map +0 -1
  190. package/dist/drift.js +0 -27
  191. package/dist/drift.js.map +0 -1
  192. package/dist/formatters.d.ts +0 -29
  193. package/dist/formatters.d.ts.map +0 -1
  194. package/dist/formatters.js +0 -87
  195. package/dist/formatters.js.map +0 -1
  196. package/dist/index.d.ts +0 -26
  197. package/dist/index.d.ts.map +0 -1
  198. package/dist/index.js +0 -26
  199. package/dist/index.js.map +0 -1
  200. package/dist/rules/ast.d.ts +0 -41
  201. package/dist/rules/ast.d.ts.map +0 -1
  202. package/dist/rules/ast.js +0 -161
  203. package/dist/rules/ast.js.map +0 -1
  204. package/dist/rules/context-no-surface-types.d.ts +0 -12
  205. package/dist/rules/context-no-surface-types.d.ts.map +0 -1
  206. package/dist/rules/context-no-surface-types.js +0 -96
  207. package/dist/rules/context-no-surface-types.js.map +0 -1
  208. package/dist/rules/implementation-returns-result.d.ts +0 -13
  209. package/dist/rules/implementation-returns-result.d.ts.map +0 -1
  210. package/dist/rules/implementation-returns-result.js +0 -277
  211. package/dist/rules/implementation-returns-result.js.map +0 -1
  212. package/dist/rules/index.d.ts +0 -15
  213. package/dist/rules/index.d.ts.map +0 -1
  214. package/dist/rules/index.js +0 -34
  215. package/dist/rules/index.js.map +0 -1
  216. package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
  217. package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
  218. package/dist/rules/no-direct-impl-in-route.js +0 -47
  219. package/dist/rules/no-direct-impl-in-route.js.map +0 -1
  220. package/dist/rules/no-direct-implementation-call.d.ts +0 -12
  221. package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
  222. package/dist/rules/no-direct-implementation-call.js +0 -39
  223. package/dist/rules/no-direct-implementation-call.js.map +0 -1
  224. package/dist/rules/no-sync-result-assumption.d.ts +0 -6
  225. package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
  226. package/dist/rules/no-sync-result-assumption.js +0 -98
  227. package/dist/rules/no-sync-result-assumption.js.map +0 -1
  228. package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
  229. package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
  230. package/dist/rules/no-throw-in-detour-target.js +0 -87
  231. package/dist/rules/no-throw-in-detour-target.js.map +0 -1
  232. package/dist/rules/no-throw-in-implementation.d.ts +0 -9
  233. package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
  234. package/dist/rules/no-throw-in-implementation.js +0 -34
  235. package/dist/rules/no-throw-in-implementation.js.map +0 -1
  236. package/dist/rules/prefer-schema-inference.d.ts +0 -7
  237. package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
  238. package/dist/rules/prefer-schema-inference.js +0 -86
  239. package/dist/rules/prefer-schema-inference.js.map +0 -1
  240. package/dist/rules/scan.d.ts +0 -8
  241. package/dist/rules/scan.d.ts.map +0 -1
  242. package/dist/rules/scan.js +0 -32
  243. package/dist/rules/scan.js.map +0 -1
  244. package/dist/rules/specs.d.ts +0 -29
  245. package/dist/rules/specs.d.ts.map +0 -1
  246. package/dist/rules/specs.js +0 -192
  247. package/dist/rules/specs.js.map +0 -1
  248. package/dist/rules/structure.d.ts +0 -13
  249. package/dist/rules/structure.d.ts.map +0 -1
  250. package/dist/rules/structure.js +0 -142
  251. package/dist/rules/structure.js.map +0 -1
  252. package/dist/rules/types.d.ts +0 -52
  253. package/dist/rules/types.d.ts.map +0 -1
  254. package/dist/rules/types.js +0 -2
  255. package/dist/rules/types.js.map +0 -1
  256. package/dist/rules/valid-describe-refs.d.ts +0 -7
  257. package/dist/rules/valid-describe-refs.d.ts.map +0 -1
  258. package/dist/rules/valid-describe-refs.js +0 -51
  259. package/dist/rules/valid-describe-refs.js.map +0 -1
  260. package/dist/rules/valid-detour-refs.d.ts +0 -6
  261. package/dist/rules/valid-detour-refs.d.ts.map +0 -1
  262. package/dist/rules/valid-detour-refs.js +0 -116
  263. package/dist/rules/valid-detour-refs.js.map +0 -1
  264. package/src/__tests__/cli.test.ts +0 -198
  265. package/src/__tests__/drift.test.ts +0 -74
  266. package/src/__tests__/formatters.test.ts +0 -157
  267. package/src/__tests__/implementation-returns-result.test.ts +0 -129
  268. package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
  269. package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
  270. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  271. package/src/__tests__/prefer-schema-inference.test.ts +0 -84
  272. package/src/__tests__/rules.test.ts +0 -227
  273. package/src/__tests__/valid-describe-refs.test.ts +0 -60
  274. package/src/rules/ast.ts +0 -213
  275. package/src/rules/no-direct-impl-in-route.ts +0 -81
  276. package/src/rules/no-throw-in-detour-target.ts +0 -150
  277. package/src/rules/valid-detour-refs.ts +0 -187
  278. package/tsconfig.json +0 -9
  279. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Project-context helpers shared by the Warden runner and resolver-backed
3
+ * rules.
4
+ */
5
+
6
+ import { realpathSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+
9
+ import {
10
+ collectImportResolutionsForFile,
11
+ createWardenResolver,
12
+ normalizePath,
13
+ } from './resolve.js';
14
+ import type {
15
+ WardenImportResolution,
16
+ WardenResolverOptions,
17
+ } from './resolve.js';
18
+ import {
19
+ getNodeBodyStatements,
20
+ getNodeDeclaration,
21
+ getNodeDeclarations,
22
+ getNodeExported,
23
+ getNodeExportKind,
24
+ getNodeId,
25
+ getNodeLocal,
26
+ getNodeName,
27
+ getNodeSource,
28
+ getNodeSpecifiers,
29
+ getNodeValue,
30
+ isDeclarationWithId,
31
+ isExportNamedDeclaration,
32
+ isVariableDeclaration,
33
+ offsetToLine,
34
+ parse,
35
+ } from '@ontrails/source';
36
+ import type { AstNode } from '@ontrails/source';
37
+ import type { WardenExportedSymbolDefinition } from './rules/types.js';
38
+ import { collectPublicWorkspaces } from './workspaces.js';
39
+ import type { WardenPublicWorkspace } from './workspaces.js';
40
+
41
+ const ONTRAILS_DOCUMENTATION_SPECIFIER_PATTERN =
42
+ /@ontrails\/[a-z0-9-]+(?:\/[A-Za-z0-9._~-]+)+/g;
43
+
44
+ const normalizeRealPath = (path: string): string => {
45
+ try {
46
+ return normalizePath(realpathSync(path));
47
+ } catch {
48
+ return normalizePath(resolve(path));
49
+ }
50
+ };
51
+
52
+ const setResolutionsForFile = (
53
+ resolutionsByFile: Map<string, readonly WardenImportResolution[]>,
54
+ sourceFilePath: string,
55
+ resolutions: readonly WardenImportResolution[]
56
+ ): void => {
57
+ const normalizedFilePath =
58
+ resolutions[0]?.importerPath ?? normalizeRealPath(sourceFilePath);
59
+ resolutionsByFile.set(normalizedFilePath, resolutions);
60
+ if (normalizedFilePath !== sourceFilePath) {
61
+ resolutionsByFile.set(sourceFilePath, resolutions);
62
+ }
63
+ };
64
+
65
+ export interface WardenProjectContextSourceFile {
66
+ readonly filePath: string;
67
+ readonly kind: 'documentation' | 'text' | 'typescript';
68
+ readonly sourceCode: string;
69
+ }
70
+
71
+ const collectDocumentationImportSpecifiers = (
72
+ sourceCode: string
73
+ ): readonly { readonly importSource: string; readonly line: number }[] => {
74
+ const specifiers: { importSource: string; line: number }[] = [];
75
+ for (const match of sourceCode.matchAll(
76
+ ONTRAILS_DOCUMENTATION_SPECIFIER_PATTERN
77
+ )) {
78
+ if (match.index === undefined) {
79
+ continue;
80
+ }
81
+ specifiers.push({
82
+ importSource: match[0],
83
+ line: offsetToLine(sourceCode, match.index),
84
+ });
85
+ }
86
+ return specifiers;
87
+ };
88
+
89
+ const exportAliasesForWorkspaces = (
90
+ workspaces: ReadonlyMap<string, WardenPublicWorkspace>
91
+ ): Record<string, string[]> => {
92
+ const aliases: Record<string, string[]> = {};
93
+ for (const workspace of workspaces.values()) {
94
+ for (const [specifier, target] of Object.entries(
95
+ workspace.exportTargets ?? {}
96
+ )) {
97
+ aliases[`${specifier}$`] = [target];
98
+ }
99
+ }
100
+ return aliases;
101
+ };
102
+
103
+ const resolveOptionsWithWorkspaceAliases = (
104
+ publicWorkspaces: ReadonlyMap<string, WardenPublicWorkspace> | undefined,
105
+ resolveOptions: WardenResolverOptions['resolveOptions'] | undefined
106
+ ): WardenResolverOptions['resolveOptions'] => {
107
+ if (!publicWorkspaces) {
108
+ return resolveOptions;
109
+ }
110
+
111
+ const workspaceAliases = exportAliasesForWorkspaces(publicWorkspaces);
112
+ return {
113
+ ...resolveOptions,
114
+ alias: {
115
+ ...workspaceAliases,
116
+ ...resolveOptions?.alias,
117
+ },
118
+ };
119
+ };
120
+
121
+ export const collectProjectImportResolutions = ({
122
+ publicWorkspaces,
123
+ resolveOptions,
124
+ rootDir,
125
+ sourceFiles,
126
+ }: {
127
+ readonly publicWorkspaces?: ReadonlyMap<string, WardenPublicWorkspace>;
128
+ readonly resolveOptions?: WardenResolverOptions['resolveOptions'];
129
+ readonly rootDir: string;
130
+ readonly sourceFiles: readonly WardenProjectContextSourceFile[];
131
+ }): ReadonlyMap<string, readonly WardenImportResolution[]> => {
132
+ const resolver = createWardenResolver({
133
+ resolveOptions: resolveOptionsWithWorkspaceAliases(
134
+ publicWorkspaces,
135
+ resolveOptions
136
+ ),
137
+ rootDir,
138
+ });
139
+ const resolutionsByFile = new Map<
140
+ string,
141
+ readonly WardenImportResolution[]
142
+ >();
143
+
144
+ for (const sourceFile of sourceFiles) {
145
+ if (sourceFile.kind !== 'typescript') {
146
+ continue;
147
+ }
148
+ const resolutions = collectImportResolutionsForFile({
149
+ filePath: sourceFile.filePath,
150
+ resolver,
151
+ sourceCode: sourceFile.sourceCode,
152
+ });
153
+ if (resolutions.length > 0) {
154
+ setResolutionsForFile(
155
+ resolutionsByFile,
156
+ sourceFile.filePath,
157
+ resolutions
158
+ );
159
+ }
160
+ }
161
+
162
+ return resolutionsByFile;
163
+ };
164
+
165
+ export const collectProjectDocumentationImportResolutions = ({
166
+ publicWorkspaces: providedPublicWorkspaces,
167
+ rootDir,
168
+ sourceFiles,
169
+ }: {
170
+ readonly publicWorkspaces?: ReadonlyMap<string, WardenPublicWorkspace>;
171
+ readonly rootDir: string;
172
+ readonly sourceFiles: readonly WardenProjectContextSourceFile[];
173
+ }): ReadonlyMap<string, readonly WardenImportResolution[]> => {
174
+ const publicWorkspaces =
175
+ providedPublicWorkspaces ?? collectPublicWorkspaces(rootDir);
176
+ const resolver = createWardenResolver({
177
+ resolveOptions: { alias: exportAliasesForWorkspaces(publicWorkspaces) },
178
+ rootDir,
179
+ });
180
+ const resolutionsByFile = new Map<
181
+ string,
182
+ readonly WardenImportResolution[]
183
+ >();
184
+
185
+ for (const sourceFile of sourceFiles) {
186
+ if (sourceFile.kind !== 'documentation') {
187
+ continue;
188
+ }
189
+ const resolutions = collectDocumentationImportSpecifiers(
190
+ sourceFile.sourceCode
191
+ ).map((specifier) =>
192
+ resolver.resolveImport(
193
+ sourceFile.filePath,
194
+ specifier.importSource,
195
+ specifier.line
196
+ )
197
+ );
198
+ if (resolutions.length > 0) {
199
+ setResolutionsForFile(
200
+ resolutionsByFile,
201
+ sourceFile.filePath,
202
+ resolutions
203
+ );
204
+ }
205
+ }
206
+
207
+ return resolutionsByFile;
208
+ };
209
+
210
+ const exportedKindForDeclaration = (
211
+ declaration: AstNode
212
+ ): WardenExportedSymbolDefinition['kind'] | null => {
213
+ if (declaration.type === 'ClassDeclaration') {
214
+ return 'class';
215
+ }
216
+ if (declaration.type === 'FunctionDeclaration') {
217
+ return 'function';
218
+ }
219
+ if (
220
+ declaration.type === 'EnumDeclaration' ||
221
+ declaration.type === 'TSEnumDeclaration'
222
+ ) {
223
+ return 'enum';
224
+ }
225
+ if (
226
+ declaration.type === 'InterfaceDeclaration' ||
227
+ declaration.type === 'TSInterfaceDeclaration'
228
+ ) {
229
+ return 'interface';
230
+ }
231
+ if (declaration.type === 'TSTypeAliasDeclaration') {
232
+ return 'type';
233
+ }
234
+ if (isVariableDeclaration(declaration)) {
235
+ return 'const';
236
+ }
237
+ return null;
238
+ };
239
+
240
+ const publicExportTargetWorkspacesByPath = (
241
+ publicWorkspaces: ReadonlyMap<string, WardenPublicWorkspace>
242
+ ): ReadonlyMap<string, WardenPublicWorkspace> => {
243
+ const workspacesByTargetPath = new Map<string, WardenPublicWorkspace>();
244
+ for (const workspace of publicWorkspaces.values()) {
245
+ for (const target of Object.values(workspace.exportTargets ?? {})) {
246
+ workspacesByTargetPath.set(normalizeRealPath(target), workspace);
247
+ }
248
+ }
249
+ return workspacesByTargetPath;
250
+ };
251
+
252
+ const readNameNode = (node: AstNode | undefined): string | null => {
253
+ if (!node) {
254
+ return null;
255
+ }
256
+ if (node.type === 'Identifier') {
257
+ return getNodeName(node) ?? null;
258
+ }
259
+ if (node.type === 'Literal' || node.type === 'StringLiteral') {
260
+ const value = getNodeValue(node);
261
+ return typeof value === 'string' ? value : null;
262
+ }
263
+ return null;
264
+ };
265
+
266
+ const exportedSpecifierKind = (
267
+ statement: AstNode,
268
+ specifier: AstNode
269
+ ): WardenExportedSymbolDefinition['kind'] =>
270
+ getNodeExportKind(statement) === 'type' ||
271
+ getNodeExportKind(specifier) === 'type'
272
+ ? 'type'
273
+ : 'export';
274
+
275
+ const reexportedDefinitions = ({
276
+ filePath,
277
+ sourceCode,
278
+ statement,
279
+ workspace,
280
+ }: {
281
+ readonly filePath: string;
282
+ readonly sourceCode: string;
283
+ readonly statement: AstNode;
284
+ readonly workspace: WardenPublicWorkspace;
285
+ }): readonly WardenExportedSymbolDefinition[] => {
286
+ const specifiers = getNodeSpecifiers(statement) ?? [];
287
+ return specifiers.flatMap((specifier) => {
288
+ if (specifier.type !== 'ExportSpecifier') {
289
+ return [];
290
+ }
291
+ const exported = getNodeExported(specifier);
292
+ const local = getNodeLocal(specifier);
293
+ const name = readNameNode(exported) ?? readNameNode(local);
294
+ return name
295
+ ? [
296
+ {
297
+ filePath,
298
+ kind: exportedSpecifierKind(statement, specifier),
299
+ line: offsetToLine(sourceCode, specifier.start),
300
+ name,
301
+ workspaceName: workspace.name,
302
+ workspaceRoot: workspace.rootDir,
303
+ } satisfies WardenExportedSymbolDefinition,
304
+ ]
305
+ : [];
306
+ });
307
+ };
308
+
309
+ const namedDeclarationDefinitions = ({
310
+ declaration,
311
+ filePath,
312
+ sourceCode,
313
+ workspace,
314
+ }: {
315
+ readonly declaration: AstNode;
316
+ readonly filePath: string;
317
+ readonly sourceCode: string;
318
+ readonly workspace: WardenPublicWorkspace;
319
+ }): readonly WardenExportedSymbolDefinition[] => {
320
+ const kind = exportedKindForDeclaration(declaration);
321
+ if (!kind) {
322
+ return [];
323
+ }
324
+
325
+ if (isVariableDeclaration(declaration)) {
326
+ return getNodeDeclarations(declaration).flatMap((declarator) => {
327
+ const name = getNodeName(getNodeId(declarator));
328
+ return name
329
+ ? [
330
+ {
331
+ filePath,
332
+ kind,
333
+ line: offsetToLine(sourceCode, declarator.start),
334
+ name,
335
+ workspaceName: workspace.name,
336
+ workspaceRoot: workspace.rootDir,
337
+ } satisfies WardenExportedSymbolDefinition,
338
+ ]
339
+ : [];
340
+ });
341
+ }
342
+
343
+ if (!isDeclarationWithId(declaration)) {
344
+ return [];
345
+ }
346
+
347
+ const name = getNodeName(getNodeId(declaration));
348
+ return name
349
+ ? [
350
+ {
351
+ filePath,
352
+ kind,
353
+ line: offsetToLine(sourceCode, declaration.start),
354
+ name,
355
+ workspaceName: workspace.name,
356
+ workspaceRoot: workspace.rootDir,
357
+ } satisfies WardenExportedSymbolDefinition,
358
+ ]
359
+ : [];
360
+ };
361
+
362
+ const collectExportedSymbolDefinitionsForFile = (
363
+ sourceFile: WardenProjectContextSourceFile,
364
+ publicExportTargetWorkspaces: ReadonlyMap<string, WardenPublicWorkspace>
365
+ ): readonly WardenExportedSymbolDefinition[] => {
366
+ if (sourceFile.kind !== 'typescript') {
367
+ return [];
368
+ }
369
+
370
+ const workspace = publicExportTargetWorkspaces.get(
371
+ normalizeRealPath(sourceFile.filePath)
372
+ );
373
+ if (!workspace) {
374
+ return [];
375
+ }
376
+
377
+ const ast = parse(sourceFile.filePath, sourceFile.sourceCode);
378
+ if (!ast) {
379
+ return [];
380
+ }
381
+
382
+ return getNodeBodyStatements(ast).flatMap((statement) => {
383
+ if (!isExportNamedDeclaration(statement)) {
384
+ return [];
385
+ }
386
+ if (getNodeSource(statement) || getNodeSpecifiers(statement)?.length) {
387
+ return reexportedDefinitions({
388
+ filePath: sourceFile.filePath,
389
+ sourceCode: sourceFile.sourceCode,
390
+ statement,
391
+ workspace,
392
+ });
393
+ }
394
+ const declaration = getNodeDeclaration(statement);
395
+ return declaration
396
+ ? namedDeclarationDefinitions({
397
+ declaration,
398
+ filePath: sourceFile.filePath,
399
+ sourceCode: sourceFile.sourceCode,
400
+ workspace,
401
+ })
402
+ : [];
403
+ });
404
+ };
405
+
406
+ export const collectProjectExportedSymbolDefinitions = ({
407
+ publicWorkspaces: providedPublicWorkspaces,
408
+ rootDir,
409
+ sourceFiles,
410
+ }: {
411
+ readonly publicWorkspaces?: ReadonlyMap<string, WardenPublicWorkspace>;
412
+ readonly rootDir: string;
413
+ readonly sourceFiles: readonly WardenProjectContextSourceFile[];
414
+ }): ReadonlyMap<string, readonly WardenExportedSymbolDefinition[]> => {
415
+ const publicWorkspaces =
416
+ providedPublicWorkspaces ?? collectPublicWorkspaces(rootDir);
417
+ const publicExportTargetWorkspaces =
418
+ publicExportTargetWorkspacesByPath(publicWorkspaces);
419
+ const definitionsByName = new Map<string, WardenExportedSymbolDefinition[]>();
420
+
421
+ for (const sourceFile of sourceFiles) {
422
+ for (const definition of collectExportedSymbolDefinitionsForFile(
423
+ sourceFile,
424
+ publicExportTargetWorkspaces
425
+ )) {
426
+ const existing = definitionsByName.get(definition.name) ?? [];
427
+ existing.push(definition);
428
+ definitionsByName.set(definition.name, existing);
429
+ }
430
+ }
431
+
432
+ return new Map(
433
+ [...definitionsByName.entries()].map(([name, definitions]) => [
434
+ name,
435
+ definitions.toSorted(
436
+ (left, right) =>
437
+ left.workspaceName.localeCompare(right.workspaceName) ||
438
+ left.filePath.localeCompare(right.filePath) ||
439
+ left.line - right.line
440
+ ),
441
+ ])
442
+ );
443
+ };
444
+
445
+ export { collectPublicWorkspaces };
446
+ export type { WardenPublicWorkspace } from './workspaces.js';