@intentius/chant 0.0.1

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 (271) hide show
  1. package/README.md +365 -0
  2. package/package.json +22 -0
  3. package/src/attrref.test.ts +148 -0
  4. package/src/attrref.ts +50 -0
  5. package/src/barrel.test.ts +157 -0
  6. package/src/barrel.ts +101 -0
  7. package/src/bench.test.ts +227 -0
  8. package/src/build.test.ts +437 -0
  9. package/src/build.ts +425 -0
  10. package/src/builder.test.ts +312 -0
  11. package/src/builder.ts +56 -0
  12. package/src/child-project.ts +44 -0
  13. package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
  14. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
  15. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
  16. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
  17. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
  18. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
  19. package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
  20. package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
  21. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
  22. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
  23. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
  24. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
  25. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
  26. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
  27. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
  28. package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
  29. package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
  30. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
  31. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
  32. package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
  33. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
  34. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
  35. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
  36. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
  37. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
  38. package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
  39. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
  40. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
  41. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
  42. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
  43. package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
  44. package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
  45. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
  46. package/src/cli/commands/build.test.ts +149 -0
  47. package/src/cli/commands/build.ts +344 -0
  48. package/src/cli/commands/diff.test.ts +148 -0
  49. package/src/cli/commands/diff.ts +221 -0
  50. package/src/cli/commands/doctor.test.ts +239 -0
  51. package/src/cli/commands/doctor.ts +224 -0
  52. package/src/cli/commands/import.test.ts +379 -0
  53. package/src/cli/commands/import.ts +335 -0
  54. package/src/cli/commands/init-lexicon.test.ts +297 -0
  55. package/src/cli/commands/init-lexicon.ts +993 -0
  56. package/src/cli/commands/init.test.ts +317 -0
  57. package/src/cli/commands/init.ts +505 -0
  58. package/src/cli/commands/licenses.ts +165 -0
  59. package/src/cli/commands/lint.test.ts +332 -0
  60. package/src/cli/commands/lint.ts +408 -0
  61. package/src/cli/commands/list.test.ts +100 -0
  62. package/src/cli/commands/list.ts +108 -0
  63. package/src/cli/commands/update.test.ts +38 -0
  64. package/src/cli/commands/update.ts +207 -0
  65. package/src/cli/conflict-check.test.ts +255 -0
  66. package/src/cli/conflict-check.ts +89 -0
  67. package/src/cli/debug.ts +8 -0
  68. package/src/cli/format.test.ts +140 -0
  69. package/src/cli/format.ts +133 -0
  70. package/src/cli/handlers/build.ts +58 -0
  71. package/src/cli/handlers/dev.ts +38 -0
  72. package/src/cli/handlers/init.ts +46 -0
  73. package/src/cli/handlers/lint.ts +36 -0
  74. package/src/cli/handlers/misc.ts +57 -0
  75. package/src/cli/handlers/serve.ts +26 -0
  76. package/src/cli/index.ts +3 -0
  77. package/src/cli/lsp/capabilities.ts +46 -0
  78. package/src/cli/lsp/diagnostics.ts +52 -0
  79. package/src/cli/lsp/server.test.ts +618 -0
  80. package/src/cli/lsp/server.ts +393 -0
  81. package/src/cli/main.test.ts +257 -0
  82. package/src/cli/main.ts +224 -0
  83. package/src/cli/mcp/resources/context.ts +59 -0
  84. package/src/cli/mcp/server.test.ts +747 -0
  85. package/src/cli/mcp/server.ts +402 -0
  86. package/src/cli/mcp/tools/build.ts +117 -0
  87. package/src/cli/mcp/tools/import.ts +48 -0
  88. package/src/cli/mcp/tools/lint.ts +45 -0
  89. package/src/cli/plugins.test.ts +31 -0
  90. package/src/cli/plugins.ts +94 -0
  91. package/src/cli/registry.ts +73 -0
  92. package/src/cli/reporters/stylish.test.ts +282 -0
  93. package/src/cli/reporters/stylish.ts +186 -0
  94. package/src/cli/watch.test.ts +81 -0
  95. package/src/cli/watch.ts +101 -0
  96. package/src/codegen/case.test.ts +30 -0
  97. package/src/codegen/case.ts +11 -0
  98. package/src/codegen/coverage.ts +167 -0
  99. package/src/codegen/docs.ts +634 -0
  100. package/src/codegen/fetch.test.ts +119 -0
  101. package/src/codegen/fetch.ts +261 -0
  102. package/src/codegen/generate-registry.test.ts +118 -0
  103. package/src/codegen/generate-registry.ts +107 -0
  104. package/src/codegen/generate-runtime-index.test.ts +81 -0
  105. package/src/codegen/generate-runtime-index.ts +99 -0
  106. package/src/codegen/generate-typescript.test.ts +146 -0
  107. package/src/codegen/generate-typescript.ts +161 -0
  108. package/src/codegen/generate.ts +206 -0
  109. package/src/codegen/json-patch.test.ts +113 -0
  110. package/src/codegen/json-patch.ts +151 -0
  111. package/src/codegen/json-schema.test.ts +196 -0
  112. package/src/codegen/json-schema.ts +209 -0
  113. package/src/codegen/naming.ts +201 -0
  114. package/src/codegen/package.ts +161 -0
  115. package/src/codegen/rollback.test.ts +92 -0
  116. package/src/codegen/rollback.ts +115 -0
  117. package/src/codegen/topo-sort.test.ts +69 -0
  118. package/src/codegen/topo-sort.ts +46 -0
  119. package/src/codegen/typecheck.test.ts +37 -0
  120. package/src/codegen/typecheck.ts +74 -0
  121. package/src/codegen/validate.test.ts +86 -0
  122. package/src/codegen/validate.ts +143 -0
  123. package/src/composite.test.ts +426 -0
  124. package/src/composite.ts +243 -0
  125. package/src/config.test.ts +91 -0
  126. package/src/config.ts +87 -0
  127. package/src/declarable.test.ts +160 -0
  128. package/src/declarable.ts +47 -0
  129. package/src/detectLexicon.test.ts +236 -0
  130. package/src/detectLexicon.ts +37 -0
  131. package/src/discovery/cache.test.ts +78 -0
  132. package/src/discovery/cache.ts +86 -0
  133. package/src/discovery/collect.test.ts +269 -0
  134. package/src/discovery/collect.ts +51 -0
  135. package/src/discovery/cycles.test.ts +238 -0
  136. package/src/discovery/cycles.ts +107 -0
  137. package/src/discovery/files.test.ts +154 -0
  138. package/src/discovery/files.ts +61 -0
  139. package/src/discovery/graph.test.ts +476 -0
  140. package/src/discovery/graph.ts +150 -0
  141. package/src/discovery/import.test.ts +199 -0
  142. package/src/discovery/import.ts +20 -0
  143. package/src/discovery/index.test.ts +272 -0
  144. package/src/discovery/index.ts +132 -0
  145. package/src/discovery/resolve.test.ts +267 -0
  146. package/src/discovery/resolve.ts +54 -0
  147. package/src/errors.test.ts +138 -0
  148. package/src/errors.ts +86 -0
  149. package/src/import/base-parser.test.ts +67 -0
  150. package/src/import/base-parser.ts +48 -0
  151. package/src/import/generator.ts +21 -0
  152. package/src/import/ir-utils.test.ts +103 -0
  153. package/src/import/ir-utils.ts +87 -0
  154. package/src/import/parser.ts +41 -0
  155. package/src/index.ts +60 -0
  156. package/src/intrinsic-interpolation.test.ts +91 -0
  157. package/src/intrinsic-interpolation.ts +89 -0
  158. package/src/intrinsic.test.ts +69 -0
  159. package/src/intrinsic.ts +43 -0
  160. package/src/lexicon-integrity.test.ts +94 -0
  161. package/src/lexicon-integrity.ts +69 -0
  162. package/src/lexicon-manifest.test.ts +101 -0
  163. package/src/lexicon-manifest.ts +71 -0
  164. package/src/lexicon-output.test.ts +182 -0
  165. package/src/lexicon-output.ts +82 -0
  166. package/src/lexicon-schema.test.ts +239 -0
  167. package/src/lexicon-schema.ts +144 -0
  168. package/src/lexicon.ts +212 -0
  169. package/src/lint/config-overrides.test.ts +254 -0
  170. package/src/lint/config.test.ts +644 -0
  171. package/src/lint/config.ts +375 -0
  172. package/src/lint/declarative.test.ts +256 -0
  173. package/src/lint/declarative.ts +187 -0
  174. package/src/lint/engine.test.ts +465 -0
  175. package/src/lint/engine.ts +172 -0
  176. package/src/lint/named-checks.test.ts +37 -0
  177. package/src/lint/named-checks.ts +33 -0
  178. package/src/lint/parser.test.ts +129 -0
  179. package/src/lint/parser.ts +42 -0
  180. package/src/lint/post-synth.test.ts +113 -0
  181. package/src/lint/post-synth.ts +76 -0
  182. package/src/lint/presets/relaxed.json +19 -0
  183. package/src/lint/presets/strict.json +19 -0
  184. package/src/lint/rule-loader.test.ts +67 -0
  185. package/src/lint/rule-loader.ts +67 -0
  186. package/src/lint/rule-options.test.ts +141 -0
  187. package/src/lint/rule.test.ts +196 -0
  188. package/src/lint/rule.ts +98 -0
  189. package/src/lint/rules/barrel-import-style.test.ts +80 -0
  190. package/src/lint/rules/barrel-import-style.ts +59 -0
  191. package/src/lint/rules/composite-scope.ts +55 -0
  192. package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
  193. package/src/lint/rules/cor017-composite-name-match.ts +108 -0
  194. package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
  195. package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
  196. package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
  197. package/src/lint/rules/declarable-naming-convention.ts +70 -0
  198. package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
  199. package/src/lint/rules/enforce-barrel-import.ts +81 -0
  200. package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
  201. package/src/lint/rules/enforce-barrel-ref.ts +75 -0
  202. package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
  203. package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
  204. package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
  205. package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
  206. package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
  207. package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
  208. package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
  209. package/src/lint/rules/evl004-spread-non-const.ts +111 -0
  210. package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
  211. package/src/lint/rules/evl005-resource-block-body.ts +49 -0
  212. package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
  213. package/src/lint/rules/evl006-barrel-usage.ts +95 -0
  214. package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
  215. package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
  216. package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
  217. package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
  218. package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
  219. package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
  220. package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
  221. package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
  222. package/src/lint/rules/export-required.test.ts +213 -0
  223. package/src/lint/rules/export-required.ts +158 -0
  224. package/src/lint/rules/file-declarable-limit.test.ts +148 -0
  225. package/src/lint/rules/file-declarable-limit.ts +96 -0
  226. package/src/lint/rules/flat-declarations.test.ts +210 -0
  227. package/src/lint/rules/flat-declarations.ts +70 -0
  228. package/src/lint/rules/index.ts +99 -0
  229. package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
  230. package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
  231. package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
  232. package/src/lint/rules/no-redundant-type-import.ts +85 -0
  233. package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
  234. package/src/lint/rules/no-redundant-value-cast.ts +46 -0
  235. package/src/lint/rules/no-string-ref.test.ts +100 -0
  236. package/src/lint/rules/no-string-ref.ts +66 -0
  237. package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
  238. package/src/lint/rules/no-unused-declarable-import.ts +103 -0
  239. package/src/lint/rules/no-unused-declarable.test.ts +134 -0
  240. package/src/lint/rules/no-unused-declarable.ts +118 -0
  241. package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
  242. package/src/lint/rules/prefer-namespace-import.ts +63 -0
  243. package/src/lint/rules/single-concern-file.test.ts +156 -0
  244. package/src/lint/rules/single-concern-file.ts +98 -0
  245. package/src/lint/rules/stale-barrel-types.ts +60 -0
  246. package/src/lint/selectors.test.ts +113 -0
  247. package/src/lint/selectors.ts +188 -0
  248. package/src/lsp/lexicon-providers.ts +191 -0
  249. package/src/lsp/types.ts +79 -0
  250. package/src/mcp/types.ts +22 -0
  251. package/src/project/scan.test.ts +178 -0
  252. package/src/project/scan.ts +182 -0
  253. package/src/project/sync.test.ts +87 -0
  254. package/src/project/sync.ts +46 -0
  255. package/src/project-validation.test.ts +64 -0
  256. package/src/project-validation.ts +79 -0
  257. package/src/pseudo-parameter.test.ts +39 -0
  258. package/src/pseudo-parameter.ts +47 -0
  259. package/src/runtime.ts +68 -0
  260. package/src/serializer-walker.test.ts +124 -0
  261. package/src/serializer-walker.ts +83 -0
  262. package/src/serializer.ts +42 -0
  263. package/src/sort.test.ts +290 -0
  264. package/src/sort.ts +58 -0
  265. package/src/stack-output.ts +82 -0
  266. package/src/types.test.ts +307 -0
  267. package/src/types.ts +46 -0
  268. package/src/utils.test.ts +195 -0
  269. package/src/utils.ts +46 -0
  270. package/src/validation.test.ts +308 -0
  271. package/src/validation.ts +50 -0
@@ -0,0 +1,267 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { resolveAttrRefs } from "./resolve";
3
+ import { AttrRef } from "../attrref";
4
+ import type { Declarable } from "../declarable";
5
+ import { DECLARABLE_MARKER } from "../declarable";
6
+ import { LOGICAL_NAME_SYMBOL, getLogicalName } from "../utils";
7
+
8
+ describe("resolveAttrRefs", () => {
9
+ test("sets logical names on all entities", () => {
10
+ const entity1: Declarable = {
11
+ entityType: "Test1",
12
+ [DECLARABLE_MARKER]: true,
13
+ };
14
+
15
+ const entity2: Declarable = {
16
+ entityType: "Test2",
17
+ [DECLARABLE_MARKER]: true,
18
+ };
19
+
20
+ const entities = new Map<string, Declarable>([
21
+ ["MyEntity1", entity1],
22
+ ["MyEntity2", entity2],
23
+ ]);
24
+
25
+ resolveAttrRefs(entities);
26
+
27
+ expect(getLogicalName(entity1)).toBe("MyEntity1");
28
+ expect(getLogicalName(entity2)).toBe("MyEntity2");
29
+ });
30
+
31
+ test("resolves AttrRef with parent in entities collection", () => {
32
+ const parent: Declarable = {
33
+ entityType: "Parent",
34
+ [DECLARABLE_MARKER]: true,
35
+ };
36
+
37
+ const child: Declarable & { ref: AttrRef } = {
38
+ entityType: "Child",
39
+ [DECLARABLE_MARKER]: true,
40
+ ref: new AttrRef(parent, "Arn"),
41
+ };
42
+
43
+ const entities = new Map<string, Declarable>([
44
+ ["ParentResource", parent],
45
+ ["ChildResource", child],
46
+ ]);
47
+
48
+ resolveAttrRefs(entities);
49
+
50
+ // Verify the AttrRef can now serialize
51
+ expect(child.ref.toJSON()).toEqual({
52
+ __attrRef: { entity: "ParentResource", attribute: "Arn" },
53
+ });
54
+ });
55
+
56
+ test("resolves multiple AttrRefs on same entity", () => {
57
+ const parent: Declarable = {
58
+ entityType: "Parent",
59
+ [DECLARABLE_MARKER]: true,
60
+ };
61
+
62
+ const child: Declarable & { arn: AttrRef; name: AttrRef } = {
63
+ entityType: "Child",
64
+ [DECLARABLE_MARKER]: true,
65
+ arn: new AttrRef(parent, "Arn"),
66
+ name: new AttrRef(parent, "Name"),
67
+ };
68
+
69
+ const entities = new Map<string, Declarable>([
70
+ ["ParentResource", parent],
71
+ ["ChildResource", child],
72
+ ]);
73
+
74
+ resolveAttrRefs(entities);
75
+
76
+ expect(child.arn.toJSON()).toEqual({
77
+ __attrRef: { entity: "ParentResource", attribute: "Arn" },
78
+ });
79
+ expect(child.name.toJSON()).toEqual({
80
+ __attrRef: { entity: "ParentResource", attribute: "Name" },
81
+ });
82
+ });
83
+
84
+ test("resolves AttrRefs with different parents", () => {
85
+ const parent1: Declarable = {
86
+ entityType: "Parent1",
87
+ [DECLARABLE_MARKER]: true,
88
+ };
89
+
90
+ const parent2: Declarable = {
91
+ entityType: "Parent2",
92
+ [DECLARABLE_MARKER]: true,
93
+ };
94
+
95
+ const child: Declarable & { ref1: AttrRef; ref2: AttrRef } = {
96
+ entityType: "Child",
97
+ [DECLARABLE_MARKER]: true,
98
+ ref1: new AttrRef(parent1, "Arn"),
99
+ ref2: new AttrRef(parent2, "Name"),
100
+ };
101
+
102
+ const entities = new Map<string, Declarable>([
103
+ ["Parent1Resource", parent1],
104
+ ["Parent2Resource", parent2],
105
+ ["ChildResource", child],
106
+ ]);
107
+
108
+ resolveAttrRefs(entities);
109
+
110
+ expect(child.ref1.toJSON()).toEqual({
111
+ __attrRef: { entity: "Parent1Resource", attribute: "Arn" },
112
+ });
113
+ expect(child.ref2.toJSON()).toEqual({
114
+ __attrRef: { entity: "Parent2Resource", attribute: "Name" },
115
+ });
116
+ });
117
+
118
+ test("throws when AttrRef parent not in entities collection", () => {
119
+ const parent = {}; // Not a Declarable, not in entities
120
+
121
+ const child: Declarable & { ref: AttrRef } = {
122
+ entityType: "Child",
123
+ [DECLARABLE_MARKER]: true,
124
+ ref: new AttrRef(parent, "Arn"),
125
+ };
126
+
127
+ const entities = new Map<string, Declarable>([["ChildResource", child]]);
128
+
129
+ expect(() => resolveAttrRefs(entities)).toThrow(
130
+ 'Cannot resolve AttrRef on "ChildResource.ref": parent entity not found in entities collection'
131
+ );
132
+ });
133
+
134
+ test("handles entities with no AttrRefs", () => {
135
+ const entity1: Declarable = {
136
+ entityType: "Test1",
137
+ [DECLARABLE_MARKER]: true,
138
+ };
139
+
140
+ const entity2: Declarable & { prop: string } = {
141
+ entityType: "Test2",
142
+ [DECLARABLE_MARKER]: true,
143
+ prop: "value",
144
+ };
145
+
146
+ const entities = new Map<string, Declarable>([
147
+ ["Entity1", entity1],
148
+ ["Entity2", entity2],
149
+ ]);
150
+
151
+ expect(() => resolveAttrRefs(entities)).not.toThrow();
152
+
153
+ expect(getLogicalName(entity1)).toBe("Entity1");
154
+ expect(getLogicalName(entity2)).toBe("Entity2");
155
+ });
156
+
157
+ test("handles empty entities map", () => {
158
+ const entities = new Map<string, Declarable>();
159
+
160
+ expect(() => resolveAttrRefs(entities)).not.toThrow();
161
+ });
162
+
163
+ test("resolves chain of entities with AttrRefs", () => {
164
+ const root: Declarable = {
165
+ entityType: "Root",
166
+ [DECLARABLE_MARKER]: true,
167
+ };
168
+
169
+ const middle: Declarable & { rootRef: AttrRef } = {
170
+ entityType: "Middle",
171
+ [DECLARABLE_MARKER]: true,
172
+ rootRef: new AttrRef(root, "Id"),
173
+ };
174
+
175
+ const leaf: Declarable & { middleRef: AttrRef } = {
176
+ entityType: "Leaf",
177
+ [DECLARABLE_MARKER]: true,
178
+ middleRef: new AttrRef(middle, "Name"),
179
+ };
180
+
181
+ const entities = new Map<string, Declarable>([
182
+ ["RootResource", root],
183
+ ["MiddleResource", middle],
184
+ ["LeafResource", leaf],
185
+ ]);
186
+
187
+ resolveAttrRefs(entities);
188
+
189
+ expect(middle.rootRef.toJSON()).toEqual({
190
+ __attrRef: { entity: "RootResource", attribute: "Id" },
191
+ });
192
+ expect(leaf.middleRef.toJSON()).toEqual({
193
+ __attrRef: { entity: "MiddleResource", attribute: "Name" },
194
+ });
195
+ });
196
+
197
+ test("handles entity referencing itself", () => {
198
+ const entity: Declarable & { selfRef: AttrRef } = {
199
+ entityType: "SelfReferencing",
200
+ [DECLARABLE_MARKER]: true,
201
+ selfRef: null as unknown as AttrRef, // Will be set below
202
+ };
203
+
204
+ entity.selfRef = new AttrRef(entity, "Arn");
205
+
206
+ const entities = new Map<string, Declarable>([["MyResource", entity]]);
207
+
208
+ resolveAttrRefs(entities);
209
+
210
+ expect(entity.selfRef.toJSON()).toEqual({
211
+ __attrRef: { entity: "MyResource", attribute: "Arn" },
212
+ });
213
+ });
214
+
215
+ test("preserves logical name symbol on entities", () => {
216
+ const entity: Declarable = {
217
+ entityType: "Test",
218
+ [DECLARABLE_MARKER]: true,
219
+ };
220
+
221
+ const entities = new Map<string, Declarable>([["TestResource", entity]]);
222
+
223
+ resolveAttrRefs(entities);
224
+
225
+ const entityWithSymbol = entity as unknown as Record<symbol, unknown>;
226
+ expect(entityWithSymbol[LOGICAL_NAME_SYMBOL]).toBe("TestResource");
227
+ });
228
+
229
+ test("uses export name as logical name", () => {
230
+ const entity: Declarable = {
231
+ entityType: "Test",
232
+ [DECLARABLE_MARKER]: true,
233
+ };
234
+
235
+ const entities = new Map<string, Declarable>([
236
+ ["MyCustomExportName", entity],
237
+ ]);
238
+
239
+ resolveAttrRefs(entities);
240
+
241
+ expect(getLogicalName(entity)).toBe("MyCustomExportName");
242
+ });
243
+
244
+ test("handles complex attribute names in AttrRef", () => {
245
+ const parent: Declarable = {
246
+ entityType: "Parent",
247
+ [DECLARABLE_MARKER]: true,
248
+ };
249
+
250
+ const child: Declarable & { ref: AttrRef } = {
251
+ entityType: "Child",
252
+ [DECLARABLE_MARKER]: true,
253
+ ref: new AttrRef(parent, "Outputs.WebsiteURL"),
254
+ };
255
+
256
+ const entities = new Map<string, Declarable>([
257
+ ["ParentResource", parent],
258
+ ["ChildResource", child],
259
+ ]);
260
+
261
+ resolveAttrRefs(entities);
262
+
263
+ expect(child.ref.toJSON()).toEqual({
264
+ __attrRef: { entity: "ParentResource", attribute: "Outputs.WebsiteURL" },
265
+ });
266
+ });
267
+ });
@@ -0,0 +1,54 @@
1
+ import type { Declarable } from "../declarable";
2
+ import { AttrRef } from "../attrref";
3
+ import { LOGICAL_NAME_SYMBOL, getAttributes } from "../utils";
4
+
5
+ /**
6
+ * Resolves all AttrRef instances in a collection of entities
7
+ * Sets logical names on entities and resolves parent references in AttrRefs
8
+ *
9
+ * @param entities - Map of export name to Declarable entity
10
+ * @throws {Error} If an AttrRef parent cannot be found in the entities collection
11
+ */
12
+ export function resolveAttrRefs(entities: Map<string, Declarable>): void {
13
+ // First pass: Set logical names on all entities
14
+ for (const [name, entity] of entities.entries()) {
15
+ (entity as unknown as Record<symbol, unknown>)[LOGICAL_NAME_SYMBOL] = name;
16
+ }
17
+
18
+ // Second pass: Resolve all AttrRef instances
19
+ for (const [name, entity] of entities.entries()) {
20
+ const attributes = getAttributes(entity);
21
+
22
+ for (const attrName of attributes) {
23
+ const attrRef = (entity as unknown as Record<string, unknown>)[attrName];
24
+
25
+ if (attrRef instanceof AttrRef) {
26
+ const parent = attrRef.parent.deref();
27
+
28
+ if (!parent) {
29
+ throw new Error(
30
+ `Cannot resolve AttrRef on "${name}.${attrName}": parent has been garbage collected`
31
+ );
32
+ }
33
+
34
+ // Find the parent entity in the entities map
35
+ let parentLogicalName: string | undefined;
36
+
37
+ for (const [entityName, entityValue] of entities.entries()) {
38
+ if (entityValue === parent) {
39
+ parentLogicalName = entityName;
40
+ break;
41
+ }
42
+ }
43
+
44
+ if (!parentLogicalName) {
45
+ throw new Error(
46
+ `Cannot resolve AttrRef on "${name}.${attrName}": parent entity not found in entities collection`
47
+ );
48
+ }
49
+
50
+ attrRef._setLogicalName(parentLogicalName);
51
+ }
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,138 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { DiscoveryError, BuildError, LintError } from "./errors";
3
+
4
+ describe("DiscoveryError", () => {
5
+ test("creates error with file, message, and type properties", () => {
6
+ const error = new DiscoveryError(
7
+ "/path/to/file.ts",
8
+ "Failed to import module",
9
+ "import"
10
+ );
11
+
12
+ expect(error).toBeInstanceOf(Error);
13
+ expect(error).toBeInstanceOf(DiscoveryError);
14
+ expect(error.name).toBe("DiscoveryError");
15
+ expect(error.file).toBe("/path/to/file.ts");
16
+ expect(error.message).toBe("Failed to import module");
17
+ expect(error.type).toBe("import");
18
+ });
19
+
20
+ test("supports resolution error type", () => {
21
+ const error = new DiscoveryError(
22
+ "/src/index.ts",
23
+ "Cannot resolve module",
24
+ "resolution"
25
+ );
26
+
27
+ expect(error.type).toBe("resolution");
28
+ });
29
+
30
+ test("supports circular error type", () => {
31
+ const error = new DiscoveryError(
32
+ "/src/circular.ts",
33
+ "Circular dependency detected",
34
+ "circular"
35
+ );
36
+
37
+ expect(error.type).toBe("circular");
38
+ });
39
+
40
+ test("serializes to JSON correctly", () => {
41
+ const error = new DiscoveryError(
42
+ "/test.ts",
43
+ "Test error",
44
+ "import"
45
+ );
46
+
47
+ const json = error.toJSON();
48
+ expect(json).toEqual({
49
+ name: "DiscoveryError",
50
+ file: "/test.ts",
51
+ message: "Test error",
52
+ type: "import",
53
+ });
54
+ });
55
+ });
56
+
57
+ describe("BuildError", () => {
58
+ test("creates error with entityName and message properties", () => {
59
+ const error = new BuildError("MyEntity", "Failed to build entity");
60
+
61
+ expect(error).toBeInstanceOf(Error);
62
+ expect(error).toBeInstanceOf(BuildError);
63
+ expect(error.name).toBe("BuildError");
64
+ expect(error.entityName).toBe("MyEntity");
65
+ expect(error.message).toBe("Failed to build entity");
66
+ });
67
+
68
+ test("serializes to JSON correctly", () => {
69
+ const error = new BuildError("TestEntity", "Serialization failed");
70
+
71
+ const json = error.toJSON();
72
+ expect(json).toEqual({
73
+ name: "BuildError",
74
+ entityName: "TestEntity",
75
+ message: "Serialization failed",
76
+ });
77
+ });
78
+
79
+ test("handles entity names with special characters", () => {
80
+ const error = new BuildError("My-Entity_v2", "Build failure");
81
+
82
+ expect(error.entityName).toBe("My-Entity_v2");
83
+ });
84
+ });
85
+
86
+ describe("LintError", () => {
87
+ test("creates error with file, line, column, ruleId, and message properties", () => {
88
+ const error = new LintError(
89
+ "/src/app.ts",
90
+ 42,
91
+ 10,
92
+ "no-unused-vars",
93
+ "Variable 'x' is declared but never used"
94
+ );
95
+
96
+ expect(error).toBeInstanceOf(Error);
97
+ expect(error).toBeInstanceOf(LintError);
98
+ expect(error.name).toBe("LintError");
99
+ expect(error.file).toBe("/src/app.ts");
100
+ expect(error.line).toBe(42);
101
+ expect(error.column).toBe(10);
102
+ expect(error.ruleId).toBe("no-unused-vars");
103
+ expect(error.message).toBe("Variable 'x' is declared but never used");
104
+ });
105
+
106
+ test("handles line 0 and column 0", () => {
107
+ const error = new LintError(
108
+ "/test.ts",
109
+ 0,
110
+ 0,
111
+ "parse-error",
112
+ "Parse error"
113
+ );
114
+
115
+ expect(error.line).toBe(0);
116
+ expect(error.column).toBe(0);
117
+ });
118
+
119
+ test("serializes to JSON correctly", () => {
120
+ const error = new LintError(
121
+ "/main.ts",
122
+ 15,
123
+ 3,
124
+ "@typescript-eslint/no-explicit-any",
125
+ "Unexpected any"
126
+ );
127
+
128
+ const json = error.toJSON();
129
+ expect(json).toEqual({
130
+ name: "LintError",
131
+ file: "/main.ts",
132
+ line: 15,
133
+ column: 3,
134
+ ruleId: "@typescript-eslint/no-explicit-any",
135
+ message: "Unexpected any",
136
+ });
137
+ });
138
+ });
package/src/errors.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Error types for chant discovery, build, and lint failures
3
+ */
4
+
5
+ export type DiscoveryErrorType = "import" | "resolution" | "circular";
6
+
7
+ /**
8
+ * Error during file discovery or module import
9
+ */
10
+ export class DiscoveryError extends Error {
11
+ readonly file: string;
12
+ readonly type: DiscoveryErrorType;
13
+
14
+ constructor(file: string, message: string, type: DiscoveryErrorType) {
15
+ super(message);
16
+ this.name = "DiscoveryError";
17
+ this.file = file;
18
+ this.type = type;
19
+ }
20
+
21
+ toJSON() {
22
+ return {
23
+ name: this.name,
24
+ file: this.file,
25
+ message: this.message,
26
+ type: this.type,
27
+ };
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Error during build/serialization
33
+ */
34
+ export class BuildError extends Error {
35
+ readonly entityName: string;
36
+
37
+ constructor(entityName: string, message: string) {
38
+ super(message);
39
+ this.name = "BuildError";
40
+ this.entityName = entityName;
41
+ }
42
+
43
+ toJSON() {
44
+ return {
45
+ name: this.name,
46
+ entityName: this.entityName,
47
+ message: this.message,
48
+ };
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Error from lint rule violation
54
+ */
55
+ export class LintError extends Error {
56
+ readonly file: string;
57
+ readonly line: number;
58
+ readonly column: number;
59
+ readonly ruleId: string;
60
+
61
+ constructor(
62
+ file: string,
63
+ line: number,
64
+ column: number,
65
+ ruleId: string,
66
+ message: string
67
+ ) {
68
+ super(message);
69
+ this.name = "LintError";
70
+ this.file = file;
71
+ this.line = line;
72
+ this.column = column;
73
+ this.ruleId = ruleId;
74
+ }
75
+
76
+ toJSON() {
77
+ return {
78
+ name: this.name,
79
+ file: this.file,
80
+ line: this.line,
81
+ column: this.column,
82
+ ruleId: this.ruleId,
83
+ message: this.message,
84
+ };
85
+ }
86
+ }
@@ -0,0 +1,67 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { BaseValueParser } from "./base-parser";
3
+
4
+ /**
5
+ * Mock parser that recognizes {"UPPER": value} as an intrinsic.
6
+ */
7
+ class MockParser extends BaseValueParser {
8
+ protected dispatchIntrinsic(
9
+ key: string,
10
+ value: unknown,
11
+ _obj: Record<string, unknown>,
12
+ ): unknown | null {
13
+ if (key === "UPPER") {
14
+ return { __intrinsic: "UPPER", value: this.parseValue(value) };
15
+ }
16
+ return null;
17
+ }
18
+ }
19
+
20
+ describe("BaseValueParser", () => {
21
+ const parser = new MockParser();
22
+
23
+ test("returns null/undefined as-is", () => {
24
+ expect(parser.parseValue(null)).toBeNull();
25
+ expect(parser.parseValue(undefined)).toBeUndefined();
26
+ });
27
+
28
+ test("returns primitives as-is", () => {
29
+ expect(parser.parseValue("hello")).toBe("hello");
30
+ expect(parser.parseValue(42)).toBe(42);
31
+ expect(parser.parseValue(true)).toBe(true);
32
+ });
33
+
34
+ test("recurses into arrays", () => {
35
+ expect(parser.parseValue([1, "two", null])).toEqual([1, "two", null]);
36
+ });
37
+
38
+ test("dispatches single-key intrinsic", () => {
39
+ expect(parser.parseValue({ UPPER: "hello" })).toEqual({
40
+ __intrinsic: "UPPER",
41
+ value: "hello",
42
+ });
43
+ });
44
+
45
+ test("recurses into intrinsic values", () => {
46
+ expect(parser.parseValue({ UPPER: { UPPER: "nested" } })).toEqual({
47
+ __intrinsic: "UPPER",
48
+ value: { __intrinsic: "UPPER", value: "nested" },
49
+ });
50
+ });
51
+
52
+ test("falls through for unknown single-key objects", () => {
53
+ expect(parser.parseValue({ unknown: "value" })).toEqual({ unknown: "value" });
54
+ });
55
+
56
+ test("recurses into multi-key objects", () => {
57
+ const result = parser.parseValue({ a: 1, b: { UPPER: "x" } });
58
+ expect(result).toEqual({ a: 1, b: { __intrinsic: "UPPER", value: "x" } });
59
+ });
60
+
61
+ test("recurses arrays inside objects", () => {
62
+ const result = parser.parseValue({ items: [{ UPPER: "a" }, "plain"] });
63
+ expect(result).toEqual({
64
+ items: [{ __intrinsic: "UPPER", value: "a" }, "plain"],
65
+ });
66
+ });
67
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Base class for template value parsers.
3
+ *
4
+ * Implements the generic recursive walk: null → array → single-key
5
+ * intrinsic dispatch → plain object → primitive. Subclasses provide
6
+ * format-specific intrinsic dispatch.
7
+ */
8
+
9
+ export abstract class BaseValueParser {
10
+ /**
11
+ * Try to interpret a single-key object as an intrinsic function.
12
+ * Return the parsed intrinsic value, or null if this key isn't an intrinsic.
13
+ */
14
+ protected abstract dispatchIntrinsic(
15
+ key: string,
16
+ value: unknown,
17
+ obj: Record<string, unknown>,
18
+ ): unknown | null;
19
+
20
+ /**
21
+ * Parse a value recursively: null → array → single-key intrinsic → object → primitive.
22
+ */
23
+ parseValue(value: unknown): unknown {
24
+ if (value === null || value === undefined) return value;
25
+
26
+ if (Array.isArray(value)) {
27
+ return value.map((v) => this.parseValue(v));
28
+ }
29
+
30
+ if (typeof value === "object") {
31
+ const obj = value as Record<string, unknown>;
32
+ const keys = Object.keys(obj);
33
+
34
+ if (keys.length === 1) {
35
+ const result = this.dispatchIntrinsic(keys[0], obj[keys[0]], obj);
36
+ if (result !== null) return result;
37
+ }
38
+
39
+ const parsed: Record<string, unknown> = {};
40
+ for (const [k, v] of Object.entries(obj)) {
41
+ parsed[k] = this.parseValue(v);
42
+ }
43
+ return parsed;
44
+ }
45
+
46
+ return value;
47
+ }
48
+ }
@@ -0,0 +1,21 @@
1
+ import type { TemplateIR } from "./parser";
2
+
3
+ /**
4
+ * Represents a generated TypeScript file
5
+ */
6
+ export interface GeneratedFile {
7
+ readonly path: string;
8
+ readonly content: string;
9
+ }
10
+
11
+ /**
12
+ * Interface for TypeScript code generators that convert IR to TypeScript
13
+ */
14
+ export interface TypeScriptGenerator {
15
+ /**
16
+ * Generate TypeScript files from intermediate representation
17
+ * @param ir - Intermediate representation of the template
18
+ * @returns Array of generated TypeScript files
19
+ */
20
+ generate(ir: TemplateIR): GeneratedFile[];
21
+ }