@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.
- package/README.md +365 -0
- package/package.json +22 -0
- package/src/attrref.test.ts +148 -0
- package/src/attrref.ts +50 -0
- package/src/barrel.test.ts +157 -0
- package/src/barrel.ts +101 -0
- package/src/bench.test.ts +227 -0
- package/src/build.test.ts +437 -0
- package/src/build.ts +425 -0
- package/src/builder.test.ts +312 -0
- package/src/builder.ts +56 -0
- package/src/child-project.ts +44 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
- package/src/cli/commands/build.test.ts +149 -0
- package/src/cli/commands/build.ts +344 -0
- package/src/cli/commands/diff.test.ts +148 -0
- package/src/cli/commands/diff.ts +221 -0
- package/src/cli/commands/doctor.test.ts +239 -0
- package/src/cli/commands/doctor.ts +224 -0
- package/src/cli/commands/import.test.ts +379 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/init-lexicon.test.ts +297 -0
- package/src/cli/commands/init-lexicon.ts +993 -0
- package/src/cli/commands/init.test.ts +317 -0
- package/src/cli/commands/init.ts +505 -0
- package/src/cli/commands/licenses.ts +165 -0
- package/src/cli/commands/lint.test.ts +332 -0
- package/src/cli/commands/lint.ts +408 -0
- package/src/cli/commands/list.test.ts +100 -0
- package/src/cli/commands/list.ts +108 -0
- package/src/cli/commands/update.test.ts +38 -0
- package/src/cli/commands/update.ts +207 -0
- package/src/cli/conflict-check.test.ts +255 -0
- package/src/cli/conflict-check.ts +89 -0
- package/src/cli/debug.ts +8 -0
- package/src/cli/format.test.ts +140 -0
- package/src/cli/format.ts +133 -0
- package/src/cli/handlers/build.ts +58 -0
- package/src/cli/handlers/dev.ts +38 -0
- package/src/cli/handlers/init.ts +46 -0
- package/src/cli/handlers/lint.ts +36 -0
- package/src/cli/handlers/misc.ts +57 -0
- package/src/cli/handlers/serve.ts +26 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/lsp/capabilities.ts +46 -0
- package/src/cli/lsp/diagnostics.ts +52 -0
- package/src/cli/lsp/server.test.ts +618 -0
- package/src/cli/lsp/server.ts +393 -0
- package/src/cli/main.test.ts +257 -0
- package/src/cli/main.ts +224 -0
- package/src/cli/mcp/resources/context.ts +59 -0
- package/src/cli/mcp/server.test.ts +747 -0
- package/src/cli/mcp/server.ts +402 -0
- package/src/cli/mcp/tools/build.ts +117 -0
- package/src/cli/mcp/tools/import.ts +48 -0
- package/src/cli/mcp/tools/lint.ts +45 -0
- package/src/cli/plugins.test.ts +31 -0
- package/src/cli/plugins.ts +94 -0
- package/src/cli/registry.ts +73 -0
- package/src/cli/reporters/stylish.test.ts +282 -0
- package/src/cli/reporters/stylish.ts +186 -0
- package/src/cli/watch.test.ts +81 -0
- package/src/cli/watch.ts +101 -0
- package/src/codegen/case.test.ts +30 -0
- package/src/codegen/case.ts +11 -0
- package/src/codegen/coverage.ts +167 -0
- package/src/codegen/docs.ts +634 -0
- package/src/codegen/fetch.test.ts +119 -0
- package/src/codegen/fetch.ts +261 -0
- package/src/codegen/generate-registry.test.ts +118 -0
- package/src/codegen/generate-registry.ts +107 -0
- package/src/codegen/generate-runtime-index.test.ts +81 -0
- package/src/codegen/generate-runtime-index.ts +99 -0
- package/src/codegen/generate-typescript.test.ts +146 -0
- package/src/codegen/generate-typescript.ts +161 -0
- package/src/codegen/generate.ts +206 -0
- package/src/codegen/json-patch.test.ts +113 -0
- package/src/codegen/json-patch.ts +151 -0
- package/src/codegen/json-schema.test.ts +196 -0
- package/src/codegen/json-schema.ts +209 -0
- package/src/codegen/naming.ts +201 -0
- package/src/codegen/package.ts +161 -0
- package/src/codegen/rollback.test.ts +92 -0
- package/src/codegen/rollback.ts +115 -0
- package/src/codegen/topo-sort.test.ts +69 -0
- package/src/codegen/topo-sort.ts +46 -0
- package/src/codegen/typecheck.test.ts +37 -0
- package/src/codegen/typecheck.ts +74 -0
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +143 -0
- package/src/composite.test.ts +426 -0
- package/src/composite.ts +243 -0
- package/src/config.test.ts +91 -0
- package/src/config.ts +87 -0
- package/src/declarable.test.ts +160 -0
- package/src/declarable.ts +47 -0
- package/src/detectLexicon.test.ts +236 -0
- package/src/detectLexicon.ts +37 -0
- package/src/discovery/cache.test.ts +78 -0
- package/src/discovery/cache.ts +86 -0
- package/src/discovery/collect.test.ts +269 -0
- package/src/discovery/collect.ts +51 -0
- package/src/discovery/cycles.test.ts +238 -0
- package/src/discovery/cycles.ts +107 -0
- package/src/discovery/files.test.ts +154 -0
- package/src/discovery/files.ts +61 -0
- package/src/discovery/graph.test.ts +476 -0
- package/src/discovery/graph.ts +150 -0
- package/src/discovery/import.test.ts +199 -0
- package/src/discovery/import.ts +20 -0
- package/src/discovery/index.test.ts +272 -0
- package/src/discovery/index.ts +132 -0
- package/src/discovery/resolve.test.ts +267 -0
- package/src/discovery/resolve.ts +54 -0
- package/src/errors.test.ts +138 -0
- package/src/errors.ts +86 -0
- package/src/import/base-parser.test.ts +67 -0
- package/src/import/base-parser.ts +48 -0
- package/src/import/generator.ts +21 -0
- package/src/import/ir-utils.test.ts +103 -0
- package/src/import/ir-utils.ts +87 -0
- package/src/import/parser.ts +41 -0
- package/src/index.ts +60 -0
- package/src/intrinsic-interpolation.test.ts +91 -0
- package/src/intrinsic-interpolation.ts +89 -0
- package/src/intrinsic.test.ts +69 -0
- package/src/intrinsic.ts +43 -0
- package/src/lexicon-integrity.test.ts +94 -0
- package/src/lexicon-integrity.ts +69 -0
- package/src/lexicon-manifest.test.ts +101 -0
- package/src/lexicon-manifest.ts +71 -0
- package/src/lexicon-output.test.ts +182 -0
- package/src/lexicon-output.ts +82 -0
- package/src/lexicon-schema.test.ts +239 -0
- package/src/lexicon-schema.ts +144 -0
- package/src/lexicon.ts +212 -0
- package/src/lint/config-overrides.test.ts +254 -0
- package/src/lint/config.test.ts +644 -0
- package/src/lint/config.ts +375 -0
- package/src/lint/declarative.test.ts +256 -0
- package/src/lint/declarative.ts +187 -0
- package/src/lint/engine.test.ts +465 -0
- package/src/lint/engine.ts +172 -0
- package/src/lint/named-checks.test.ts +37 -0
- package/src/lint/named-checks.ts +33 -0
- package/src/lint/parser.test.ts +129 -0
- package/src/lint/parser.ts +42 -0
- package/src/lint/post-synth.test.ts +113 -0
- package/src/lint/post-synth.ts +76 -0
- package/src/lint/presets/relaxed.json +19 -0
- package/src/lint/presets/strict.json +19 -0
- package/src/lint/rule-loader.test.ts +67 -0
- package/src/lint/rule-loader.ts +67 -0
- package/src/lint/rule-options.test.ts +141 -0
- package/src/lint/rule.test.ts +196 -0
- package/src/lint/rule.ts +98 -0
- package/src/lint/rules/barrel-import-style.test.ts +80 -0
- package/src/lint/rules/barrel-import-style.ts +59 -0
- package/src/lint/rules/composite-scope.ts +55 -0
- package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
- package/src/lint/rules/cor017-composite-name-match.ts +108 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
- package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
- package/src/lint/rules/declarable-naming-convention.ts +70 -0
- package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
- package/src/lint/rules/enforce-barrel-import.ts +81 -0
- package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
- package/src/lint/rules/enforce-barrel-ref.ts +75 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
- package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
- package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
- package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
- package/src/lint/rules/evl004-spread-non-const.ts +111 -0
- package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
- package/src/lint/rules/evl005-resource-block-body.ts +49 -0
- package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
- package/src/lint/rules/evl006-barrel-usage.ts +95 -0
- package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
- package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
- package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
- package/src/lint/rules/export-required.test.ts +213 -0
- package/src/lint/rules/export-required.ts +158 -0
- package/src/lint/rules/file-declarable-limit.test.ts +148 -0
- package/src/lint/rules/file-declarable-limit.ts +96 -0
- package/src/lint/rules/flat-declarations.test.ts +210 -0
- package/src/lint/rules/flat-declarations.ts +70 -0
- package/src/lint/rules/index.ts +99 -0
- package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
- package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
- package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
- package/src/lint/rules/no-redundant-type-import.ts +85 -0
- package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
- package/src/lint/rules/no-redundant-value-cast.ts +46 -0
- package/src/lint/rules/no-string-ref.test.ts +100 -0
- package/src/lint/rules/no-string-ref.ts +66 -0
- package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
- package/src/lint/rules/no-unused-declarable-import.ts +103 -0
- package/src/lint/rules/no-unused-declarable.test.ts +134 -0
- package/src/lint/rules/no-unused-declarable.ts +118 -0
- package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
- package/src/lint/rules/prefer-namespace-import.ts +63 -0
- package/src/lint/rules/single-concern-file.test.ts +156 -0
- package/src/lint/rules/single-concern-file.ts +98 -0
- package/src/lint/rules/stale-barrel-types.ts +60 -0
- package/src/lint/selectors.test.ts +113 -0
- package/src/lint/selectors.ts +188 -0
- package/src/lsp/lexicon-providers.ts +191 -0
- package/src/lsp/types.ts +79 -0
- package/src/mcp/types.ts +22 -0
- package/src/project/scan.test.ts +178 -0
- package/src/project/scan.ts +182 -0
- package/src/project/sync.test.ts +87 -0
- package/src/project/sync.ts +46 -0
- package/src/project-validation.test.ts +64 -0
- package/src/project-validation.ts +79 -0
- package/src/pseudo-parameter.test.ts +39 -0
- package/src/pseudo-parameter.ts +47 -0
- package/src/runtime.ts +68 -0
- package/src/serializer-walker.test.ts +124 -0
- package/src/serializer-walker.ts +83 -0
- package/src/serializer.ts +42 -0
- package/src/sort.test.ts +290 -0
- package/src/sort.ts +58 -0
- package/src/stack-output.ts +82 -0
- package/src/types.test.ts +307 -0
- package/src/types.ts +46 -0
- package/src/utils.test.ts +195 -0
- package/src/utils.ts +46 -0
- package/src/validation.test.ts +308 -0
- package/src/validation.ts +50 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { DiscoveryCache } from "./cache";
|
|
3
|
+
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
|
|
7
|
+
describe("DiscoveryCache", () => {
|
|
8
|
+
test("hasFileChanged returns true for unknown files", () => {
|
|
9
|
+
const cache = new DiscoveryCache();
|
|
10
|
+
expect(cache.hasFileChanged("/nonexistent/file.ts")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("trackFileImport and hasFileChanged detect changes", async () => {
|
|
14
|
+
const cache = new DiscoveryCache();
|
|
15
|
+
const dir = join(tmpdir(), `chant-cache-test-${Date.now()}`);
|
|
16
|
+
mkdirSync(dir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
const file = join(dir, "test.ts");
|
|
19
|
+
writeFileSync(file, "export const a = 1;");
|
|
20
|
+
|
|
21
|
+
const { statSync: stat } = require("fs");
|
|
22
|
+
const { mtimeMs } = stat(file);
|
|
23
|
+
cache.trackFileImport(file, mtimeMs, ["a"]);
|
|
24
|
+
|
|
25
|
+
// File hasn't changed
|
|
26
|
+
expect(cache.hasFileChanged(file)).toBe(false);
|
|
27
|
+
|
|
28
|
+
// Wait briefly to ensure mtime differs, then modify
|
|
29
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
30
|
+
writeFileSync(file, "export const a = 2;");
|
|
31
|
+
|
|
32
|
+
// File has changed
|
|
33
|
+
expect(cache.hasFileChanged(file)).toBe(true);
|
|
34
|
+
|
|
35
|
+
rmSync(dir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("invalidate removes entries and returns entity names", () => {
|
|
39
|
+
const cache = new DiscoveryCache();
|
|
40
|
+
cache.trackFileImport("/a.ts", 100, ["bucket", "role"]);
|
|
41
|
+
cache.trackFileImport("/b.ts", 200, ["table"]);
|
|
42
|
+
|
|
43
|
+
const invalidated = cache.invalidate(["/a.ts"]);
|
|
44
|
+
expect(invalidated.has("bucket")).toBe(true);
|
|
45
|
+
expect(invalidated.has("role")).toBe(true);
|
|
46
|
+
expect(invalidated.has("table")).toBe(false);
|
|
47
|
+
|
|
48
|
+
// a.ts is now unknown
|
|
49
|
+
expect(cache.hasFileChanged("/a.ts")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("invalidate with unknown file returns empty set", () => {
|
|
53
|
+
const cache = new DiscoveryCache();
|
|
54
|
+
const invalidated = cache.invalidate(["/unknown.ts"]);
|
|
55
|
+
expect(invalidated.size).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("trackedFiles returns all tracked file paths", () => {
|
|
59
|
+
const cache = new DiscoveryCache();
|
|
60
|
+
cache.trackFileImport("/a.ts", 100, ["x"]);
|
|
61
|
+
cache.trackFileImport("/b.ts", 200, ["y"]);
|
|
62
|
+
expect(cache.trackedFiles().sort()).toEqual(["/a.ts", "/b.ts"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("getFileEntities returns entity names for tracked file", () => {
|
|
66
|
+
const cache = new DiscoveryCache();
|
|
67
|
+
cache.trackFileImport("/a.ts", 100, ["bucket", "role"]);
|
|
68
|
+
expect(cache.getFileEntities("/a.ts")).toEqual(["bucket", "role"]);
|
|
69
|
+
expect(cache.getFileEntities("/unknown.ts")).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("clear removes all entries", () => {
|
|
73
|
+
const cache = new DiscoveryCache();
|
|
74
|
+
cache.trackFileImport("/a.ts", 100, ["x"]);
|
|
75
|
+
cache.clear();
|
|
76
|
+
expect(cache.trackedFiles()).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { statSync } from "fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache entry for a single file
|
|
5
|
+
*/
|
|
6
|
+
interface FileCacheEntry {
|
|
7
|
+
/** Last modified time in milliseconds */
|
|
8
|
+
mtime: number;
|
|
9
|
+
/** Entity names exported by this file */
|
|
10
|
+
entityNames: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Discovery cache for incremental rebuilds.
|
|
15
|
+
*
|
|
16
|
+
* Tracks file mtimes and file→entity mappings so that only changed files
|
|
17
|
+
* need to be re-imported during watch-mode rebuilds.
|
|
18
|
+
*/
|
|
19
|
+
export class DiscoveryCache {
|
|
20
|
+
private files = new Map<string, FileCacheEntry>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a file has changed since it was last tracked.
|
|
24
|
+
* Returns true if the file is new or its mtime has changed.
|
|
25
|
+
*/
|
|
26
|
+
hasFileChanged(filePath: string): boolean {
|
|
27
|
+
const entry = this.files.get(filePath);
|
|
28
|
+
if (!entry) return true;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const stat = statSync(filePath);
|
|
32
|
+
return stat.mtimeMs !== entry.mtime;
|
|
33
|
+
} catch {
|
|
34
|
+
// File may have been deleted
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Record a successful import, storing the file's mtime and entity names.
|
|
41
|
+
*/
|
|
42
|
+
trackFileImport(filePath: string, mtime: number, entityNames: string[]): void {
|
|
43
|
+
this.files.set(filePath, { mtime, entityNames });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Invalidate cache entries for the given files.
|
|
48
|
+
* Returns the set of entity names that were invalidated.
|
|
49
|
+
*/
|
|
50
|
+
invalidate(changedFiles: string[]): Set<string> {
|
|
51
|
+
const invalidatedEntities = new Set<string>();
|
|
52
|
+
|
|
53
|
+
for (const file of changedFiles) {
|
|
54
|
+
const entry = this.files.get(file);
|
|
55
|
+
if (entry) {
|
|
56
|
+
for (const name of entry.entityNames) {
|
|
57
|
+
invalidatedEntities.add(name);
|
|
58
|
+
}
|
|
59
|
+
this.files.delete(file);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return invalidatedEntities;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get all tracked file paths.
|
|
68
|
+
*/
|
|
69
|
+
trackedFiles(): string[] {
|
|
70
|
+
return [...this.files.keys()];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get entity names for a tracked file.
|
|
75
|
+
*/
|
|
76
|
+
getFileEntities(filePath: string): string[] | undefined {
|
|
77
|
+
return this.files.get(filePath)?.entityNames;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Clear all cached data.
|
|
82
|
+
*/
|
|
83
|
+
clear(): void {
|
|
84
|
+
this.files.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { collectEntities } from "./collect";
|
|
3
|
+
import { DECLARABLE_MARKER } from "../declarable";
|
|
4
|
+
import { DiscoveryError } from "../errors";
|
|
5
|
+
import { Composite, CompositeRegistry } from "../composite";
|
|
6
|
+
import { createMockEntity, expectToThrow } from "@intentius/chant-test-utils";
|
|
7
|
+
|
|
8
|
+
describe("collectEntities", () => {
|
|
9
|
+
test("collects declarable entities from single module", () => {
|
|
10
|
+
const entity1 = createMockEntity("test");
|
|
11
|
+
|
|
12
|
+
const modules = [
|
|
13
|
+
{
|
|
14
|
+
file: "test.ts",
|
|
15
|
+
exports: {
|
|
16
|
+
entity1,
|
|
17
|
+
notDeclarable: "value",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const result = collectEntities(modules);
|
|
23
|
+
expect(result.size).toBe(1);
|
|
24
|
+
expect(result.get("entity1")).toBe(entity1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("collects declarable entities from multiple modules", () => {
|
|
28
|
+
const entity1 = createMockEntity("type1");
|
|
29
|
+
const entity2 = createMockEntity("type2");
|
|
30
|
+
const entity3 = createMockEntity("type3");
|
|
31
|
+
|
|
32
|
+
const modules = [
|
|
33
|
+
{
|
|
34
|
+
file: "file1.ts",
|
|
35
|
+
exports: {
|
|
36
|
+
entity1,
|
|
37
|
+
helper: "function",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
file: "file2.ts",
|
|
42
|
+
exports: {
|
|
43
|
+
entity2,
|
|
44
|
+
entity3,
|
|
45
|
+
util: 42,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const result = collectEntities(modules);
|
|
51
|
+
expect(result.size).toBe(3);
|
|
52
|
+
expect(result.get("entity1")).toBe(entity1);
|
|
53
|
+
expect(result.get("entity2")).toBe(entity2);
|
|
54
|
+
expect(result.get("entity3")).toBe(entity3);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("ignores non-declarable exports", () => {
|
|
58
|
+
const entity = createMockEntity("test");
|
|
59
|
+
|
|
60
|
+
const modules = [
|
|
61
|
+
{
|
|
62
|
+
file: "mixed.ts",
|
|
63
|
+
exports: {
|
|
64
|
+
entity,
|
|
65
|
+
string: "value",
|
|
66
|
+
number: 123,
|
|
67
|
+
object: { key: "value" },
|
|
68
|
+
array: [1, 2, 3],
|
|
69
|
+
func: () => "test",
|
|
70
|
+
nullValue: null,
|
|
71
|
+
undefinedValue: undefined,
|
|
72
|
+
boolValue: true,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const result = collectEntities(modules);
|
|
78
|
+
expect(result.size).toBe(1);
|
|
79
|
+
expect(result.get("entity")).toBe(entity);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("returns empty map when no declarables found", () => {
|
|
83
|
+
const modules = [
|
|
84
|
+
{
|
|
85
|
+
file: "empty.ts",
|
|
86
|
+
exports: {
|
|
87
|
+
value1: "string",
|
|
88
|
+
value2: 42,
|
|
89
|
+
value3: { data: "object" },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const result = collectEntities(modules);
|
|
95
|
+
expect(result.size).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("returns empty map when modules array is empty", () => {
|
|
99
|
+
const result = collectEntities([]);
|
|
100
|
+
expect(result.size).toBe(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("throws DiscoveryError with type 'resolution' on duplicate export names", async () => {
|
|
104
|
+
const entity1 = createMockEntity("type1");
|
|
105
|
+
const entity2 = createMockEntity("type2");
|
|
106
|
+
|
|
107
|
+
const modules = [
|
|
108
|
+
{
|
|
109
|
+
file: "file1.ts",
|
|
110
|
+
exports: { myEntity: entity1 },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
file: "file2.ts",
|
|
114
|
+
exports: { myEntity: entity2 },
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
await expectToThrow(
|
|
119
|
+
() => collectEntities(modules),
|
|
120
|
+
DiscoveryError,
|
|
121
|
+
(error) => {
|
|
122
|
+
expect(error.type).toBe("resolution");
|
|
123
|
+
expect(error.file).toBe("file2.ts");
|
|
124
|
+
expect(error.message).toContain("Duplicate");
|
|
125
|
+
expect(error.message).toContain("myEntity");
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("throws DiscoveryError with detailed message for duplicates", async () => {
|
|
131
|
+
const entity1 = createMockEntity("test");
|
|
132
|
+
const entity2 = createMockEntity("test");
|
|
133
|
+
|
|
134
|
+
const modules = [
|
|
135
|
+
{
|
|
136
|
+
file: "a.ts",
|
|
137
|
+
exports: { duplicateName: entity1 },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
file: "b.ts",
|
|
141
|
+
exports: { duplicateName: entity2 },
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
await expectToThrow(
|
|
146
|
+
() => collectEntities(modules),
|
|
147
|
+
DiscoveryError,
|
|
148
|
+
(error) => {
|
|
149
|
+
expect(error.type).toBe("resolution");
|
|
150
|
+
expect(error.message).toBe('Duplicate export name "duplicateName" found');
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("allows same export name if not declarable in one module", () => {
|
|
156
|
+
const entity = createMockEntity("test");
|
|
157
|
+
|
|
158
|
+
const modules = [
|
|
159
|
+
{
|
|
160
|
+
file: "file1.ts",
|
|
161
|
+
exports: { name: "not declarable" },
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
file: "file2.ts",
|
|
165
|
+
exports: { name: entity },
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
const result = collectEntities(modules);
|
|
170
|
+
expect(result.size).toBe(1);
|
|
171
|
+
expect(result.get("name")).toBe(entity);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("preserves entity type information", () => {
|
|
175
|
+
const entity1 = createMockEntity("parameter");
|
|
176
|
+
const entity2 = createMockEntity("output");
|
|
177
|
+
|
|
178
|
+
const modules = [
|
|
179
|
+
{
|
|
180
|
+
file: "test.ts",
|
|
181
|
+
exports: { entity1, entity2 },
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const result = collectEntities(modules);
|
|
186
|
+
expect(result.get("entity1")?.entityType).toBe("parameter");
|
|
187
|
+
expect(result.get("entity2")?.entityType).toBe("output");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("correctly identifies objects without DECLARABLE_MARKER as non-declarable", () => {
|
|
191
|
+
const fakeDeclarable = {
|
|
192
|
+
entityType: "fake",
|
|
193
|
+
// Missing DECLARABLE_MARKER
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const modules = [
|
|
197
|
+
{
|
|
198
|
+
file: "test.ts",
|
|
199
|
+
exports: { fakeDeclarable },
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const result = collectEntities(modules);
|
|
204
|
+
expect(result.size).toBe(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("correctly identifies objects with wrong marker value as non-declarable", () => {
|
|
208
|
+
const fakeDeclarable = {
|
|
209
|
+
entityType: "fake",
|
|
210
|
+
[DECLARABLE_MARKER]: false, // Wrong value
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const modules = [
|
|
214
|
+
{
|
|
215
|
+
file: "test.ts",
|
|
216
|
+
exports: { fakeDeclarable },
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
const result = collectEntities(modules);
|
|
221
|
+
expect(result.size).toBe(0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("error serializes to JSON correctly", async () => {
|
|
225
|
+
const modules = [
|
|
226
|
+
{
|
|
227
|
+
file: "first.ts",
|
|
228
|
+
exports: { dup: createMockEntity("test") },
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
file: "second.ts",
|
|
232
|
+
exports: { dup: createMockEntity("test") },
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
const error = await expectToThrow(
|
|
237
|
+
() => collectEntities(modules),
|
|
238
|
+
DiscoveryError
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const json = error.toJSON();
|
|
242
|
+
expect(json.name).toBe("DiscoveryError");
|
|
243
|
+
expect(json.file).toBe("second.ts");
|
|
244
|
+
expect(json.type).toBe("resolution");
|
|
245
|
+
expect(json.message).toBeDefined();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("collectEntities with composites", () => {
|
|
250
|
+
beforeEach(() => {
|
|
251
|
+
CompositeRegistry.clear();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("composite is expanded into individual entities", () => {
|
|
255
|
+
const Comp = Composite(() => ({
|
|
256
|
+
a: createMockEntity("TestA"),
|
|
257
|
+
b: createMockEntity("TestB"),
|
|
258
|
+
}));
|
|
259
|
+
const instance = Comp({});
|
|
260
|
+
|
|
261
|
+
const entities = collectEntities([
|
|
262
|
+
{ file: "test.ts", exports: { myComp: instance } },
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
expect(entities.has("myComp_a")).toBe(true);
|
|
266
|
+
expect(entities.has("myComp_b")).toBe(true);
|
|
267
|
+
expect(entities.has("myComp")).toBe(false);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { isDeclarable, type Declarable } from "../declarable";
|
|
2
|
+
import { isCompositeInstance, expandComposite } from "../composite";
|
|
3
|
+
import { DiscoveryError } from "../errors";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Collects all declarable entities from imported modules.
|
|
7
|
+
* CompositeInstance exports are expanded into individual entities
|
|
8
|
+
* with `{exportName}_{memberName}` naming.
|
|
9
|
+
*
|
|
10
|
+
* @param modules - Array of module records with their exports
|
|
11
|
+
* @returns Map of export name to Declarable entity
|
|
12
|
+
* @throws {DiscoveryError} with type "resolution" if duplicate export names are found
|
|
13
|
+
*/
|
|
14
|
+
export function collectEntities(
|
|
15
|
+
modules: Array<{ file: string; exports: Record<string, unknown> }>
|
|
16
|
+
): Map<string, Declarable> {
|
|
17
|
+
const entities = new Map<string, Declarable>();
|
|
18
|
+
|
|
19
|
+
for (const { file, exports } of modules) {
|
|
20
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
21
|
+
if (isDeclarable(value)) {
|
|
22
|
+
if (entities.has(name)) {
|
|
23
|
+
// Same object re-exported from multiple files (e.g. barrel re-exports) is fine
|
|
24
|
+
if (entities.get(name) !== value) {
|
|
25
|
+
throw new DiscoveryError(
|
|
26
|
+
file,
|
|
27
|
+
`Duplicate export name "${name}" found`,
|
|
28
|
+
"resolution"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
entities.set(name, value);
|
|
33
|
+
}
|
|
34
|
+
} else if (isCompositeInstance(value)) {
|
|
35
|
+
const expanded = expandComposite(name, value);
|
|
36
|
+
for (const [expandedName, entity] of expanded) {
|
|
37
|
+
if (entities.has(expandedName)) {
|
|
38
|
+
throw new DiscoveryError(
|
|
39
|
+
file,
|
|
40
|
+
`Duplicate entity name "${expandedName}" from composite expansion of "${name}"`,
|
|
41
|
+
"resolution",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
entities.set(expandedName, entity);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return entities;
|
|
51
|
+
}
|