@intentius/chant 0.0.4 → 0.0.8

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 (75) hide show
  1. package/README.md +10 -351
  2. package/bin/chant +20 -0
  3. package/package.json +18 -17
  4. package/src/bench.test.ts +3 -54
  5. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +8 -23
  6. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +22 -18
  7. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +8 -23
  8. package/src/cli/commands/build.ts +1 -2
  9. package/src/cli/commands/import.test.ts +1 -1
  10. package/src/cli/commands/import.ts +2 -2
  11. package/src/cli/commands/init-lexicon.test.ts +0 -3
  12. package/src/cli/commands/init-lexicon.ts +31 -95
  13. package/src/cli/commands/init.test.ts +10 -14
  14. package/src/cli/commands/init.ts +16 -10
  15. package/src/cli/commands/lint.ts +9 -33
  16. package/src/cli/commands/list.ts +2 -2
  17. package/src/cli/commands/update.ts +5 -3
  18. package/src/cli/conflict-check.test.ts +0 -1
  19. package/src/cli/handlers/dev.ts +1 -9
  20. package/src/cli/main.ts +14 -4
  21. package/src/cli/mcp/server.test.ts +207 -4
  22. package/src/cli/mcp/server.ts +6 -0
  23. package/src/cli/mcp/tools/explain.ts +134 -0
  24. package/src/cli/mcp/tools/scaffold.ts +107 -0
  25. package/src/cli/mcp/tools/search.ts +98 -0
  26. package/src/codegen/docs-interpolation.test.ts +2 -2
  27. package/src/codegen/docs.ts +5 -4
  28. package/src/codegen/generate-registry.test.ts +2 -2
  29. package/src/codegen/generate-registry.ts +5 -6
  30. package/src/codegen/generate-typescript.test.ts +6 -6
  31. package/src/codegen/generate-typescript.ts +2 -6
  32. package/src/codegen/generate.ts +1 -12
  33. package/src/codegen/package.ts +28 -1
  34. package/src/codegen/typecheck.ts +6 -11
  35. package/src/codegen/validate.ts +16 -0
  36. package/src/config.ts +4 -0
  37. package/src/discovery/files.ts +6 -6
  38. package/src/discovery/import.ts +1 -1
  39. package/src/index.ts +1 -2
  40. package/src/lexicon-integrity.ts +5 -4
  41. package/src/lexicon.ts +2 -6
  42. package/src/lint/config.ts +8 -6
  43. package/src/lint/engine.ts +1 -5
  44. package/src/lint/rule.ts +0 -18
  45. package/src/lint/rules/evl009-composite-no-constant.test.ts +24 -8
  46. package/src/lint/rules/evl009-composite-no-constant.ts +50 -29
  47. package/src/lint/rules/index.ts +1 -22
  48. package/src/runtime-adapter.ts +158 -0
  49. package/src/serializer-walker.test.ts +0 -9
  50. package/src/serializer-walker.ts +1 -3
  51. package/src/stack-output.ts +3 -3
  52. package/src/barrel.test.ts +0 -157
  53. package/src/barrel.ts +0 -101
  54. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +0 -45
  55. package/src/codegen/case.test.ts +0 -30
  56. package/src/codegen/case.ts +0 -11
  57. package/src/codegen/rollback.test.ts +0 -92
  58. package/src/codegen/rollback.ts +0 -115
  59. package/src/lint/rules/barrel-import-style.test.ts +0 -80
  60. package/src/lint/rules/barrel-import-style.ts +0 -59
  61. package/src/lint/rules/enforce-barrel-import.test.ts +0 -169
  62. package/src/lint/rules/enforce-barrel-import.ts +0 -81
  63. package/src/lint/rules/enforce-barrel-ref.test.ts +0 -114
  64. package/src/lint/rules/enforce-barrel-ref.ts +0 -75
  65. package/src/lint/rules/evl006-barrel-usage.test.ts +0 -63
  66. package/src/lint/rules/evl006-barrel-usage.ts +0 -95
  67. package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +0 -118
  68. package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +0 -140
  69. package/src/lint/rules/prefer-namespace-import.test.ts +0 -102
  70. package/src/lint/rules/prefer-namespace-import.ts +0 -63
  71. package/src/lint/rules/stale-barrel-types.ts +0 -60
  72. package/src/project/scan.test.ts +0 -178
  73. package/src/project/scan.ts +0 -182
  74. package/src/project/sync.test.ts +0 -87
  75. package/src/project/sync.ts +0 -46
@@ -1,118 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import * as ts from "typescript";
3
- import { evl008UnresolvableBarrelRefRule } from "./evl008-unresolvable-barrel-ref";
4
- import type { LintContext } from "../rule";
5
-
6
- function createContext(
7
- code: string,
8
- barrelExports?: Set<string>,
9
- filePath = "test.ts",
10
- ): LintContext {
11
- const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
12
- return { sourceFile, entities: [], filePath, lexicon: undefined, barrelExports };
13
- }
14
-
15
- describe("EVL008: unresolvable-barrel-ref", () => {
16
- test("rule metadata", () => {
17
- expect(evl008UnresolvableBarrelRefRule.id).toBe("EVL008");
18
- expect(evl008UnresolvableBarrelRefRule.severity).toBe("error");
19
- expect(evl008UnresolvableBarrelRefRule.category).toBe("correctness");
20
- });
21
-
22
- test("skips when no barrelExports provided", () => {
23
- const ctx = createContext(`
24
- export const $ = barrel(import.meta.dir);
25
- const x = $.myBucket;
26
- `);
27
- expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
28
- });
29
-
30
- test("allows valid direct barrel reference", () => {
31
- const exports = new Set(["myBucket", "myRole"]);
32
- const ctx = createContext(
33
- `
34
- export const $ = barrel(import.meta.dir);
35
- const x = $.myBucket;
36
- `,
37
- exports,
38
- );
39
- expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
40
- });
41
-
42
- test("flags invalid direct barrel reference", () => {
43
- const exports = new Set(["myBucket", "myRole"]);
44
- const ctx = createContext(
45
- `
46
- export const $ = barrel(import.meta.dir);
47
- const x = $.nonExistent;
48
- `,
49
- exports,
50
- );
51
- const diags = evl008UnresolvableBarrelRefRule.check(ctx);
52
- expect(diags).toHaveLength(1);
53
- expect(diags[0].ruleId).toBe("EVL008");
54
- expect(diags[0].message).toContain("nonExistent");
55
- expect(diags[0].message).toContain("not exported");
56
- });
57
-
58
- test("allows valid namespace barrel reference", () => {
59
- const exports = new Set(["myBucket"]);
60
- const ctx = createContext(
61
- `
62
- import * as _ from "./_";
63
- const x = _.$.myBucket;
64
- `,
65
- exports,
66
- );
67
- expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
68
- });
69
-
70
- test("flags invalid namespace barrel reference", () => {
71
- const exports = new Set(["myBucket"]);
72
- const ctx = createContext(
73
- `
74
- import * as _ from "./_";
75
- const x = _.$.missing;
76
- `,
77
- exports,
78
- );
79
- const diags = evl008UnresolvableBarrelRefRule.check(ctx);
80
- expect(diags).toHaveLength(1);
81
- expect(diags[0].message).toContain("missing");
82
- });
83
-
84
- test("does not flag when no barrel usage in file", () => {
85
- const exports = new Set(["myBucket"]);
86
- const ctx = createContext(`const x = someObj.prop;`, exports);
87
- expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
88
- });
89
-
90
- test("flags multiple invalid references", () => {
91
- const exports = new Set(["myBucket"]);
92
- const ctx = createContext(
93
- `
94
- export const $ = barrel(import.meta.dir);
95
- const a = $.missing1;
96
- const b = $.missing2;
97
- `,
98
- exports,
99
- );
100
- expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(2);
101
- });
102
-
103
- test("handles mixed valid and invalid", () => {
104
- const exports = new Set(["bucket", "role"]);
105
- const ctx = createContext(
106
- `
107
- export const $ = barrel(import.meta.dir);
108
- const a = $.bucket;
109
- const b = $.missing;
110
- const c = $.role;
111
- `,
112
- exports,
113
- );
114
- const diags = evl008UnresolvableBarrelRefRule.check(ctx);
115
- expect(diags).toHaveLength(1);
116
- expect(diags[0].message).toContain("missing");
117
- });
118
- });
@@ -1,140 +0,0 @@
1
- import * as ts from "typescript";
2
- import type { LintRule, LintContext, LintDiagnostic } from "../rule";
3
-
4
- /**
5
- * EVL008: Unresolvable Barrel Reference
6
- *
7
- * When accessing the barrel variable (e.g., $.resourceName), the
8
- * referenced name must exist in the project's barrel exports.
9
- * Requires barrelExports on LintContext.
10
- */
11
-
12
- /**
13
- * Find the barrel variable name in a source file.
14
- *
15
- * Patterns:
16
- * - `export const $ = barrel(...)` → "$"
17
- * - `import * as _ from "./_"` then `_.$` → "$" accessed via "_"
18
- */
19
- function findBarrelVarName(sourceFile: ts.SourceFile): string | null {
20
- for (const stmt of sourceFile.statements) {
21
- // export const $ = barrel(...)
22
- if (
23
- ts.isVariableStatement(stmt) &&
24
- stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
25
- ) {
26
- for (const decl of stmt.declarationList.declarations) {
27
- if (
28
- ts.isIdentifier(decl.name) &&
29
- decl.name.text === "$" &&
30
- decl.initializer &&
31
- ts.isCallExpression(decl.initializer) &&
32
- ts.isIdentifier(decl.initializer.expression) &&
33
- decl.initializer.expression.text === "barrel"
34
- ) {
35
- return "$";
36
- }
37
- }
38
- }
39
- }
40
- return null;
41
- }
42
-
43
- /**
44
- * Find namespace imports that re-export the barrel.
45
- * e.g., `import * as _ from "./_"` — then _.$ accesses the barrel.
46
- */
47
- function findBarrelNamespaceImports(sourceFile: ts.SourceFile): string[] {
48
- const names: string[] = [];
49
- for (const stmt of sourceFile.statements) {
50
- if (
51
- ts.isImportDeclaration(stmt) &&
52
- stmt.importClause?.namedBindings &&
53
- ts.isNamespaceImport(stmt.importClause.namedBindings) &&
54
- stmt.moduleSpecifier &&
55
- ts.isStringLiteral(stmt.moduleSpecifier)
56
- ) {
57
- const spec = stmt.moduleSpecifier.text;
58
- // Convention: barrel files are "_" or "./_"
59
- if (spec === "./_" || spec === "_") {
60
- names.push(stmt.importClause.namedBindings.name.text);
61
- }
62
- }
63
- }
64
- return names;
65
- }
66
-
67
- function checkNode(
68
- node: ts.Node,
69
- context: LintContext,
70
- diagnostics: LintDiagnostic[],
71
- barrelVar: string | null,
72
- namespaceImports: string[],
73
- ): void {
74
- if (ts.isPropertyAccessExpression(node)) {
75
- const accessedName = node.name.text;
76
-
77
- // Direct barrel access: $.resourceName
78
- if (barrelVar && ts.isIdentifier(node.expression) && node.expression.text === barrelVar) {
79
- if (context.barrelExports && !context.barrelExports.has(accessedName)) {
80
- const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
81
- node.getStart(context.sourceFile),
82
- );
83
- diagnostics.push({
84
- file: context.filePath,
85
- line: line + 1,
86
- column: character + 1,
87
- ruleId: "EVL008",
88
- severity: "error",
89
- message: `Unresolvable barrel reference — "${accessedName}" is not exported from the barrel`,
90
- });
91
- }
92
- }
93
-
94
- // Namespace access: _.$.resourceName
95
- if (
96
- ts.isPropertyAccessExpression(node.expression) &&
97
- ts.isIdentifier(node.expression.expression) &&
98
- namespaceImports.includes(node.expression.expression.text) &&
99
- node.expression.name.text === "$"
100
- ) {
101
- if (context.barrelExports && !context.barrelExports.has(accessedName)) {
102
- const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
103
- node.getStart(context.sourceFile),
104
- );
105
- diagnostics.push({
106
- file: context.filePath,
107
- line: line + 1,
108
- column: character + 1,
109
- ruleId: "EVL008",
110
- severity: "error",
111
- message: `Unresolvable barrel reference — "${accessedName}" is not exported from the barrel`,
112
- });
113
- }
114
- }
115
- }
116
-
117
- ts.forEachChild(node, (child) =>
118
- checkNode(child, context, diagnostics, barrelVar, namespaceImports),
119
- );
120
- }
121
-
122
- export const evl008UnresolvableBarrelRefRule: LintRule = {
123
- id: "EVL008",
124
- severity: "error",
125
- category: "correctness",
126
- check(context: LintContext): LintDiagnostic[] {
127
- // Skip if no barrel exports data is available
128
- if (!context.barrelExports) return [];
129
-
130
- const barrelVar = findBarrelVarName(context.sourceFile);
131
- const namespaceImports = findBarrelNamespaceImports(context.sourceFile);
132
-
133
- // If this file doesn't use the barrel, nothing to check
134
- if (!barrelVar && namespaceImports.length === 0) return [];
135
-
136
- const diagnostics: LintDiagnostic[] = [];
137
- checkNode(context.sourceFile, context, diagnostics, barrelVar, namespaceImports);
138
- return diagnostics;
139
- },
140
- };
@@ -1,102 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import * as ts from "typescript";
3
- import { preferNamespaceImportRule } from "./prefer-namespace-import";
4
- import type { LintContext } from "../rule";
5
-
6
- function createContext(code: string, filePath = "test.ts"): LintContext {
7
- const sourceFile = ts.createSourceFile(
8
- filePath,
9
- code,
10
- ts.ScriptTarget.Latest,
11
- true
12
- );
13
-
14
- return {
15
- sourceFile,
16
- entities: [],
17
- filePath,
18
- lexicon: undefined,
19
- };
20
- }
21
-
22
- describe("COR006: prefer-namespace-import", () => {
23
- test("rule metadata", () => {
24
- expect(preferNamespaceImportRule.id).toBe("COR006");
25
- expect(preferNamespaceImportRule.severity).toBe("error");
26
- expect(preferNamespaceImportRule.category).toBe("style");
27
- });
28
-
29
- test("flags named import from @intentius/chant* package", () => {
30
- const code = `import { Bucket } from "@intentius/chant-lexicon-testdom";`;
31
- const context = createContext(code);
32
- const diagnostics = preferNamespaceImportRule.check(context);
33
-
34
- expect(diagnostics).toHaveLength(1);
35
- expect(diagnostics[0].ruleId).toBe("COR006");
36
- expect(diagnostics[0].severity).toBe("error");
37
- expect(diagnostics[0].message).toBe(
38
- `Use namespace import for @intentius/chant-lexicon-testdom — replace with: import * as testdom from "@intentius/chant-lexicon-testdom"`
39
- );
40
- });
41
-
42
- test("allows namespace import from @intentius/chant* package", () => {
43
- const code = `import * as testdom from "@intentius/chant-lexicon-testdom";`;
44
- const context = createContext(code);
45
- const diagnostics = preferNamespaceImportRule.check(context);
46
- expect(diagnostics).toHaveLength(0);
47
- });
48
-
49
- test("allows type-only import from @intentius/chant* package", () => {
50
- const code = `import type { Declarable } from "@intentius/chant";`;
51
- const context = createContext(code);
52
- const diagnostics = preferNamespaceImportRule.check(context);
53
- expect(diagnostics).toHaveLength(0);
54
- });
55
-
56
- test("does not flag imports from non-@intentius/chant packages", () => {
57
- const code = `import { useState } from "react";`;
58
- const context = createContext(code);
59
- const diagnostics = preferNamespaceImportRule.check(context);
60
- expect(diagnostics).toHaveLength(0);
61
- });
62
-
63
- test("does not flag imports from relative paths", () => {
64
- const code = `import { helper } from "./utils";`;
65
- const context = createContext(code);
66
- const diagnostics = preferNamespaceImportRule.check(context);
67
- expect(diagnostics).toHaveLength(0);
68
- });
69
-
70
- test("derives alias from package name", () => {
71
- const code = `import { Config } from "@intentius/chant";`;
72
- const context = createContext(code);
73
- const diagnostics = preferNamespaceImportRule.check(context);
74
-
75
- expect(diagnostics).toHaveLength(1);
76
- expect(diagnostics[0].message).toContain("import * as core");
77
- });
78
-
79
- test("flags multiple named imports from different @intentius/chant packages", () => {
80
- const code = `
81
- import { Bucket } from "@intentius/chant-lexicon-testdom";
82
- import { Config } from "@intentius/chant";
83
- `;
84
- const context = createContext(code);
85
- const diagnostics = preferNamespaceImportRule.check(context);
86
-
87
- expect(diagnostics).toHaveLength(2);
88
- expect(diagnostics[0].message).toContain("@intentius/chant-lexicon-testdom");
89
- expect(diagnostics[1].message).toContain("@intentius/chant");
90
- });
91
-
92
- test("reports correct line and column numbers", () => {
93
- const code = `import { Bucket } from "@intentius/chant-lexicon-testdom";`;
94
- const context = createContext(code);
95
- const diagnostics = preferNamespaceImportRule.check(context);
96
-
97
- expect(diagnostics).toHaveLength(1);
98
- expect(diagnostics[0].line).toBe(1);
99
- expect(diagnostics[0].column).toBe(1);
100
- expect(diagnostics[0].file).toBe("test.ts");
101
- });
102
- });
@@ -1,63 +0,0 @@
1
- import * as ts from "typescript";
2
- import type { LintRule, LintContext, LintDiagnostic } from "../rule";
3
-
4
- /**
5
- * COR006: prefer-namespace-import
6
- *
7
- * Enforce `import * as pkg` for `@intentius/chant*` package imports.
8
- *
9
- * Triggers on: import { Bucket } from "@intentius/chant-lexicon-<name>"
10
- * OK: import * as <name> from "@intentius/chant-lexicon-<name>"
11
- * OK: import type { Declarable } from "@intentius/chant"
12
- */
13
-
14
- function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
15
- if (ts.isImportDeclaration(node)) {
16
- const moduleSpecifier = node.moduleSpecifier;
17
- if (!ts.isStringLiteral(moduleSpecifier)) return;
18
-
19
- const modulePath = moduleSpecifier.text;
20
- if (!modulePath.startsWith("@intentius/chant") && !modulePath.startsWith("@intentius/chant-lexicon-")) return;
21
-
22
- // Skip type-only imports: import type { X } from "..."
23
- if (node.importClause?.isTypeOnly) return;
24
-
25
- const importClause = node.importClause;
26
- if (!importClause?.namedBindings) return;
27
-
28
- // Flag named imports (not namespace imports)
29
- if (ts.isNamedImports(importClause.namedBindings)) {
30
- const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
31
- node.getStart(context.sourceFile)
32
- );
33
-
34
- const pkgName = modulePath === "@intentius/chant"
35
- ? "core"
36
- : modulePath.startsWith("@intentius/chant-lexicon-")
37
- ? modulePath.replace("@intentius/chant-lexicon-", "")
38
- : modulePath.replace("@intentius/chant-", "");
39
-
40
- diagnostics.push({
41
- file: context.filePath,
42
- line: line + 1,
43
- column: character + 1,
44
- ruleId: "COR006",
45
- severity: "error",
46
- message: `Use namespace import for ${modulePath} — replace with: import * as ${pkgName} from "${modulePath}"`,
47
- });
48
- }
49
- }
50
-
51
- ts.forEachChild(node, child => checkNode(child, context, diagnostics));
52
- }
53
-
54
- export const preferNamespaceImportRule: LintRule = {
55
- id: "COR006",
56
- severity: "error",
57
- category: "style",
58
- check(context: LintContext): LintDiagnostic[] {
59
- const diagnostics: LintDiagnostic[] = [];
60
- checkNode(context.sourceFile, context, diagnostics);
61
- return diagnostics;
62
- },
63
- };
@@ -1,60 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { join, dirname } from "node:path";
3
- import type { LintRule, LintContext, LintDiagnostic } from "../rule";
4
- import { generateBarrelTypes } from "../../project/sync";
5
-
6
- /**
7
- * COR016: stale-barrel-types
8
- *
9
- * Checks that `_.d.ts` barrel type declarations are up-to-date with
10
- * the current project scan. Fires only on `_.ts` barrel files.
11
- *
12
- * When --fix is used, the fix writes the expected content to `_.d.ts`.
13
- */
14
- export const staleBarrelTypesRule: LintRule = {
15
- id: "COR016",
16
- severity: "warning",
17
- category: "correctness",
18
- check(context: LintContext): LintDiagnostic[] {
19
- // Only fire on _.ts barrel files
20
- if (!context.filePath.endsWith("/_.ts") && context.filePath !== "_.ts") {
21
- return [];
22
- }
23
-
24
- // Requires projectScan
25
- if (!context.projectScan) return [];
26
-
27
- const expected = generateBarrelTypes(context.projectScan);
28
- const dtsPath = join(dirname(context.filePath), "_.d.ts");
29
-
30
- let existing: string | undefined;
31
- try {
32
- existing = readFileSync(dtsPath, "utf-8");
33
- } catch {
34
- // File doesn't exist
35
- }
36
-
37
- if (existing === expected) return [];
38
-
39
- const message = existing === undefined
40
- ? "Missing _.d.ts barrel type declarations. Run 'chant lint --fix' to generate."
41
- : "Stale _.d.ts barrel type declarations. Run 'chant lint --fix' to regenerate.";
42
-
43
- return [
44
- {
45
- file: context.filePath,
46
- line: 1,
47
- column: 1,
48
- ruleId: "COR016",
49
- severity: "warning",
50
- message,
51
- fix: {
52
- range: [0, 0],
53
- replacement: "",
54
- kind: "write-file",
55
- params: { path: dtsPath, content: expected },
56
- },
57
- },
58
- ];
59
- },
60
- };
@@ -1,178 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { scanProject } from "./scan";
3
- import { withTestDir } from "@intentius/chant-test-utils";
4
- import { writeFile } from "node:fs/promises";
5
- import { join } from "node:path";
6
-
7
- describe("scanProject", () => {
8
- test("finds barrel and extracts lexicon package", async () => {
9
- await withTestDir(async (dir) => {
10
- await writeFile(
11
- join(dir, "_.ts"),
12
- `export * from "@intentius/chant-lexicon-testdom";\nexport * from "./storage";\n`,
13
- );
14
- await writeFile(
15
- join(dir, "storage.ts"),
16
- `import * as td from "@intentius/chant-lexicon-testdom";\nexport const bucket = new td.Bucket({});\n`,
17
- );
18
-
19
- const scan = scanProject(dir);
20
- expect(scan.barrelPath).toBe(join(dir, "_.ts"));
21
- expect(scan.lexiconPackage).toBe("@intentius/chant-lexicon-testdom");
22
- });
23
- });
24
-
25
- test("collects exports from sibling files", async () => {
26
- await withTestDir(async (dir) => {
27
- await writeFile(
28
- join(dir, "_.ts"),
29
- `export * from "@intentius/chant-lexicon-testdom";\n`,
30
- );
31
- await writeFile(
32
- join(dir, "storage.ts"),
33
- `import * as td from "@intentius/chant-lexicon-testdom";\nexport const dataBucket = new td.Bucket({});\nexport const logsBucket = new td.Bucket({});\n`,
34
- );
35
- await writeFile(
36
- join(dir, "compute.ts"),
37
- `import * as td from "@intentius/chant-lexicon-testdom";\nexport const handler = new td.Function({});\n`,
38
- );
39
-
40
- const scan = scanProject(dir);
41
- expect(scan.exports).toHaveLength(3);
42
- const names = scan.exports.map((e) => e.name);
43
- expect(names).toContain("dataBucket");
44
- expect(names).toContain("logsBucket");
45
- expect(names).toContain("handler");
46
- });
47
- });
48
-
49
- test("infers class names from new X.ClassName(...) patterns", async () => {
50
- await withTestDir(async (dir) => {
51
- await writeFile(
52
- join(dir, "_.ts"),
53
- `export * from "@intentius/chant-lexicon-testdom";\n`,
54
- );
55
- await writeFile(
56
- join(dir, "resources.ts"),
57
- [
58
- `import * as td from "@intentius/chant-lexicon-testdom";`,
59
- `export const bucket = new td.Bucket({});`,
60
- `export const role = new td.Role({});`,
61
- `export const fn = new td.Function({});`,
62
- ].join("\n"),
63
- );
64
-
65
- const scan = scanProject(dir);
66
- expect(scan.exports).toHaveLength(3);
67
-
68
- const bucket = scan.exports.find((e) => e.name === "bucket");
69
- expect(bucket?.className).toBe("Bucket");
70
-
71
- const role = scan.exports.find((e) => e.name === "role");
72
- expect(role?.className).toBe("Role");
73
-
74
- const fn = scan.exports.find((e) => e.name === "fn");
75
- expect(fn?.className).toBe("Function");
76
- });
77
- });
78
-
79
- test("infers class names from type annotations", async () => {
80
- await withTestDir(async (dir) => {
81
- await writeFile(
82
- join(dir, "_.ts"),
83
- `export * from "@intentius/chant-lexicon-testdom";\n`,
84
- );
85
- await writeFile(
86
- join(dir, "config.ts"),
87
- [
88
- `import * as td from "@intentius/chant-lexicon-testdom";`,
89
- `export const encryption: td.ServerSideEncryptionByDefault = { sseAlgorithm: "AES256" };`,
90
- `export const versioning: td.VersioningConfiguration = { status: "Enabled" };`,
91
- ].join("\n"),
92
- );
93
-
94
- const scan = scanProject(dir);
95
- expect(scan.exports).toHaveLength(2);
96
-
97
- const encryption = scan.exports.find((e) => e.name === "encryption");
98
- expect(encryption?.className).toBe("ServerSideEncryptionByDefault");
99
-
100
- const versioning = scan.exports.find((e) => e.name === "versioning");
101
- expect(versioning?.className).toBe("VersioningConfiguration");
102
- });
103
- });
104
-
105
- test("excludes barrel, test files, and underscore-prefixed files", async () => {
106
- await withTestDir(async (dir) => {
107
- await writeFile(
108
- join(dir, "_.ts"),
109
- `export * from "@intentius/chant-lexicon-testdom";\n`,
110
- );
111
- await writeFile(
112
- join(dir, "_internal.ts"),
113
- `export const secret = "hidden";\n`,
114
- );
115
- await writeFile(
116
- join(dir, "app.test.ts"),
117
- `import { test } from "bun:test";\ntest("passes", () => {});\n`,
118
- );
119
- await writeFile(
120
- join(dir, "app.spec.ts"),
121
- `import { test } from "bun:test";\ntest("passes", () => {});\n`,
122
- );
123
- await writeFile(
124
- join(dir, "app.ts"),
125
- `export const app = "hello";\n`,
126
- );
127
-
128
- const scan = scanProject(dir);
129
- expect(scan.exports).toHaveLength(1);
130
- expect(scan.exports[0].name).toBe("app");
131
- });
132
- });
133
-
134
- test("throws on missing barrel", async () => {
135
- await withTestDir(async (dir) => {
136
- expect(() => scanProject(dir)).toThrow("No barrel file found");
137
- });
138
- });
139
-
140
- test("returns empty className for exports without recognizable patterns", async () => {
141
- await withTestDir(async (dir) => {
142
- await writeFile(
143
- join(dir, "_.ts"),
144
- `export * from "@intentius/chant-lexicon-testdom";\n`,
145
- );
146
- await writeFile(
147
- join(dir, "utils.ts"),
148
- `export const greeting = "hello";\nexport const count = 42;\n`,
149
- );
150
-
151
- const scan = scanProject(dir);
152
- expect(scan.exports).toHaveLength(2);
153
- for (const exp of scan.exports) {
154
- expect(exp.className).toBe("");
155
- }
156
- });
157
- });
158
-
159
- test("infers class names from new _.ClassName(...) patterns", async () => {
160
- await withTestDir(async (dir) => {
161
- await writeFile(
162
- join(dir, "_.ts"),
163
- `export * from "@intentius/chant-lexicon-testdom";\n`,
164
- );
165
- await writeFile(
166
- join(dir, "resources.ts"),
167
- [
168
- `import * as _ from "./_";`,
169
- `export const bucket = new _.Bucket({});`,
170
- ].join("\n"),
171
- );
172
-
173
- const scan = scanProject(dir);
174
- expect(scan.exports).toHaveLength(1);
175
- expect(scan.exports[0].className).toBe("Bucket");
176
- });
177
- });
178
- });