@intentius/chant 0.0.8 → 0.0.10

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 (62) hide show
  1. package/package.json +2 -1
  2. package/src/bench.test.ts +1 -1
  3. package/src/cli/commands/doctor.ts +8 -3
  4. package/src/cli/commands/init.test.ts +44 -4
  5. package/src/cli/commands/init.ts +55 -23
  6. package/src/cli/commands/lint.ts +27 -13
  7. package/src/cli/handlers/init.ts +1 -0
  8. package/src/cli/lsp/server.ts +1 -1
  9. package/src/cli/main.ts +4 -0
  10. package/src/cli/mcp/server.test.ts +28 -2
  11. package/src/cli/mcp/tools/scaffold.ts +21 -3
  12. package/src/cli/registry.ts +1 -0
  13. package/src/cli/reporters/stylish.test.ts +212 -1
  14. package/src/cli/reporters/stylish.ts +133 -36
  15. package/src/codegen/docs-rules.test.ts +112 -0
  16. package/src/codegen/docs-rules.ts +129 -0
  17. package/src/codegen/docs.ts +3 -1
  18. package/src/codegen/generate-typescript.test.ts +64 -0
  19. package/src/codegen/generate-typescript.ts +13 -3
  20. package/src/codegen/package.ts +1 -1
  21. package/src/composite.test.ts +83 -16
  22. package/src/composite.ts +7 -5
  23. package/src/detectLexicon.test.ts +2 -2
  24. package/src/discovery/collect.test.ts +2 -2
  25. package/src/discovery/collect.ts +1 -1
  26. package/src/index.ts +1 -0
  27. package/src/lexicon-schema.ts +8 -0
  28. package/src/lexicon.ts +13 -1
  29. package/src/lint/declarative.ts +6 -0
  30. package/src/lint/engine.test.ts +287 -11
  31. package/src/lint/engine.ts +101 -23
  32. package/src/lint/rule-registry.test.ts +112 -0
  33. package/src/lint/rule-registry.ts +118 -0
  34. package/src/lint/rule.ts +8 -0
  35. package/src/lint/rules/cor017-composite-name-match.ts +2 -1
  36. package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +4 -3
  37. package/src/lint/rules/declarable-naming-convention.ts +1 -0
  38. package/src/lint/rules/evl001-non-literal-expression.ts +1 -0
  39. package/src/lint/rules/evl002-control-flow-resource.ts +1 -0
  40. package/src/lint/rules/evl003-dynamic-property-access.ts +1 -0
  41. package/src/lint/rules/evl004-spread-non-const.ts +1 -0
  42. package/src/lint/rules/evl005-resource-block-body.ts +1 -0
  43. package/src/lint/rules/evl007-invalid-siblings.ts +1 -0
  44. package/src/lint/rules/evl009-composite-no-constant.ts +1 -0
  45. package/src/lint/rules/evl010-composite-no-transform.ts +1 -0
  46. package/src/lint/rules/export-required.ts +1 -0
  47. package/src/lint/rules/file-declarable-limit.ts +1 -0
  48. package/src/lint/rules/flat-declarations.test.ts +8 -7
  49. package/src/lint/rules/flat-declarations.ts +2 -3
  50. package/src/lint/rules/no-cyclic-declarable-ref.ts +1 -0
  51. package/src/lint/rules/no-redundant-type-import.ts +1 -0
  52. package/src/lint/rules/no-redundant-value-cast.ts +1 -0
  53. package/src/lint/rules/no-string-ref.ts +1 -0
  54. package/src/lint/rules/no-unused-declarable-import.ts +1 -0
  55. package/src/lint/rules/no-unused-declarable.test.ts +8 -0
  56. package/src/lint/rules/no-unused-declarable.ts +4 -0
  57. package/src/lint/rules/single-concern-file.ts +1 -0
  58. package/src/lsp/lexicon-providers.ts +7 -0
  59. package/src/lsp/types.ts +1 -0
  60. package/src/resource-attributes.test.ts +79 -0
  61. package/src/resource-attributes.ts +42 -0
  62. package/src/runtime.ts +4 -3
@@ -0,0 +1,79 @@
1
+ import { describe, test, expect, spyOn } from "bun:test";
2
+ import { resolveDependsOn } from "./resource-attributes";
3
+ import { DECLARABLE_MARKER, type Declarable } from "./declarable";
4
+
5
+ function mockDeclarable(type = "AWS::S3::Bucket"): Declarable {
6
+ return {
7
+ [DECLARABLE_MARKER]: true,
8
+ lexicon: "aws",
9
+ entityType: type,
10
+ kind: "resource",
11
+ } as Declarable;
12
+ }
13
+
14
+ describe("resolveDependsOn", () => {
15
+ test("resolves a single Declarable to its logical name", () => {
16
+ const bucket = mockDeclarable();
17
+ const entityNames = new Map<Declarable, string>([[bucket, "MyBucket"]]);
18
+
19
+ const result = resolveDependsOn(bucket, entityNames, "MyResource");
20
+ expect(result).toEqual(["MyBucket"]);
21
+ });
22
+
23
+ test("resolves an array of Declarables", () => {
24
+ const bucket = mockDeclarable("AWS::S3::Bucket");
25
+ const role = mockDeclarable("AWS::IAM::Role");
26
+ const entityNames = new Map<Declarable, string>([
27
+ [bucket, "MyBucket"],
28
+ [role, "MyRole"],
29
+ ]);
30
+
31
+ const result = resolveDependsOn([bucket, role], entityNames, "MyResource");
32
+ expect(result).toEqual(["MyBucket", "MyRole"]);
33
+ });
34
+
35
+ test("passes through a single string", () => {
36
+ const entityNames = new Map<Declarable, string>();
37
+ const result = resolveDependsOn("ExternalResource", entityNames, "MyResource");
38
+ expect(result).toEqual(["ExternalResource"]);
39
+ });
40
+
41
+ test("passes through an array of strings", () => {
42
+ const entityNames = new Map<Declarable, string>();
43
+ const result = resolveDependsOn(["ResA", "ResB"], entityNames, "MyResource");
44
+ expect(result).toEqual(["ResA", "ResB"]);
45
+ });
46
+
47
+ test("handles mixed strings and Declarables", () => {
48
+ const bucket = mockDeclarable();
49
+ const entityNames = new Map<Declarable, string>([[bucket, "MyBucket"]]);
50
+
51
+ const result = resolveDependsOn(["ManualRef", bucket], entityNames, "MyResource");
52
+ expect(result).toEqual(["ManualRef", "MyBucket"]);
53
+ });
54
+
55
+ test("warns and skips Declarable not found in entityNames", () => {
56
+ const bucket = mockDeclarable();
57
+ const entityNames = new Map<Declarable, string>(); // bucket not registered
58
+ const spy = spyOn(console, "warn").mockImplementation(() => {});
59
+
60
+ const result = resolveDependsOn(bucket, entityNames, "MyResource");
61
+ expect(result).toEqual([]);
62
+ expect(spy).toHaveBeenCalledTimes(1);
63
+ expect(spy.mock.calls[0][0]).toContain("MyResource");
64
+
65
+ spy.mockRestore();
66
+ });
67
+
68
+ test("returns empty array for empty array input", () => {
69
+ const entityNames = new Map<Declarable, string>();
70
+ const result = resolveDependsOn([], entityNames, "MyResource");
71
+ expect(result).toEqual([]);
72
+ });
73
+
74
+ test("skips non-string non-Declarable values silently", () => {
75
+ const entityNames = new Map<Declarable, string>();
76
+ const result = resolveDependsOn([42, null, true] as unknown[], entityNames, "MyResource");
77
+ expect(result).toEqual([]);
78
+ });
79
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Utilities for resolving resource-level attributes (DependsOn, Condition, etc.).
3
+ *
4
+ * Shared by lexicon serializers — the DependsOn resolution logic converts
5
+ * Declarable object references to logical names.
6
+ */
7
+
8
+ import type { Declarable } from "./declarable";
9
+
10
+ /**
11
+ * Resolve a DependsOn value (Declarable, string, or mixed array) into
12
+ * an array of logical resource names.
13
+ *
14
+ * - Strings pass through as-is (user-specified logical names)
15
+ * - Declarable objects are looked up in the entityNames map
16
+ * - Unknown values emit a console warning and are skipped
17
+ */
18
+ export function resolveDependsOn(
19
+ deps: unknown,
20
+ entityNames: Map<Declarable, string>,
21
+ resourceName: string,
22
+ ): string[] {
23
+ const items = Array.isArray(deps) ? deps : [deps];
24
+ const resolved: string[] = [];
25
+
26
+ for (const dep of items) {
27
+ if (typeof dep === "string") {
28
+ resolved.push(dep);
29
+ } else if (typeof dep === "object" && dep !== null && "entityType" in dep) {
30
+ const depName = entityNames.get(dep as Declarable);
31
+ if (depName) {
32
+ resolved.push(depName);
33
+ } else {
34
+ console.warn(
35
+ `[chant] warning: DependsOn in "${resourceName}" references a declarable not found in the build — is the target resource exported?`,
36
+ );
37
+ }
38
+ }
39
+ }
40
+
41
+ return resolved;
42
+ }
package/src/runtime.ts CHANGED
@@ -22,13 +22,14 @@ export function createResource(
22
22
  type: string,
23
23
  lexicon: string,
24
24
  attrMap: Record<string, string>,
25
- ): new (props: Record<string, unknown>) => Record<string, unknown> {
26
- const ResourceClass = function (this: Record<string, unknown>, props: Record<string, unknown>) {
25
+ ): new (props: Record<string, unknown>, attributes?: Record<string, unknown>) => Record<string, unknown> {
26
+ const ResourceClass = function (this: Record<string, unknown>, props: Record<string, unknown>, attributes?: Record<string, unknown>) {
27
27
  Object.defineProperty(this, DECLARABLE_MARKER, { value: true, enumerable: false });
28
28
  Object.defineProperty(this, "lexicon", { value: lexicon, enumerable: false });
29
29
  Object.defineProperty(this, "entityType", { value: type, enumerable: false });
30
30
  Object.defineProperty(this, "kind", { value: "resource", enumerable: false });
31
31
  Object.defineProperty(this, "props", { value: props ?? {}, enumerable: false, configurable: true });
32
+ Object.defineProperty(this, "attributes", { value: attributes ?? {}, enumerable: false, configurable: true });
32
33
 
33
34
  // Create AttrRef instances for each attribute
34
35
  // Must be enumerable so getAttributes() can discover them for resolveAttrRefs()
@@ -39,7 +40,7 @@ export function createResource(
39
40
  writable: false,
40
41
  });
41
42
  }
42
- } as unknown as new (props: Record<string, unknown>) => Record<string, unknown>;
43
+ } as unknown as new (props: Record<string, unknown>, attributes?: Record<string, unknown>) => Record<string, unknown>;
43
44
 
44
45
  // Set the constructor name for debugging
45
46
  Object.defineProperty(ResourceClass, "name", { value: type.split("::").pop() ?? type });