@intentius/chant 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/bench.test.ts +1 -1
- package/src/cli/commands/doctor.ts +8 -3
- package/src/cli/commands/init.test.ts +44 -4
- package/src/cli/commands/init.ts +55 -23
- package/src/cli/commands/lint.ts +27 -13
- package/src/cli/handlers/init.ts +1 -0
- package/src/cli/lsp/server.ts +1 -1
- package/src/cli/main.ts +4 -0
- package/src/cli/mcp/server.test.ts +28 -2
- package/src/cli/mcp/tools/scaffold.ts +21 -3
- package/src/cli/registry.ts +1 -0
- package/src/cli/reporters/stylish.test.ts +212 -1
- package/src/cli/reporters/stylish.ts +133 -36
- package/src/codegen/docs-rules.test.ts +112 -0
- package/src/codegen/docs-rules.ts +129 -0
- package/src/codegen/docs.ts +3 -1
- package/src/codegen/generate-typescript.test.ts +64 -0
- package/src/codegen/generate-typescript.ts +13 -3
- package/src/codegen/package.ts +1 -1
- package/src/composite.test.ts +83 -16
- package/src/composite.ts +7 -5
- package/src/detectLexicon.test.ts +2 -2
- package/src/discovery/collect.test.ts +2 -2
- package/src/discovery/collect.ts +1 -1
- package/src/index.ts +1 -0
- package/src/lexicon-schema.ts +8 -0
- package/src/lexicon.ts +13 -1
- package/src/lint/declarative.ts +6 -0
- package/src/lint/engine.test.ts +287 -11
- package/src/lint/engine.ts +101 -23
- package/src/lint/rule-registry.test.ts +112 -0
- package/src/lint/rule-registry.ts +118 -0
- package/src/lint/rule.ts +8 -0
- package/src/lint/rules/cor017-composite-name-match.ts +2 -1
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +4 -3
- package/src/lint/rules/declarable-naming-convention.ts +1 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +1 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +1 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +1 -0
- package/src/lint/rules/evl004-spread-non-const.ts +1 -0
- package/src/lint/rules/evl005-resource-block-body.ts +1 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +1 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +1 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +1 -0
- package/src/lint/rules/export-required.ts +1 -0
- package/src/lint/rules/file-declarable-limit.ts +1 -0
- package/src/lint/rules/flat-declarations.test.ts +8 -7
- package/src/lint/rules/flat-declarations.ts +2 -3
- package/src/lint/rules/no-cyclic-declarable-ref.ts +1 -0
- package/src/lint/rules/no-redundant-type-import.ts +1 -0
- package/src/lint/rules/no-redundant-value-cast.ts +1 -0
- package/src/lint/rules/no-string-ref.ts +1 -0
- package/src/lint/rules/no-unused-declarable-import.ts +1 -0
- package/src/lint/rules/no-unused-declarable.test.ts +8 -0
- package/src/lint/rules/no-unused-declarable.ts +4 -0
- package/src/lint/rules/single-concern-file.ts +1 -0
- package/src/lsp/lexicon-providers.ts +7 -0
- package/src/lsp/types.ts +1 -0
- package/src/resource-attributes.test.ts +79 -0
- package/src/resource-attributes.ts +42 -0
- 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 });
|