@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,157 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { barrel } from "./barrel";
|
|
6
|
+
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = mkdtempSync(join(tmpdir(), "barrel-test-"));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("barrel", () => {
|
|
18
|
+
test("returns exports from sibling .ts files", () => {
|
|
19
|
+
writeFileSync(
|
|
20
|
+
join(tempDir, "alpha.ts"),
|
|
21
|
+
"export const alphaValue = 42;",
|
|
22
|
+
);
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(tempDir, "beta.ts"),
|
|
25
|
+
"export const betaValue = 'hello';",
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const $ = barrel(tempDir);
|
|
29
|
+
|
|
30
|
+
expect($.alphaValue).toBe(42);
|
|
31
|
+
expect($.betaValue).toBe("hello");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("excludes files starting with underscore", () => {
|
|
35
|
+
writeFileSync(
|
|
36
|
+
join(tempDir, "_config.ts"),
|
|
37
|
+
"export const secret = 'hidden';",
|
|
38
|
+
);
|
|
39
|
+
writeFileSync(
|
|
40
|
+
join(tempDir, "visible.ts"),
|
|
41
|
+
"export const visible = true;",
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const $ = barrel(tempDir);
|
|
45
|
+
|
|
46
|
+
expect($.secret).toBeUndefined();
|
|
47
|
+
expect($.visible).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("excludes test files", () => {
|
|
51
|
+
writeFileSync(
|
|
52
|
+
join(tempDir, "foo.test.ts"),
|
|
53
|
+
"export const testVal = 1;",
|
|
54
|
+
);
|
|
55
|
+
writeFileSync(
|
|
56
|
+
join(tempDir, "bar.spec.ts"),
|
|
57
|
+
"export const specVal = 2;",
|
|
58
|
+
);
|
|
59
|
+
writeFileSync(
|
|
60
|
+
join(tempDir, "real.ts"),
|
|
61
|
+
"export const realVal = 3;",
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const $ = barrel(tempDir);
|
|
65
|
+
|
|
66
|
+
expect($.testVal).toBeUndefined();
|
|
67
|
+
expect($.specVal).toBeUndefined();
|
|
68
|
+
expect($.realVal).toBe(3);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("caches results after first access", () => {
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(tempDir, "mod.ts"),
|
|
74
|
+
"export const x = 1; export const y = 2;",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const $ = barrel(tempDir);
|
|
78
|
+
|
|
79
|
+
// First access triggers load
|
|
80
|
+
expect($.x).toBe(1);
|
|
81
|
+
|
|
82
|
+
// Write a new file after first load
|
|
83
|
+
writeFileSync(
|
|
84
|
+
join(tempDir, "late.ts"),
|
|
85
|
+
"export const lateVal = 99;",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Should NOT pick up new file since cache is already populated
|
|
89
|
+
expect($.lateVal).toBeUndefined();
|
|
90
|
+
// Existing values still work
|
|
91
|
+
expect($.y).toBe(2);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns undefined for missing keys", () => {
|
|
95
|
+
writeFileSync(
|
|
96
|
+
join(tempDir, "mod.ts"),
|
|
97
|
+
"export const exists = true;",
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const $ = barrel(tempDir);
|
|
101
|
+
|
|
102
|
+
expect($.nonExistent).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("has and ownKeys work correctly", () => {
|
|
106
|
+
writeFileSync(
|
|
107
|
+
join(tempDir, "mod.ts"),
|
|
108
|
+
"export const dataBucket = 's3://bucket';",
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const $ = barrel(tempDir);
|
|
112
|
+
|
|
113
|
+
expect("dataBucket" in $).toBe(true);
|
|
114
|
+
expect("missing" in $).toBe(false);
|
|
115
|
+
expect(Object.keys($)).toEqual(["dataBucket"]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("handles empty directory", () => {
|
|
119
|
+
const $ = barrel(tempDir);
|
|
120
|
+
|
|
121
|
+
expect(Object.keys($)).toEqual([]);
|
|
122
|
+
expect($.anything).toBeUndefined();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("handles load errors gracefully", () => {
|
|
126
|
+
writeFileSync(
|
|
127
|
+
join(tempDir, "broken.ts"),
|
|
128
|
+
"export const x = ;; SYNTAX ERROR @@#$",
|
|
129
|
+
);
|
|
130
|
+
writeFileSync(
|
|
131
|
+
join(tempDir, "good.ts"),
|
|
132
|
+
"export const goodVal = 'works';",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const $ = barrel(tempDir);
|
|
136
|
+
|
|
137
|
+
expect($.goodVal).toBe("works");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("first export wins on name collision", () => {
|
|
141
|
+
// Files are sorted by readdirSync (OS-dependent), but we can verify
|
|
142
|
+
// that the proxy doesn't crash on collisions
|
|
143
|
+
writeFileSync(
|
|
144
|
+
join(tempDir, "aaa.ts"),
|
|
145
|
+
"export const shared = 'first';",
|
|
146
|
+
);
|
|
147
|
+
writeFileSync(
|
|
148
|
+
join(tempDir, "zzz.ts"),
|
|
149
|
+
"export const shared = 'second';",
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const $ = barrel(tempDir);
|
|
153
|
+
|
|
154
|
+
// One of them wins (first by readdir order)
|
|
155
|
+
expect(typeof $.shared).toBe("string");
|
|
156
|
+
});
|
|
157
|
+
});
|
package/src/barrel.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
export function barrel(dir: string): Record<string, unknown> {
|
|
5
|
+
let allExports: Record<string, unknown> | null = null;
|
|
6
|
+
|
|
7
|
+
function load(): Record<string, unknown> {
|
|
8
|
+
if (allExports) return allExports;
|
|
9
|
+
allExports = {};
|
|
10
|
+
|
|
11
|
+
const files = readdirSync(dir)
|
|
12
|
+
.filter(
|
|
13
|
+
(f) =>
|
|
14
|
+
f.endsWith(".ts") &&
|
|
15
|
+
!f.startsWith("_") &&
|
|
16
|
+
!f.endsWith(".test.ts") &&
|
|
17
|
+
!f.endsWith(".spec.ts"),
|
|
18
|
+
)
|
|
19
|
+
.sort();
|
|
20
|
+
|
|
21
|
+
// Identify files that reference the barrel (.$. or .$[) — these
|
|
22
|
+
// may silently resolve cross-references to undefined if their
|
|
23
|
+
// dependency files haven't loaded yet
|
|
24
|
+
const barrelRefPattern = /\.\$[.\[]/;
|
|
25
|
+
const usesBarrel = new Set<string>();
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const src = readFileSync(join(dir, file), "utf-8");
|
|
28
|
+
if (barrelRefPattern.test(src)) {
|
|
29
|
+
usesBarrel.add(file);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadFile(file: string, overwrite = false): boolean {
|
|
34
|
+
const fullPath = join(dir, file);
|
|
35
|
+
try {
|
|
36
|
+
const mod = require(fullPath);
|
|
37
|
+
for (const [key, val] of Object.entries(mod)) {
|
|
38
|
+
if (val !== undefined && (overwrite || !(key in allExports!))) {
|
|
39
|
+
allExports![key] = val;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
// Clear require cache so retry re-executes the file
|
|
45
|
+
delete require.cache[require.resolve(fullPath)];
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// First pass — load all files in alphabetical order
|
|
51
|
+
const failed: string[] = [];
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
if (!loadFile(file)) {
|
|
54
|
+
failed.push(file);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Retry files that threw — their dependencies are now available
|
|
59
|
+
for (const file of failed) {
|
|
60
|
+
loadFile(file);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Second pass — reload files that reference the barrel so
|
|
64
|
+
// cross-references that silently resolved to undefined now
|
|
65
|
+
// pick up the correct values. Files without barrel references
|
|
66
|
+
// keep their original instances to preserve the reference graph.
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
if (!usesBarrel.has(file)) continue;
|
|
69
|
+
const fullPath = join(dir, file);
|
|
70
|
+
try { delete require.cache[require.resolve(fullPath)]; } catch {}
|
|
71
|
+
}
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
if (!usesBarrel.has(file)) continue;
|
|
74
|
+
loadFile(file, true);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return allExports;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new Proxy<Record<string, unknown>>({}, {
|
|
81
|
+
get(_, prop: string | symbol) {
|
|
82
|
+
if (typeof prop === 'symbol') return undefined;
|
|
83
|
+
return load()[prop];
|
|
84
|
+
},
|
|
85
|
+
has(_, prop: string | symbol) {
|
|
86
|
+
if (typeof prop === 'symbol') return false;
|
|
87
|
+
return prop in load();
|
|
88
|
+
},
|
|
89
|
+
ownKeys(_) {
|
|
90
|
+
return Object.keys(load());
|
|
91
|
+
},
|
|
92
|
+
getOwnPropertyDescriptor(_, prop: string | symbol) {
|
|
93
|
+
if (typeof prop === 'symbol') return undefined;
|
|
94
|
+
const exports = load();
|
|
95
|
+
if (prop in exports) {
|
|
96
|
+
return { configurable: true, enumerable: true, value: exports[prop] };
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { withTestDir } from "@intentius/chant-test-utils";
|
|
3
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { discover } from "./discovery/index";
|
|
6
|
+
import { runLint } from "./lint/engine";
|
|
7
|
+
import { build } from "./build";
|
|
8
|
+
import type { Serializer } from "./serializer";
|
|
9
|
+
import type { LintRule } from "./lint/rule";
|
|
10
|
+
|
|
11
|
+
// Import all core lint rules
|
|
12
|
+
import {
|
|
13
|
+
flatDeclarationsRule,
|
|
14
|
+
exportRequiredRule,
|
|
15
|
+
fileDeclarableLimitRule,
|
|
16
|
+
singleConcernFileRule,
|
|
17
|
+
preferNamespaceImportRule,
|
|
18
|
+
barrelImportStyleRule,
|
|
19
|
+
declarableNamingConventionRule,
|
|
20
|
+
noUnusedDeclarableImportRule,
|
|
21
|
+
noRedundantValueCastRule,
|
|
22
|
+
noUnusedDeclarableRule,
|
|
23
|
+
noCyclicDeclarableRefRule,
|
|
24
|
+
noRedundantTypeImportRule,
|
|
25
|
+
noStringRefRule,
|
|
26
|
+
enforceBarrelImportRule,
|
|
27
|
+
enforceBarrelRefRule,
|
|
28
|
+
evl001NonLiteralExpressionRule,
|
|
29
|
+
evl002ControlFlowResourceRule,
|
|
30
|
+
evl003DynamicPropertyAccessRule,
|
|
31
|
+
evl004SpreadNonConstRule,
|
|
32
|
+
evl005ResourceBlockBodyRule,
|
|
33
|
+
evl006BarrelUsageRule,
|
|
34
|
+
evl007InvalidSiblingsRule,
|
|
35
|
+
evl008UnresolvableBarrelRefRule,
|
|
36
|
+
} from "./lint/rules/index";
|
|
37
|
+
|
|
38
|
+
const coreRules: LintRule[] = [
|
|
39
|
+
flatDeclarationsRule,
|
|
40
|
+
exportRequiredRule,
|
|
41
|
+
fileDeclarableLimitRule,
|
|
42
|
+
singleConcernFileRule,
|
|
43
|
+
preferNamespaceImportRule,
|
|
44
|
+
barrelImportStyleRule,
|
|
45
|
+
declarableNamingConventionRule,
|
|
46
|
+
noUnusedDeclarableImportRule,
|
|
47
|
+
noRedundantValueCastRule,
|
|
48
|
+
noUnusedDeclarableRule,
|
|
49
|
+
noCyclicDeclarableRefRule,
|
|
50
|
+
noRedundantTypeImportRule,
|
|
51
|
+
noStringRefRule,
|
|
52
|
+
enforceBarrelImportRule,
|
|
53
|
+
enforceBarrelRefRule,
|
|
54
|
+
evl001NonLiteralExpressionRule,
|
|
55
|
+
evl002ControlFlowResourceRule,
|
|
56
|
+
evl003DynamicPropertyAccessRule,
|
|
57
|
+
evl004SpreadNonConstRule,
|
|
58
|
+
evl005ResourceBlockBodyRule,
|
|
59
|
+
evl006BarrelUsageRule,
|
|
60
|
+
evl007InvalidSiblingsRule,
|
|
61
|
+
evl008UnresolvableBarrelRefRule,
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
interface FixtureSize {
|
|
65
|
+
name: string;
|
|
66
|
+
files: number;
|
|
67
|
+
entitiesPerFile: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const sizes: FixtureSize[] = [
|
|
71
|
+
{ name: "Small", files: 4, entitiesPerFile: 2 },
|
|
72
|
+
{ name: "Medium", files: 20, entitiesPerFile: 2 },
|
|
73
|
+
{ name: "Large", files: 100, entitiesPerFile: 2 },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function generateFixtureFile(index: number, entitiesPerFile: number): string {
|
|
77
|
+
const lines: string[] = [];
|
|
78
|
+
for (let i = 0; i < entitiesPerFile; i++) {
|
|
79
|
+
const n = index * entitiesPerFile + i;
|
|
80
|
+
lines.push(`export const config_${n} = {
|
|
81
|
+
lexicon: "bench",
|
|
82
|
+
entityType: "Config",
|
|
83
|
+
kind: "property",
|
|
84
|
+
[Symbol.for("chant.declarable")]: true,
|
|
85
|
+
algorithm: "AES256",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const bucket_${n} = {
|
|
89
|
+
lexicon: "bench",
|
|
90
|
+
entityType: "Bucket",
|
|
91
|
+
[Symbol.for("chant.declarable")]: true,
|
|
92
|
+
bucketName: "bucket-${n}",
|
|
93
|
+
encryption: config_${n},
|
|
94
|
+
};`);
|
|
95
|
+
}
|
|
96
|
+
return lines.join("\n\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function generateFixture(dir: string, size: FixtureSize): Promise<string[]> {
|
|
100
|
+
const files: string[] = [];
|
|
101
|
+
for (let i = 0; i < size.files; i++) {
|
|
102
|
+
const filePath = join(dir, `infra-${String(i).padStart(3, "0")}.ts`);
|
|
103
|
+
await writeFile(filePath, generateFixtureFile(i, size.entitiesPerFile));
|
|
104
|
+
files.push(filePath);
|
|
105
|
+
}
|
|
106
|
+
return files;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function timeMs<T>(fn: () => Promise<T>): Promise<{ result: T; ms: number }> {
|
|
110
|
+
const start = performance.now();
|
|
111
|
+
const result = await fn();
|
|
112
|
+
return { result, ms: performance.now() - start };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function benchmark<T>(fn: () => Promise<T>, runs: number = 5): Promise<{ min: number; avg: number; max: number; result: T }> {
|
|
116
|
+
const times: number[] = [];
|
|
117
|
+
let lastResult!: T;
|
|
118
|
+
for (let i = 0; i < runs; i++) {
|
|
119
|
+
const { result, ms } = await timeMs(fn);
|
|
120
|
+
times.push(ms);
|
|
121
|
+
lastResult = result;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
min: Math.min(...times),
|
|
125
|
+
avg: times.reduce((a, b) => a + b, 0) / times.length,
|
|
126
|
+
max: Math.max(...times),
|
|
127
|
+
result: lastResult,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function fmt(ms: number): string {
|
|
132
|
+
return `${ms.toFixed(1)}ms`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function pad(s: string, n: number): string {
|
|
136
|
+
return s.padEnd(n);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const benchSerializer: Serializer = {
|
|
140
|
+
name: "bench",
|
|
141
|
+
rulePrefix: "BENCH",
|
|
142
|
+
serialize: (entities) => JSON.stringify({ count: entities.size }),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
describe("performance benchmarks", () => {
|
|
146
|
+
test("benchmark suite", async () => {
|
|
147
|
+
const output: string[] = [];
|
|
148
|
+
output.push("");
|
|
149
|
+
output.push("chant performance benchmarks");
|
|
150
|
+
output.push("============================");
|
|
151
|
+
output.push("");
|
|
152
|
+
|
|
153
|
+
// --- Fixture generation ---
|
|
154
|
+
output.push("Fixture generation:");
|
|
155
|
+
|
|
156
|
+
for (const size of sizes) {
|
|
157
|
+
await withTestDir(async (dir) => {
|
|
158
|
+
const totalEntities = size.files * size.entitiesPerFile;
|
|
159
|
+
const { ms } = await timeMs(() => generateFixture(dir, size));
|
|
160
|
+
output.push(` ${pad(`${size.name} (${size.files} files, ~${totalEntities} entities)`, 42)} ${fmt(ms)}`);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
output.push("");
|
|
165
|
+
|
|
166
|
+
// --- Discovery ---
|
|
167
|
+
output.push("Discovery:");
|
|
168
|
+
|
|
169
|
+
for (const size of sizes) {
|
|
170
|
+
await withTestDir(async (dir) => {
|
|
171
|
+
await generateFixture(dir, size);
|
|
172
|
+
const { avg } = await benchmark(() => discover(dir), 3);
|
|
173
|
+
output.push(` ${pad(size.name, 10)} ${fmt(avg)} (avg of 3 runs)`);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
output.push("");
|
|
178
|
+
|
|
179
|
+
// --- Lint ---
|
|
180
|
+
output.push(`Lint (${coreRules.length} core rules):`);
|
|
181
|
+
|
|
182
|
+
for (const size of sizes) {
|
|
183
|
+
await withTestDir(async (dir) => {
|
|
184
|
+
const files = await generateFixture(dir, size);
|
|
185
|
+
const { avg } = await benchmark(() => runLint(files, coreRules), 3);
|
|
186
|
+
output.push(` ${pad(size.name, 10)} ${fmt(avg)} (avg of 3 runs)`);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
output.push("");
|
|
191
|
+
|
|
192
|
+
// --- Build ---
|
|
193
|
+
output.push("Build (full pipeline):");
|
|
194
|
+
|
|
195
|
+
for (const size of sizes) {
|
|
196
|
+
await withTestDir(async (dir) => {
|
|
197
|
+
await generateFixture(dir, size);
|
|
198
|
+
const { avg } = await benchmark(() => build(dir, [benchSerializer]), 3);
|
|
199
|
+
output.push(` ${pad(size.name, 10)} ${fmt(avg)} (avg of 3 runs)`);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
output.push("");
|
|
204
|
+
|
|
205
|
+
// --- Startup ---
|
|
206
|
+
output.push("Startup:");
|
|
207
|
+
|
|
208
|
+
{
|
|
209
|
+
const { ms } = await timeMs(async () => {
|
|
210
|
+
await import("@intentius/chant");
|
|
211
|
+
});
|
|
212
|
+
output.push(` import("@intentius/chant") ${fmt(ms)}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
output.push("");
|
|
216
|
+
|
|
217
|
+
// Print results
|
|
218
|
+
const report = output.join("\n");
|
|
219
|
+
console.log(report);
|
|
220
|
+
|
|
221
|
+
// Sanity assertions — just verify the benchmarks actually ran
|
|
222
|
+
expect(report).toContain("Discovery:");
|
|
223
|
+
expect(report).toContain("Lint");
|
|
224
|
+
expect(report).toContain("Build");
|
|
225
|
+
expect(report).toContain("Startup:");
|
|
226
|
+
}, 120_000);
|
|
227
|
+
});
|