@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.
- package/package.json +2 -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
|
@@ -88,6 +88,70 @@ describe("writeConstructor", () => {
|
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
describe("writeConstructor with resourceAttributesType", () => {
|
|
92
|
+
test("empty props with attributes type emits both params", () => {
|
|
93
|
+
const lines: string[] = [];
|
|
94
|
+
writeConstructor(lines, [], undefined, "CFResourceAttributes");
|
|
95
|
+
expect(lines.join("\n")).toContain(
|
|
96
|
+
"constructor(props: Record<string, unknown>, attributes?: CFResourceAttributes);",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("props with attributes type emits second param after closing brace", () => {
|
|
101
|
+
const lines: string[] = [];
|
|
102
|
+
writeConstructor(
|
|
103
|
+
lines,
|
|
104
|
+
[{ name: "BucketName", type: "string", required: true }],
|
|
105
|
+
undefined,
|
|
106
|
+
"CFResourceAttributes",
|
|
107
|
+
);
|
|
108
|
+
const output = lines.join("\n");
|
|
109
|
+
expect(output).toContain("constructor(props: {");
|
|
110
|
+
expect(output).toContain("BucketName: string;");
|
|
111
|
+
expect(output).toContain("}, attributes?: CFResourceAttributes);");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("without attributes type, closing brace has no second param", () => {
|
|
115
|
+
const lines: string[] = [];
|
|
116
|
+
writeConstructor(
|
|
117
|
+
lines,
|
|
118
|
+
[{ name: "Name", type: "string", required: true }],
|
|
119
|
+
undefined,
|
|
120
|
+
);
|
|
121
|
+
const output = lines.join("\n");
|
|
122
|
+
expect(output).toContain(" });");
|
|
123
|
+
expect(output).not.toContain("attributes?");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("writeResourceClass with resourceAttributesType", () => {
|
|
128
|
+
test("resource class constructor includes attributes param", () => {
|
|
129
|
+
const lines: string[] = [];
|
|
130
|
+
writeResourceClass(
|
|
131
|
+
lines,
|
|
132
|
+
"Bucket",
|
|
133
|
+
[{ name: "BucketName", type: "string", required: false }],
|
|
134
|
+
[{ name: "Arn", type: "string" }],
|
|
135
|
+
undefined,
|
|
136
|
+
"CFResourceAttributes",
|
|
137
|
+
);
|
|
138
|
+
const output = lines.join("\n");
|
|
139
|
+
expect(output).toContain("}, attributes?: CFResourceAttributes);");
|
|
140
|
+
expect(output).toContain("readonly Arn: string;");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("property class does not get attributes param", () => {
|
|
144
|
+
const lines: string[] = [];
|
|
145
|
+
writePropertyClass(
|
|
146
|
+
lines,
|
|
147
|
+
"BucketConfig",
|
|
148
|
+
[{ name: "Enabled", type: "boolean", required: false }],
|
|
149
|
+
);
|
|
150
|
+
const output = lines.join("\n");
|
|
151
|
+
expect(output).not.toContain("attributes?");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
91
155
|
describe("writeEnumType", () => {
|
|
92
156
|
test("writes single-line for short enum", () => {
|
|
93
157
|
const lines: string[] = [];
|
|
@@ -42,10 +42,11 @@ export function writeResourceClass(
|
|
|
42
42
|
properties: DtsProperty[],
|
|
43
43
|
attributes: DtsAttribute[],
|
|
44
44
|
remap?: Map<string, string>,
|
|
45
|
+
resourceAttributesType?: string,
|
|
45
46
|
): void {
|
|
46
47
|
lines.push("");
|
|
47
48
|
lines.push(`export declare class ${tsName} {`);
|
|
48
|
-
writeConstructor(lines, properties, remap);
|
|
49
|
+
writeConstructor(lines, properties, remap, resourceAttributesType);
|
|
49
50
|
|
|
50
51
|
// Attributes as readonly properties (sorted)
|
|
51
52
|
const attrs = [...attributes].sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -79,9 +80,14 @@ export function writeConstructor(
|
|
|
79
80
|
lines: string[],
|
|
80
81
|
props: DtsProperty[],
|
|
81
82
|
remap: Map<string, string> | undefined,
|
|
83
|
+
resourceAttributesType?: string,
|
|
82
84
|
): void {
|
|
83
85
|
if (props.length === 0) {
|
|
84
|
-
|
|
86
|
+
if (resourceAttributesType) {
|
|
87
|
+
lines.push(` constructor(props: Record<string, unknown>, attributes?: ${resourceAttributesType});`);
|
|
88
|
+
} else {
|
|
89
|
+
lines.push(" constructor(props: Record<string, unknown>);");
|
|
90
|
+
}
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
93
|
|
|
@@ -100,7 +106,11 @@ export function writeConstructor(
|
|
|
100
106
|
}
|
|
101
107
|
lines.push(` ${p.name}${optional}: ${tsType};`);
|
|
102
108
|
}
|
|
103
|
-
|
|
109
|
+
if (resourceAttributesType) {
|
|
110
|
+
lines.push(` }, attributes?: ${resourceAttributesType});`);
|
|
111
|
+
} else {
|
|
112
|
+
lines.push(" });");
|
|
113
|
+
}
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
/**
|
package/src/codegen/package.ts
CHANGED
|
@@ -113,7 +113,7 @@ export async function packagePipeline(
|
|
|
113
113
|
/**
|
|
114
114
|
* Collect lint rule source files from a lexicon package.
|
|
115
115
|
* Auto-discovers .ts files in the specified directories,
|
|
116
|
-
* skipping test files,
|
|
116
|
+
* skipping test files, re-export files (index.ts), and non-.ts files.
|
|
117
117
|
*/
|
|
118
118
|
export function collectRules(
|
|
119
119
|
srcDir: string,
|
package/src/composite.test.ts
CHANGED
|
@@ -151,8 +151,8 @@ describe("expandComposite", () => {
|
|
|
151
151
|
|
|
152
152
|
const expanded = expandComposite("storage", MyComp({}));
|
|
153
153
|
expect(expanded.size).toBe(2);
|
|
154
|
-
expect(expanded.get("
|
|
155
|
-
expect(expanded.get("
|
|
154
|
+
expect(expanded.get("storageBucket")?.entityType).toBe("Bucket");
|
|
155
|
+
expect(expanded.get("storageRole")?.entityType).toBe("Role");
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
test("handles nested composites", () => {
|
|
@@ -167,8 +167,8 @@ describe("expandComposite", () => {
|
|
|
167
167
|
|
|
168
168
|
const expanded = expandComposite("app", Outer({}));
|
|
169
169
|
expect(expanded.size).toBe(2);
|
|
170
|
-
expect(expanded.get("
|
|
171
|
-
expect(expanded.get("
|
|
170
|
+
expect(expanded.get("appBucket")?.entityType).toBe("Bucket");
|
|
171
|
+
expect(expanded.get("appNestedTable")?.entityType).toBe("Table");
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
test("preserves Declarable identity (same object reference)", () => {
|
|
@@ -176,7 +176,7 @@ describe("expandComposite", () => {
|
|
|
176
176
|
const MyComp = Composite<{}>(() => ({ bucket }));
|
|
177
177
|
|
|
178
178
|
const expanded = expandComposite("s", MyComp({}));
|
|
179
|
-
expect(expanded.get("
|
|
179
|
+
expect(expanded.get("sBucket")).toBe(bucket);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
test("handles empty composite", () => {
|
|
@@ -230,6 +230,22 @@ describe("resource() helper", () => {
|
|
|
230
230
|
const instance = resource(MockResource, {});
|
|
231
231
|
expect(instance.arn).toBeInstanceOf(AttrRef);
|
|
232
232
|
});
|
|
233
|
+
|
|
234
|
+
test("forwards attributes as second constructor argument", () => {
|
|
235
|
+
// MockResource doesn't store attributes, so use createResource which does
|
|
236
|
+
const { createResource } = require("./runtime");
|
|
237
|
+
const TestRes = createResource("Test::Resource", "test", { arn: "Arn" });
|
|
238
|
+
const attrs = { DependsOn: ["Other"], Condition: "IsProd" };
|
|
239
|
+
const instance = resource(TestRes as any, { name: "test" }, attrs);
|
|
240
|
+
expect((instance as any).attributes).toEqual(attrs);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("without attributes, resource() creates instance with empty attributes", () => {
|
|
244
|
+
const { createResource } = require("./runtime");
|
|
245
|
+
const TestRes = createResource("Test::Resource", "test", { arn: "Arn" });
|
|
246
|
+
const instance = resource(TestRes as any, { name: "test" });
|
|
247
|
+
expect((instance as any).attributes).toEqual({});
|
|
248
|
+
});
|
|
233
249
|
});
|
|
234
250
|
|
|
235
251
|
function mockDeclarableWithProps(type: string, props: Record<string, unknown>): Declarable {
|
|
@@ -313,6 +329,57 @@ describe("withDefaults", () => {
|
|
|
313
329
|
expect(CompositeRegistry.size).toBe(1);
|
|
314
330
|
});
|
|
315
331
|
|
|
332
|
+
test("function-based defaults receive caller props", () => {
|
|
333
|
+
let receivedByFn: Partial<{ name: string; timeout: number }> | undefined;
|
|
334
|
+
const Base = Composite<{ name: string; timeout: number }>((props) => ({
|
|
335
|
+
item: mockDeclarable(props.name),
|
|
336
|
+
}));
|
|
337
|
+
|
|
338
|
+
const Wrapped = withDefaults(Base, (props) => {
|
|
339
|
+
receivedByFn = props;
|
|
340
|
+
return { timeout: 30 } as { timeout: number };
|
|
341
|
+
});
|
|
342
|
+
Wrapped({ name: "test" });
|
|
343
|
+
expect(receivedByFn).toEqual({ name: "test" });
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("user props override computed defaults", () => {
|
|
347
|
+
let received: { timeout: number } | undefined;
|
|
348
|
+
const Base = Composite<{ timeout: number }>((props) => {
|
|
349
|
+
received = props;
|
|
350
|
+
return { item: mockDeclarable() };
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const Wrapped = withDefaults(Base, () => ({ timeout: 30 }) as { timeout: number });
|
|
354
|
+
Wrapped({ timeout: 60 });
|
|
355
|
+
expect(received!.timeout).toBe(60);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("stacking: withDefaults(withDefaults(base, static), fn) works", () => {
|
|
359
|
+
let received: { a: number; b: number; c: number } | undefined;
|
|
360
|
+
const Base = Composite<{ a: number; b: number; c: number }>((props) => {
|
|
361
|
+
received = props;
|
|
362
|
+
return { item: mockDeclarable() };
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const Step1 = withDefaults(Base, { a: 1 });
|
|
366
|
+
const Step2 = withDefaults(Step1, (props) => ({ b: (props.c ?? 0) + 10 }) as { b: number });
|
|
367
|
+
Step2({ c: 3 });
|
|
368
|
+
expect(received).toEqual({ a: 1, b: 13, c: 3 });
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("undefined computed values don't overwrite user props", () => {
|
|
372
|
+
let received: { name: string; timeout: number } | undefined;
|
|
373
|
+
const Base = Composite<{ name: string; timeout: number }>((props) => {
|
|
374
|
+
received = props;
|
|
375
|
+
return { item: mockDeclarable() };
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const Wrapped = withDefaults(Base, () => ({ timeout: undefined }) as unknown as { timeout: number });
|
|
379
|
+
Wrapped({ name: "test", timeout: 42 });
|
|
380
|
+
expect(received!.timeout).toBe(42);
|
|
381
|
+
});
|
|
382
|
+
|
|
316
383
|
test("expandComposite works identically on defaulted composites", () => {
|
|
317
384
|
const Base = Composite<{ name: string; timeout: number }>((props) => ({
|
|
318
385
|
fn: mockDeclarable(`Fn-${props.name}`),
|
|
@@ -323,8 +390,8 @@ describe("withDefaults", () => {
|
|
|
323
390
|
const expanded = expandComposite("api", Wrapped({ name: "test" }));
|
|
324
391
|
|
|
325
392
|
expect(expanded.size).toBe(2);
|
|
326
|
-
expect(expanded.get("
|
|
327
|
-
expect(expanded.get("
|
|
393
|
+
expect(expanded.get("apiFn")?.entityType).toBe("Fn-test");
|
|
394
|
+
expect(expanded.get("apiRole")?.entityType).toBe("Role-test");
|
|
328
395
|
});
|
|
329
396
|
});
|
|
330
397
|
|
|
@@ -342,8 +409,8 @@ describe("propagate", () => {
|
|
|
342
409
|
const instance = propagate(MyComp({}), { env: "prod" });
|
|
343
410
|
const expanded = expandComposite("s", instance);
|
|
344
411
|
|
|
345
|
-
const bucketProps = (expanded.get("
|
|
346
|
-
const roleProps = (expanded.get("
|
|
412
|
+
const bucketProps = (expanded.get("sBucket") as any).props;
|
|
413
|
+
const roleProps = (expanded.get("sRole") as any).props;
|
|
347
414
|
expect(bucketProps.env).toBe("prod");
|
|
348
415
|
expect(roleProps.env).toBe("prod");
|
|
349
416
|
});
|
|
@@ -359,7 +426,7 @@ describe("propagate", () => {
|
|
|
359
426
|
tags: [{ key: "env", value: "prod" }],
|
|
360
427
|
});
|
|
361
428
|
const expanded = expandComposite("s", instance);
|
|
362
|
-
const tags = (expanded.get("
|
|
429
|
+
const tags = (expanded.get("sBucket") as any).props.tags;
|
|
363
430
|
|
|
364
431
|
expect(tags).toEqual([
|
|
365
432
|
{ key: "env", value: "prod" },
|
|
@@ -374,7 +441,7 @@ describe("propagate", () => {
|
|
|
374
441
|
|
|
375
442
|
const instance = propagate(MyComp({}), { region: "eu-west-1" });
|
|
376
443
|
const expanded = expandComposite("s", instance);
|
|
377
|
-
expect((expanded.get("
|
|
444
|
+
expect((expanded.get("sBucket") as any).props.region).toBe("us-west-2");
|
|
378
445
|
});
|
|
379
446
|
|
|
380
447
|
test("undefined values in shared props are stripped", () => {
|
|
@@ -384,7 +451,7 @@ describe("propagate", () => {
|
|
|
384
451
|
|
|
385
452
|
const instance = propagate(MyComp({}), { name: undefined, extra: "yes" });
|
|
386
453
|
const expanded = expandComposite("s", instance);
|
|
387
|
-
const props = (expanded.get("
|
|
454
|
+
const props = (expanded.get("sBucket") as any).props;
|
|
388
455
|
expect(props.name).toBe("data");
|
|
389
456
|
expect(props.extra).toBe("yes");
|
|
390
457
|
});
|
|
@@ -402,8 +469,8 @@ describe("propagate", () => {
|
|
|
402
469
|
const instance = propagate(Outer({}), { env: "prod" });
|
|
403
470
|
const expanded = expandComposite("app", instance);
|
|
404
471
|
|
|
405
|
-
expect((expanded.get("
|
|
406
|
-
expect((expanded.get("
|
|
472
|
+
expect((expanded.get("appBucket") as any).props.env).toBe("prod");
|
|
473
|
+
expect((expanded.get("appNestedTable") as any).props.env).toBe("prod");
|
|
407
474
|
});
|
|
408
475
|
|
|
409
476
|
test("expanded declarables are same object references", () => {
|
|
@@ -412,7 +479,7 @@ describe("propagate", () => {
|
|
|
412
479
|
|
|
413
480
|
const instance = propagate(MyComp({}), { env: "prod" });
|
|
414
481
|
const expanded = expandComposite("s", instance);
|
|
415
|
-
expect(expanded.get("
|
|
482
|
+
expect(expanded.get("sBucket")).toBe(bucket);
|
|
416
483
|
});
|
|
417
484
|
|
|
418
485
|
test("composites without propagate work unchanged", () => {
|
|
@@ -421,6 +488,6 @@ describe("propagate", () => {
|
|
|
421
488
|
}));
|
|
422
489
|
|
|
423
490
|
const expanded = expandComposite("s", MyComp({}));
|
|
424
|
-
expect((expanded.get("
|
|
491
|
+
expect((expanded.get("sBucket") as any).props.name).toBe("data");
|
|
425
492
|
});
|
|
426
493
|
});
|
package/src/composite.ts
CHANGED
|
@@ -123,7 +123,7 @@ export function expandComposite(
|
|
|
123
123
|
const shared = (instance as any)[SHARED_PROPS] as Record<string, unknown> | undefined;
|
|
124
124
|
|
|
125
125
|
for (const [memberName, member] of Object.entries(instance.members)) {
|
|
126
|
-
const fullName = `${prefix}
|
|
126
|
+
const fullName = `${prefix}${memberName[0].toUpperCase()}${memberName.slice(1)}`;
|
|
127
127
|
|
|
128
128
|
if (isCompositeInstance(member)) {
|
|
129
129
|
const nested = expandComposite(fullName, member);
|
|
@@ -182,10 +182,11 @@ type Simplify<T> = { [K in keyof T]: T[K] };
|
|
|
182
182
|
*/
|
|
183
183
|
export function withDefaults<P, M extends CompositeMembers, D extends Partial<P>>(
|
|
184
184
|
definition: CompositeDefinition<P, M>,
|
|
185
|
-
defaults: D,
|
|
185
|
+
defaults: D | ((props: Partial<P>) => D),
|
|
186
186
|
): CompositeDefinition<Simplify<PartialByDefault<P, D>>, M> {
|
|
187
187
|
const wrapped = ((props: Simplify<PartialByDefault<P, D>>) => {
|
|
188
|
-
|
|
188
|
+
const resolved = typeof defaults === "function" ? defaults(props as Partial<P>) : defaults;
|
|
189
|
+
return definition({ ...resolved, ...props } as P);
|
|
189
190
|
}) as CompositeDefinition<Simplify<PartialByDefault<P, D>>, M>;
|
|
190
191
|
|
|
191
192
|
Object.defineProperty(wrapped, "compositeName", {
|
|
@@ -236,8 +237,9 @@ export function propagate<M extends CompositeMembers>(
|
|
|
236
237
|
* Exists so lint tooling can validate composite member construction (EVL005).
|
|
237
238
|
*/
|
|
238
239
|
export function resource<T extends Declarable, P>(
|
|
239
|
-
Type: new (props: P) => T,
|
|
240
|
+
Type: new (props: P, attributes?: Record<string, unknown>) => T,
|
|
240
241
|
props: P,
|
|
242
|
+
attributes?: Record<string, unknown>,
|
|
241
243
|
): T {
|
|
242
|
-
return new Type(props);
|
|
244
|
+
return new Type(props, attributes);
|
|
243
245
|
}
|
|
@@ -213,7 +213,7 @@ describe("detectLexicons", () => {
|
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
test("detects lexicon from export statement", async () => {
|
|
216
|
-
const file = join(testDir, "
|
|
216
|
+
const file = join(testDir, "reexport.ts");
|
|
217
217
|
await writeFile(
|
|
218
218
|
file,
|
|
219
219
|
'export * from "@intentius/chant-lexicon-testdom";\nimport * as core from "@intentius/chant";'
|
|
@@ -224,7 +224,7 @@ describe("detectLexicons", () => {
|
|
|
224
224
|
});
|
|
225
225
|
|
|
226
226
|
test("detects lexicon from export with curly braces", async () => {
|
|
227
|
-
const file = join(testDir, "
|
|
227
|
+
const file = join(testDir, "reexport.ts");
|
|
228
228
|
await writeFile(
|
|
229
229
|
file,
|
|
230
230
|
'export { Bucket, Interpolate } from "@intentius/chant-lexicon-testdom";'
|
|
@@ -262,8 +262,8 @@ describe("collectEntities with composites", () => {
|
|
|
262
262
|
{ file: "test.ts", exports: { myComp: instance } },
|
|
263
263
|
]);
|
|
264
264
|
|
|
265
|
-
expect(entities.has("
|
|
266
|
-
expect(entities.has("
|
|
265
|
+
expect(entities.has("myCompA")).toBe(true);
|
|
266
|
+
expect(entities.has("myCompB")).toBe(true);
|
|
267
267
|
expect(entities.has("myComp")).toBe(false);
|
|
268
268
|
});
|
|
269
269
|
});
|
package/src/discovery/collect.ts
CHANGED
|
@@ -23,7 +23,7 @@ export function collectEntities(
|
|
|
23
23
|
for (const [name, value] of Object.entries(exports)) {
|
|
24
24
|
if (isDeclarable(value)) {
|
|
25
25
|
if (entities.has(name)) {
|
|
26
|
-
// Same object re-exported from multiple files (e.g.
|
|
26
|
+
// Same object re-exported from multiple files (e.g. re-exports from multiple files) is fine
|
|
27
27
|
if (entities.get(name) !== value) {
|
|
28
28
|
throw new DiscoveryError(
|
|
29
29
|
file,
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export * from "./codegen/validate";
|
|
|
52
52
|
export * from "./codegen/docs";
|
|
53
53
|
export * from "./runtime";
|
|
54
54
|
export * from "./runtime-adapter";
|
|
55
|
+
export * from "./resource-attributes";
|
|
55
56
|
export * from "./stack-output";
|
|
56
57
|
export * from "./child-project";
|
|
57
58
|
export * from "./lsp/types";
|
package/src/lexicon-schema.ts
CHANGED
|
@@ -72,6 +72,14 @@ export const LexiconEntrySchema = z.object({
|
|
|
72
72
|
createOnly: z.array(z.string()).optional(),
|
|
73
73
|
writeOnly: z.array(z.string()).optional(),
|
|
74
74
|
primaryIdentifier: z.array(z.string()).optional(),
|
|
75
|
+
deprecatedProperties: z.array(z.string()).optional(),
|
|
76
|
+
conditionalCreateOnly: z.array(z.string()).optional(),
|
|
77
|
+
replacementStrategy: z.enum(["delete_then_create", "create_then_delete"]).optional(),
|
|
78
|
+
tagging: z.object({
|
|
79
|
+
taggable: z.boolean(),
|
|
80
|
+
tagOnCreate: z.boolean(),
|
|
81
|
+
tagUpdatable: z.boolean(),
|
|
82
|
+
}).optional(),
|
|
75
83
|
runtimeDeprecations: z.record(z.string(), z.string()).optional(),
|
|
76
84
|
});
|
|
77
85
|
|
package/src/lexicon.ts
CHANGED
|
@@ -97,6 +97,18 @@ export interface IntrinsicDef {
|
|
|
97
97
|
readonly isTag?: boolean;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Structured init template output from a lexicon plugin.
|
|
102
|
+
*/
|
|
103
|
+
export interface InitTemplateSet {
|
|
104
|
+
/** Source files written to src/ */
|
|
105
|
+
src: Record<string, string>;
|
|
106
|
+
/** Application scaffold files written to project root */
|
|
107
|
+
root?: Record<string, string>;
|
|
108
|
+
/** Scripts merged into generated package.json */
|
|
109
|
+
scripts?: Record<string, string>;
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
/**
|
|
101
113
|
* Plugin interface for lexicon packages.
|
|
102
114
|
*
|
|
@@ -156,7 +168,7 @@ export interface LexiconPlugin {
|
|
|
156
168
|
skills?(): SkillDefinition[];
|
|
157
169
|
|
|
158
170
|
/** Return source file templates for `chant init` project scaffolding */
|
|
159
|
-
initTemplates?():
|
|
171
|
+
initTemplates?(template?: string): InitTemplateSet;
|
|
160
172
|
|
|
161
173
|
/** Optional initialization hook */
|
|
162
174
|
init?(): void | Promise<void>;
|
package/src/lint/declarative.ts
CHANGED
|
@@ -39,6 +39,10 @@ export interface RuleSpec {
|
|
|
39
39
|
severity: Severity;
|
|
40
40
|
/** Category for grouping */
|
|
41
41
|
category: Category;
|
|
42
|
+
/** Human-readable description of what this rule checks */
|
|
43
|
+
description?: string;
|
|
44
|
+
/** Link to rule documentation */
|
|
45
|
+
helpUri?: string;
|
|
42
46
|
/** Selector name (or compound) to find target nodes */
|
|
43
47
|
selector: string;
|
|
44
48
|
/** Optional match condition to further filter nodes */
|
|
@@ -61,6 +65,8 @@ export function rule(spec: RuleSpec): LintRule {
|
|
|
61
65
|
id: spec.id,
|
|
62
66
|
severity: spec.severity,
|
|
63
67
|
category: spec.category,
|
|
68
|
+
description: spec.description,
|
|
69
|
+
helpUri: spec.helpUri,
|
|
64
70
|
check(context: LintContext): LintDiagnostic[] {
|
|
65
71
|
const diagnostics: LintDiagnostic[] = [];
|
|
66
72
|
const sf = context.sourceFile;
|