@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,130 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { evl004SpreadNonConstRule } from "./evl004-spread-non-const";
|
|
4
|
+
import type { LintContext } from "../rule";
|
|
5
|
+
|
|
6
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
7
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
8
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("EVL004: spread-non-const", () => {
|
|
12
|
+
test("rule metadata", () => {
|
|
13
|
+
expect(evl004SpreadNonConstRule.id).toBe("EVL004");
|
|
14
|
+
expect(evl004SpreadNonConstRule.severity).toBe("error");
|
|
15
|
+
expect(evl004SpreadNonConstRule.category).toBe("correctness");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("allows spread from const variable", () => {
|
|
19
|
+
const ctx = createContext(`
|
|
20
|
+
const base = { a: 1, b: 2 };
|
|
21
|
+
const merged = { ...base, c: 3 };
|
|
22
|
+
`);
|
|
23
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("allows spread from object literal", () => {
|
|
27
|
+
const ctx = createContext(`const x = { ...{ a: 1 }, b: 2 };`);
|
|
28
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("allows spread from const property access", () => {
|
|
32
|
+
const ctx = createContext(`
|
|
33
|
+
const config = { base: { a: 1 } };
|
|
34
|
+
const x = { ...config.base };
|
|
35
|
+
`);
|
|
36
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("flags spread from let variable", () => {
|
|
40
|
+
const ctx = createContext(`
|
|
41
|
+
let base = { a: 1 };
|
|
42
|
+
const x = { ...base };
|
|
43
|
+
`);
|
|
44
|
+
const diags = evl004SpreadNonConstRule.check(ctx);
|
|
45
|
+
expect(diags).toHaveLength(1);
|
|
46
|
+
expect(diags[0].ruleId).toBe("EVL004");
|
|
47
|
+
expect(diags[0].message).toContain("non-const");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("flags spread from function call", () => {
|
|
51
|
+
const ctx = createContext(`const x = { ...getDefaults() };`);
|
|
52
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("flags spread from method call", () => {
|
|
56
|
+
const ctx = createContext(`const x = { ...config.getDefaults() };`);
|
|
57
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("flags array spread from non-const", () => {
|
|
61
|
+
const ctx = createContext(`
|
|
62
|
+
let items = [1, 2];
|
|
63
|
+
const x = [...items, 3];
|
|
64
|
+
`);
|
|
65
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("allows array spread from const", () => {
|
|
69
|
+
const ctx = createContext(`
|
|
70
|
+
const items = [1, 2];
|
|
71
|
+
const x = [...items, 3];
|
|
72
|
+
`);
|
|
73
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("flags multiple violations", () => {
|
|
77
|
+
const ctx = createContext(`
|
|
78
|
+
let a = {};
|
|
79
|
+
let b = [];
|
|
80
|
+
const x = { ...a };
|
|
81
|
+
const y = [...b];
|
|
82
|
+
`);
|
|
83
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("allows spread inside Composite() factory callback", () => {
|
|
87
|
+
const ctx = createContext(`
|
|
88
|
+
function SecureApi(props) {
|
|
89
|
+
return LambdaApi({
|
|
90
|
+
timeout: 10,
|
|
91
|
+
...props,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
`);
|
|
95
|
+
// This is a regular function, NOT inside Composite — should still flag
|
|
96
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("allows spread inside Composite() definition", () => {
|
|
100
|
+
const ctx = createContext(`
|
|
101
|
+
const MyComp = Composite((props) => {
|
|
102
|
+
const role = new Role({ ...props.roleConfig });
|
|
103
|
+
return { role };
|
|
104
|
+
});
|
|
105
|
+
`);
|
|
106
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("allows spread inside _.Composite() definition", () => {
|
|
110
|
+
const ctx = createContext(`
|
|
111
|
+
const MyComp = _.Composite((props) => {
|
|
112
|
+
const role = new _.Role({ ...props.defaults, name: props.name });
|
|
113
|
+
return { role };
|
|
114
|
+
});
|
|
115
|
+
`);
|
|
116
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("still flags outside Composite() in same file", () => {
|
|
120
|
+
const ctx = createContext(`
|
|
121
|
+
const MyComp = Composite((props) => {
|
|
122
|
+
const role = new Role({ ...props.config });
|
|
123
|
+
return { role };
|
|
124
|
+
});
|
|
125
|
+
let defaults = {};
|
|
126
|
+
const x = { ...defaults };
|
|
127
|
+
`);
|
|
128
|
+
expect(evl004SpreadNonConstRule.check(ctx)).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
|
+
import { isInsideCompositeFactory } from "./composite-scope";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EVL004: Spread From Non-Const Source
|
|
7
|
+
*
|
|
8
|
+
* Spread expressions ({...x} or [...x]) must reference a const-declared
|
|
9
|
+
* variable, object literal, or property access chain from a const.
|
|
10
|
+
* Spreading from function calls or non-traceable sources is blocked.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function isTraceableToConst(node: ts.Node, sourceFile: ts.SourceFile): boolean {
|
|
14
|
+
// Object or array literals are fine
|
|
15
|
+
if (ts.isObjectLiteralExpression(node) || ts.isArrayLiteralExpression(node)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Property access: follow the chain to the root
|
|
20
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
21
|
+
return isTraceableToConst(node.expression, sourceFile);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Identifier — check if it's a const declaration
|
|
25
|
+
if (ts.isIdentifier(node)) {
|
|
26
|
+
return isConstIdentifier(node, sourceFile);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Parenthesized
|
|
30
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
31
|
+
return isTraceableToConst(node.expression, sourceFile);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// As/satisfies cast
|
|
35
|
+
if (ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) {
|
|
36
|
+
return isTraceableToConst(node.expression, sourceFile);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isConstIdentifier(id: ts.Identifier, sourceFile: ts.SourceFile): boolean {
|
|
43
|
+
// Walk all statements looking for a const declaration of this identifier
|
|
44
|
+
for (const stmt of sourceFile.statements) {
|
|
45
|
+
if (ts.isVariableStatement(stmt)) {
|
|
46
|
+
if (stmt.declarationList.flags & ts.NodeFlags.Const) {
|
|
47
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
48
|
+
if (ts.isIdentifier(decl.name) && decl.name.text === id.text) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
|
|
59
|
+
// Skip spreads inside Composite() factory callbacks
|
|
60
|
+
if (isInsideCompositeFactory(node)) {
|
|
61
|
+
ts.forEachChild(node, (child) => checkNode(child, context, diagnostics));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Spread in object literal: { ...expr }
|
|
66
|
+
if (ts.isSpreadAssignment(node)) {
|
|
67
|
+
if (!isTraceableToConst(node.expression, context.sourceFile)) {
|
|
68
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
69
|
+
node.getStart(context.sourceFile),
|
|
70
|
+
);
|
|
71
|
+
diagnostics.push({
|
|
72
|
+
file: context.filePath,
|
|
73
|
+
line: line + 1,
|
|
74
|
+
column: character + 1,
|
|
75
|
+
ruleId: "EVL004",
|
|
76
|
+
severity: "error",
|
|
77
|
+
message: "Spread from non-const source — spread expression must reference a const variable or literal",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Spread in array literal: [...expr]
|
|
83
|
+
if (ts.isSpreadElement(node)) {
|
|
84
|
+
if (!isTraceableToConst(node.expression, context.sourceFile)) {
|
|
85
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
86
|
+
node.getStart(context.sourceFile),
|
|
87
|
+
);
|
|
88
|
+
diagnostics.push({
|
|
89
|
+
file: context.filePath,
|
|
90
|
+
line: line + 1,
|
|
91
|
+
column: character + 1,
|
|
92
|
+
ruleId: "EVL004",
|
|
93
|
+
severity: "error",
|
|
94
|
+
message: "Spread from non-const source — spread expression must reference a const variable or literal",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ts.forEachChild(node, (child) => checkNode(child, context, diagnostics));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const evl004SpreadNonConstRule: LintRule = {
|
|
103
|
+
id: "EVL004",
|
|
104
|
+
severity: "error",
|
|
105
|
+
category: "correctness",
|
|
106
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
107
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
108
|
+
checkNode(context.sourceFile, context, diagnostics);
|
|
109
|
+
return diagnostics;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { evl005ResourceBlockBodyRule } from "./evl005-resource-block-body";
|
|
4
|
+
import type { LintContext } from "../rule";
|
|
5
|
+
|
|
6
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
7
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
8
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("EVL005: resource-block-body", () => {
|
|
12
|
+
test("rule metadata", () => {
|
|
13
|
+
expect(evl005ResourceBlockBodyRule.id).toBe("EVL005");
|
|
14
|
+
expect(evl005ResourceBlockBodyRule.severity).toBe("error");
|
|
15
|
+
expect(evl005ResourceBlockBodyRule.category).toBe("correctness");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("allows expression body", () => {
|
|
19
|
+
const ctx = createContext(`resource(Bucket, (props) => ({ name: "x" }));`);
|
|
20
|
+
expect(evl005ResourceBlockBodyRule.check(ctx)).toHaveLength(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("flags block body", () => {
|
|
24
|
+
const ctx = createContext(`resource(Bucket, (props) => { return { name: "x" }; });`);
|
|
25
|
+
const diags = evl005ResourceBlockBodyRule.check(ctx);
|
|
26
|
+
expect(diags).toHaveLength(1);
|
|
27
|
+
expect(diags[0].ruleId).toBe("EVL005");
|
|
28
|
+
expect(diags[0].message).toContain("Block body");
|
|
29
|
+
expect(diags[0].message).toContain("expression body");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("does not flag non-resource calls", () => {
|
|
33
|
+
const ctx = createContext(`someOther(Type, (props) => { return {}; });`);
|
|
34
|
+
expect(evl005ResourceBlockBodyRule.check(ctx)).toHaveLength(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("does not flag resource with no second argument", () => {
|
|
38
|
+
const ctx = createContext(`resource(Bucket);`);
|
|
39
|
+
expect(evl005ResourceBlockBodyRule.check(ctx)).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("does not flag resource with non-arrow second argument", () => {
|
|
43
|
+
const ctx = createContext(`resource(Bucket, factoryFn);`);
|
|
44
|
+
expect(evl005ResourceBlockBodyRule.check(ctx)).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("allows expression body with parenthesized object", () => {
|
|
48
|
+
const ctx = createContext(`resource(Bucket, (props, siblings) => ({ name: siblings.other.arn }));`);
|
|
49
|
+
expect(evl005ResourceBlockBodyRule.check(ctx)).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("flags multiple violations", () => {
|
|
53
|
+
const ctx = createContext(`
|
|
54
|
+
resource(Bucket, (p) => { return {}; });
|
|
55
|
+
resource(Role, (p) => { return {}; });
|
|
56
|
+
`);
|
|
57
|
+
expect(evl005ResourceBlockBodyRule.check(ctx)).toHaveLength(2);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EVL005: Block Body in resource() Callback
|
|
6
|
+
*
|
|
7
|
+
* The second argument to resource() must be an arrow function with an
|
|
8
|
+
* expression body: (props) => ({...}). Block bodies are not allowed
|
|
9
|
+
* because the evaluator expects an expression, not statements.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
|
|
13
|
+
if (
|
|
14
|
+
ts.isCallExpression(node) &&
|
|
15
|
+
ts.isIdentifier(node.expression) &&
|
|
16
|
+
node.expression.text === "resource"
|
|
17
|
+
) {
|
|
18
|
+
// resource(Type, (props) => ({ ... })) — second arg is the factory
|
|
19
|
+
const factory = node.arguments[1];
|
|
20
|
+
if (factory && ts.isArrowFunction(factory)) {
|
|
21
|
+
if (ts.isBlock(factory.body)) {
|
|
22
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
23
|
+
factory.body.getStart(context.sourceFile),
|
|
24
|
+
);
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
file: context.filePath,
|
|
27
|
+
line: line + 1,
|
|
28
|
+
column: character + 1,
|
|
29
|
+
ruleId: "EVL005",
|
|
30
|
+
severity: "error",
|
|
31
|
+
message: "Block body in resource() callback — use expression body: (props) => ({...})",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ts.forEachChild(node, (child) => checkNode(child, context, diagnostics));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const evl005ResourceBlockBodyRule: LintRule = {
|
|
41
|
+
id: "EVL005",
|
|
42
|
+
severity: "error",
|
|
43
|
+
category: "correctness",
|
|
44
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
45
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
46
|
+
checkNode(context.sourceFile, context, diagnostics);
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { evl006BarrelUsageRule } from "./evl006-barrel-usage";
|
|
4
|
+
import type { LintContext } from "../rule";
|
|
5
|
+
|
|
6
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
7
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
8
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("EVL006: barrel-usage", () => {
|
|
12
|
+
test("rule metadata", () => {
|
|
13
|
+
expect(evl006BarrelUsageRule.id).toBe("EVL006");
|
|
14
|
+
expect(evl006BarrelUsageRule.severity).toBe("error");
|
|
15
|
+
expect(evl006BarrelUsageRule.category).toBe("correctness");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("allows correct barrel usage", () => {
|
|
19
|
+
const ctx = createContext(`export const $ = barrel(import.meta.dir);`);
|
|
20
|
+
expect(evl006BarrelUsageRule.check(ctx)).toHaveLength(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("flags string literal argument", () => {
|
|
24
|
+
const ctx = createContext(`export const $ = barrel("./src");`);
|
|
25
|
+
const diags = evl006BarrelUsageRule.check(ctx);
|
|
26
|
+
expect(diags.some((d) => d.message.includes("import.meta.dir"))).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("flags variable argument", () => {
|
|
30
|
+
const ctx = createContext(`export const $ = barrel(myDir);`);
|
|
31
|
+
const diags = evl006BarrelUsageRule.check(ctx);
|
|
32
|
+
expect(diags.some((d) => d.message.includes("import.meta.dir"))).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("flags non-exported barrel", () => {
|
|
36
|
+
const ctx = createContext(`const $ = barrel(import.meta.dir);`);
|
|
37
|
+
const diags = evl006BarrelUsageRule.check(ctx);
|
|
38
|
+
expect(diags.some((d) => d.message.includes("export const $"))).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("flags barrel not assigned to $", () => {
|
|
42
|
+
const ctx = createContext(`export const myBarrel = barrel(import.meta.dir);`);
|
|
43
|
+
const diags = evl006BarrelUsageRule.check(ctx);
|
|
44
|
+
expect(diags.some((d) => d.message.includes("export const $"))).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("flags let instead of const", () => {
|
|
48
|
+
const ctx = createContext(`export let $ = barrel(import.meta.dir);`);
|
|
49
|
+
const diags = evl006BarrelUsageRule.check(ctx);
|
|
50
|
+
expect(diags.some((d) => d.message.includes("export const $"))).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("does not flag non-barrel calls", () => {
|
|
54
|
+
const ctx = createContext(`export const x = someFunction(import.meta.dir);`);
|
|
55
|
+
expect(evl006BarrelUsageRule.check(ctx)).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("flags no arguments", () => {
|
|
59
|
+
const ctx = createContext(`export const $ = barrel();`);
|
|
60
|
+
const diags = evl006BarrelUsageRule.check(ctx);
|
|
61
|
+
expect(diags.some((d) => d.message.includes("import.meta.dir"))).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EVL006: Incorrect barrel() Usage
|
|
6
|
+
*
|
|
7
|
+
* Two constraints:
|
|
8
|
+
* 1. The argument to barrel() must be import.meta.dir
|
|
9
|
+
* 2. The call must be part of: export const $ = barrel(import.meta.dir)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function isImportMetaDir(node: ts.Node): boolean {
|
|
13
|
+
// import.meta.dir is a PropertyAccessExpression: (import.meta).dir
|
|
14
|
+
if (
|
|
15
|
+
ts.isPropertyAccessExpression(node) &&
|
|
16
|
+
node.name.text === "dir" &&
|
|
17
|
+
ts.isMetaProperty(node.expression) &&
|
|
18
|
+
node.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
|
|
19
|
+
node.expression.name.text === "meta"
|
|
20
|
+
) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
|
|
27
|
+
if (
|
|
28
|
+
ts.isCallExpression(node) &&
|
|
29
|
+
ts.isIdentifier(node.expression) &&
|
|
30
|
+
node.expression.text === "barrel"
|
|
31
|
+
) {
|
|
32
|
+
// Check constraint 1: argument must be import.meta.dir
|
|
33
|
+
const arg = node.arguments[0];
|
|
34
|
+
if (!arg || !isImportMetaDir(arg)) {
|
|
35
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
36
|
+
node.getStart(context.sourceFile),
|
|
37
|
+
);
|
|
38
|
+
diagnostics.push({
|
|
39
|
+
file: context.filePath,
|
|
40
|
+
line: line + 1,
|
|
41
|
+
column: character + 1,
|
|
42
|
+
ruleId: "EVL006",
|
|
43
|
+
severity: "error",
|
|
44
|
+
message: "barrel() argument must be import.meta.dir",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check constraint 2: must be export const $ = barrel(...)
|
|
49
|
+
let isValidExport = false;
|
|
50
|
+
const parent = node.parent;
|
|
51
|
+
if (parent && ts.isVariableDeclaration(parent)) {
|
|
52
|
+
if (ts.isIdentifier(parent.name) && parent.name.text === "$") {
|
|
53
|
+
const declList = parent.parent;
|
|
54
|
+
if (declList && ts.isVariableDeclarationList(declList)) {
|
|
55
|
+
const stmt = declList.parent;
|
|
56
|
+
if (
|
|
57
|
+
stmt &&
|
|
58
|
+
ts.isVariableStatement(stmt) &&
|
|
59
|
+
stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
60
|
+
(declList.flags & ts.NodeFlags.Const) !== 0
|
|
61
|
+
) {
|
|
62
|
+
isValidExport = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!isValidExport) {
|
|
69
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
70
|
+
node.getStart(context.sourceFile),
|
|
71
|
+
);
|
|
72
|
+
diagnostics.push({
|
|
73
|
+
file: context.filePath,
|
|
74
|
+
line: line + 1,
|
|
75
|
+
column: character + 1,
|
|
76
|
+
ruleId: "EVL006",
|
|
77
|
+
severity: "error",
|
|
78
|
+
message: "barrel() must be used as: export const $ = barrel(import.meta.dir)",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ts.forEachChild(node, (child) => checkNode(child, context, diagnostics));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const evl006BarrelUsageRule: LintRule = {
|
|
87
|
+
id: "EVL006",
|
|
88
|
+
severity: "error",
|
|
89
|
+
category: "correctness",
|
|
90
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
91
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
92
|
+
checkNode(context.sourceFile, context, diagnostics);
|
|
93
|
+
return diagnostics;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { evl007InvalidSiblingsRule } from "./evl007-invalid-siblings";
|
|
4
|
+
import type { LintContext } from "../rule";
|
|
5
|
+
|
|
6
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
7
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
8
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("EVL007: invalid-siblings", () => {
|
|
12
|
+
test("rule metadata", () => {
|
|
13
|
+
expect(evl007InvalidSiblingsRule.id).toBe("EVL007");
|
|
14
|
+
expect(evl007InvalidSiblingsRule.severity).toBe("error");
|
|
15
|
+
expect(evl007InvalidSiblingsRule.category).toBe("correctness");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("allows valid sibling access", () => {
|
|
19
|
+
const ctx = createContext(`
|
|
20
|
+
Composite((props) => ({
|
|
21
|
+
bucket: resource(Bucket, () => ({ name: "x" })),
|
|
22
|
+
role: resource(Role, (p, siblings) => ({ bucketArn: siblings.bucket.arn })),
|
|
23
|
+
}));
|
|
24
|
+
`);
|
|
25
|
+
expect(evl007InvalidSiblingsRule.check(ctx)).toHaveLength(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("flags invalid sibling key", () => {
|
|
29
|
+
const ctx = createContext(`
|
|
30
|
+
Composite((props) => ({
|
|
31
|
+
bucket: resource(Bucket, () => ({ name: "x" })),
|
|
32
|
+
role: resource(Role, (p, siblings) => ({ bucketArn: siblings.nonExistent.arn })),
|
|
33
|
+
}));
|
|
34
|
+
`);
|
|
35
|
+
const diags = evl007InvalidSiblingsRule.check(ctx);
|
|
36
|
+
expect(diags).toHaveLength(1);
|
|
37
|
+
expect(diags[0].ruleId).toBe("EVL007");
|
|
38
|
+
expect(diags[0].message).toContain("nonExistent");
|
|
39
|
+
expect(diags[0].message).toContain("not a member");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("allows access to all defined members", () => {
|
|
43
|
+
const ctx = createContext(`
|
|
44
|
+
Composite((props) => ({
|
|
45
|
+
bucket: resource(Bucket, () => ({ name: "x" })),
|
|
46
|
+
table: resource(Table, () => ({ name: "y" })),
|
|
47
|
+
role: resource(Role, (p, siblings) => ({
|
|
48
|
+
bucketArn: siblings.bucket.arn,
|
|
49
|
+
tableArn: siblings.table.arn,
|
|
50
|
+
})),
|
|
51
|
+
}));
|
|
52
|
+
`);
|
|
53
|
+
expect(evl007InvalidSiblingsRule.check(ctx)).toHaveLength(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("does not flag when no siblings parameter", () => {
|
|
57
|
+
const ctx = createContext(`
|
|
58
|
+
Composite((props) => ({
|
|
59
|
+
bucket: resource(Bucket, (p) => ({ name: "x" })),
|
|
60
|
+
}));
|
|
61
|
+
`);
|
|
62
|
+
expect(evl007InvalidSiblingsRule.check(ctx)).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("does not flag non-Composite calls", () => {
|
|
66
|
+
const ctx = createContext(`
|
|
67
|
+
someFunction((props) => ({
|
|
68
|
+
bucket: resource(Bucket, (p, siblings) => ({ x: siblings.nonExistent })),
|
|
69
|
+
}));
|
|
70
|
+
`);
|
|
71
|
+
expect(evl007InvalidSiblingsRule.check(ctx)).toHaveLength(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("flags multiple invalid accesses", () => {
|
|
75
|
+
const ctx = createContext(`
|
|
76
|
+
Composite((props) => ({
|
|
77
|
+
bucket: resource(Bucket, () => ({ name: "x" })),
|
|
78
|
+
role: resource(Role, (p, siblings) => ({
|
|
79
|
+
a: siblings.missing1.arn,
|
|
80
|
+
b: siblings.missing2.arn,
|
|
81
|
+
})),
|
|
82
|
+
}));
|
|
83
|
+
`);
|
|
84
|
+
const diags = evl007InvalidSiblingsRule.check(ctx);
|
|
85
|
+
expect(diags).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
});
|