@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,139 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EVL007: Invalid Siblings Access
|
|
6
|
+
*
|
|
7
|
+
* In a Composite factory, resource() callbacks receive a `siblings` parameter.
|
|
8
|
+
* Accessing siblings.key must reference a key that exists in the composite's
|
|
9
|
+
* resource map.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
|
|
13
|
+
// Look for Composite(...) calls
|
|
14
|
+
if (
|
|
15
|
+
ts.isCallExpression(node) &&
|
|
16
|
+
ts.isIdentifier(node.expression) &&
|
|
17
|
+
node.expression.text === "Composite"
|
|
18
|
+
) {
|
|
19
|
+
// The factory function is the argument to Composite
|
|
20
|
+
const factoryArg = node.arguments[0];
|
|
21
|
+
if (factoryArg && (ts.isArrowFunction(factoryArg) || ts.isFunctionExpression(factoryArg))) {
|
|
22
|
+
checkCompositeFactory(factoryArg, context, diagnostics);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ts.forEachChild(node, (child) => checkNode(child, context, diagnostics));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function checkCompositeFactory(
|
|
30
|
+
factory: ts.ArrowFunction | ts.FunctionExpression,
|
|
31
|
+
context: LintContext,
|
|
32
|
+
diagnostics: LintDiagnostic[],
|
|
33
|
+
): void {
|
|
34
|
+
// The factory body should contain an object literal with resource() calls
|
|
35
|
+
// that define the composite members. Collect the keys.
|
|
36
|
+
const body = factory.body;
|
|
37
|
+
let memberKeys: Set<string> | null = null;
|
|
38
|
+
|
|
39
|
+
if (ts.isBlock(body)) {
|
|
40
|
+
// Look for return statement with object literal
|
|
41
|
+
for (const stmt of body.statements) {
|
|
42
|
+
if (ts.isReturnStatement(stmt) && stmt.expression && ts.isObjectLiteralExpression(stmt.expression)) {
|
|
43
|
+
memberKeys = collectObjectKeys(stmt.expression);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else if (ts.isParenthesizedExpression(body) && ts.isObjectLiteralExpression(body.expression)) {
|
|
47
|
+
memberKeys = collectObjectKeys(body.expression);
|
|
48
|
+
} else if (ts.isObjectLiteralExpression(body)) {
|
|
49
|
+
memberKeys = collectObjectKeys(body);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!memberKeys || memberKeys.size === 0) return;
|
|
53
|
+
|
|
54
|
+
// Now find resource() calls within the factory and check siblings access
|
|
55
|
+
walkForResourceCalls(body, memberKeys, context, diagnostics);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function collectObjectKeys(obj: ts.ObjectLiteralExpression): Set<string> {
|
|
59
|
+
const keys = new Set<string>();
|
|
60
|
+
for (const prop of obj.properties) {
|
|
61
|
+
if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {
|
|
62
|
+
if (ts.isIdentifier(prop.name!)) {
|
|
63
|
+
keys.add(prop.name!.text);
|
|
64
|
+
} else if (ts.isStringLiteral(prop.name!)) {
|
|
65
|
+
keys.add(prop.name!.text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return keys;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function walkForResourceCalls(
|
|
73
|
+
node: ts.Node,
|
|
74
|
+
memberKeys: Set<string>,
|
|
75
|
+
context: LintContext,
|
|
76
|
+
diagnostics: LintDiagnostic[],
|
|
77
|
+
): void {
|
|
78
|
+
if (
|
|
79
|
+
ts.isCallExpression(node) &&
|
|
80
|
+
ts.isIdentifier(node.expression) &&
|
|
81
|
+
node.expression.text === "resource"
|
|
82
|
+
) {
|
|
83
|
+
// resource(Type, (props, siblings) => ...)
|
|
84
|
+
const callback = node.arguments[1];
|
|
85
|
+
if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
|
|
86
|
+
const siblingsParam = callback.parameters[1];
|
|
87
|
+
if (siblingsParam && ts.isIdentifier(siblingsParam.name)) {
|
|
88
|
+
const siblingsName = siblingsParam.name.text;
|
|
89
|
+
checkSiblingsAccess(callback.body, siblingsName, memberKeys, context, diagnostics);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
ts.forEachChild(node, (child) =>
|
|
95
|
+
walkForResourceCalls(child, memberKeys, context, diagnostics),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function checkSiblingsAccess(
|
|
100
|
+
node: ts.Node,
|
|
101
|
+
siblingsName: string,
|
|
102
|
+
memberKeys: Set<string>,
|
|
103
|
+
context: LintContext,
|
|
104
|
+
diagnostics: LintDiagnostic[],
|
|
105
|
+
): void {
|
|
106
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
107
|
+
if (ts.isIdentifier(node.expression) && node.expression.text === siblingsName) {
|
|
108
|
+
const accessedKey = node.name.text;
|
|
109
|
+
if (!memberKeys.has(accessedKey)) {
|
|
110
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
111
|
+
node.getStart(context.sourceFile),
|
|
112
|
+
);
|
|
113
|
+
diagnostics.push({
|
|
114
|
+
file: context.filePath,
|
|
115
|
+
line: line + 1,
|
|
116
|
+
column: character + 1,
|
|
117
|
+
ruleId: "EVL007",
|
|
118
|
+
severity: "error",
|
|
119
|
+
message: `Invalid siblings access — "${accessedKey}" is not a member of this composite`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ts.forEachChild(node, (child) =>
|
|
126
|
+
checkSiblingsAccess(child, siblingsName, memberKeys, context, diagnostics),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const evl007InvalidSiblingsRule: LintRule = {
|
|
131
|
+
id: "EVL007",
|
|
132
|
+
severity: "error",
|
|
133
|
+
category: "correctness",
|
|
134
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
135
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
136
|
+
checkNode(context.sourceFile, context, diagnostics);
|
|
137
|
+
return diagnostics;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { evl008UnresolvableBarrelRefRule } from "./evl008-unresolvable-barrel-ref";
|
|
4
|
+
import type { LintContext } from "../rule";
|
|
5
|
+
|
|
6
|
+
function createContext(
|
|
7
|
+
code: string,
|
|
8
|
+
barrelExports?: Set<string>,
|
|
9
|
+
filePath = "test.ts",
|
|
10
|
+
): LintContext {
|
|
11
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
12
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined, barrelExports };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("EVL008: unresolvable-barrel-ref", () => {
|
|
16
|
+
test("rule metadata", () => {
|
|
17
|
+
expect(evl008UnresolvableBarrelRefRule.id).toBe("EVL008");
|
|
18
|
+
expect(evl008UnresolvableBarrelRefRule.severity).toBe("error");
|
|
19
|
+
expect(evl008UnresolvableBarrelRefRule.category).toBe("correctness");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("skips when no barrelExports provided", () => {
|
|
23
|
+
const ctx = createContext(`
|
|
24
|
+
export const $ = barrel(import.meta.dir);
|
|
25
|
+
const x = $.myBucket;
|
|
26
|
+
`);
|
|
27
|
+
expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("allows valid direct barrel reference", () => {
|
|
31
|
+
const exports = new Set(["myBucket", "myRole"]);
|
|
32
|
+
const ctx = createContext(
|
|
33
|
+
`
|
|
34
|
+
export const $ = barrel(import.meta.dir);
|
|
35
|
+
const x = $.myBucket;
|
|
36
|
+
`,
|
|
37
|
+
exports,
|
|
38
|
+
);
|
|
39
|
+
expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("flags invalid direct barrel reference", () => {
|
|
43
|
+
const exports = new Set(["myBucket", "myRole"]);
|
|
44
|
+
const ctx = createContext(
|
|
45
|
+
`
|
|
46
|
+
export const $ = barrel(import.meta.dir);
|
|
47
|
+
const x = $.nonExistent;
|
|
48
|
+
`,
|
|
49
|
+
exports,
|
|
50
|
+
);
|
|
51
|
+
const diags = evl008UnresolvableBarrelRefRule.check(ctx);
|
|
52
|
+
expect(diags).toHaveLength(1);
|
|
53
|
+
expect(diags[0].ruleId).toBe("EVL008");
|
|
54
|
+
expect(diags[0].message).toContain("nonExistent");
|
|
55
|
+
expect(diags[0].message).toContain("not exported");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("allows valid namespace barrel reference", () => {
|
|
59
|
+
const exports = new Set(["myBucket"]);
|
|
60
|
+
const ctx = createContext(
|
|
61
|
+
`
|
|
62
|
+
import * as _ from "./_";
|
|
63
|
+
const x = _.$.myBucket;
|
|
64
|
+
`,
|
|
65
|
+
exports,
|
|
66
|
+
);
|
|
67
|
+
expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("flags invalid namespace barrel reference", () => {
|
|
71
|
+
const exports = new Set(["myBucket"]);
|
|
72
|
+
const ctx = createContext(
|
|
73
|
+
`
|
|
74
|
+
import * as _ from "./_";
|
|
75
|
+
const x = _.$.missing;
|
|
76
|
+
`,
|
|
77
|
+
exports,
|
|
78
|
+
);
|
|
79
|
+
const diags = evl008UnresolvableBarrelRefRule.check(ctx);
|
|
80
|
+
expect(diags).toHaveLength(1);
|
|
81
|
+
expect(diags[0].message).toContain("missing");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("does not flag when no barrel usage in file", () => {
|
|
85
|
+
const exports = new Set(["myBucket"]);
|
|
86
|
+
const ctx = createContext(`const x = someObj.prop;`, exports);
|
|
87
|
+
expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("flags multiple invalid references", () => {
|
|
91
|
+
const exports = new Set(["myBucket"]);
|
|
92
|
+
const ctx = createContext(
|
|
93
|
+
`
|
|
94
|
+
export const $ = barrel(import.meta.dir);
|
|
95
|
+
const a = $.missing1;
|
|
96
|
+
const b = $.missing2;
|
|
97
|
+
`,
|
|
98
|
+
exports,
|
|
99
|
+
);
|
|
100
|
+
expect(evl008UnresolvableBarrelRefRule.check(ctx)).toHaveLength(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("handles mixed valid and invalid", () => {
|
|
104
|
+
const exports = new Set(["bucket", "role"]);
|
|
105
|
+
const ctx = createContext(
|
|
106
|
+
`
|
|
107
|
+
export const $ = barrel(import.meta.dir);
|
|
108
|
+
const a = $.bucket;
|
|
109
|
+
const b = $.missing;
|
|
110
|
+
const c = $.role;
|
|
111
|
+
`,
|
|
112
|
+
exports,
|
|
113
|
+
);
|
|
114
|
+
const diags = evl008UnresolvableBarrelRefRule.check(ctx);
|
|
115
|
+
expect(diags).toHaveLength(1);
|
|
116
|
+
expect(diags[0].message).toContain("missing");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EVL008: Unresolvable Barrel Reference
|
|
6
|
+
*
|
|
7
|
+
* When accessing the barrel variable (e.g., $.resourceName), the
|
|
8
|
+
* referenced name must exist in the project's barrel exports.
|
|
9
|
+
* Requires barrelExports on LintContext.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find the barrel variable name in a source file.
|
|
14
|
+
*
|
|
15
|
+
* Patterns:
|
|
16
|
+
* - `export const $ = barrel(...)` → "$"
|
|
17
|
+
* - `import * as _ from "./_"` then `_.$` → "$" accessed via "_"
|
|
18
|
+
*/
|
|
19
|
+
function findBarrelVarName(sourceFile: ts.SourceFile): string | null {
|
|
20
|
+
for (const stmt of sourceFile.statements) {
|
|
21
|
+
// export const $ = barrel(...)
|
|
22
|
+
if (
|
|
23
|
+
ts.isVariableStatement(stmt) &&
|
|
24
|
+
stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
|
|
25
|
+
) {
|
|
26
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
27
|
+
if (
|
|
28
|
+
ts.isIdentifier(decl.name) &&
|
|
29
|
+
decl.name.text === "$" &&
|
|
30
|
+
decl.initializer &&
|
|
31
|
+
ts.isCallExpression(decl.initializer) &&
|
|
32
|
+
ts.isIdentifier(decl.initializer.expression) &&
|
|
33
|
+
decl.initializer.expression.text === "barrel"
|
|
34
|
+
) {
|
|
35
|
+
return "$";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find namespace imports that re-export the barrel.
|
|
45
|
+
* e.g., `import * as _ from "./_"` — then _.$ accesses the barrel.
|
|
46
|
+
*/
|
|
47
|
+
function findBarrelNamespaceImports(sourceFile: ts.SourceFile): string[] {
|
|
48
|
+
const names: string[] = [];
|
|
49
|
+
for (const stmt of sourceFile.statements) {
|
|
50
|
+
if (
|
|
51
|
+
ts.isImportDeclaration(stmt) &&
|
|
52
|
+
stmt.importClause?.namedBindings &&
|
|
53
|
+
ts.isNamespaceImport(stmt.importClause.namedBindings) &&
|
|
54
|
+
stmt.moduleSpecifier &&
|
|
55
|
+
ts.isStringLiteral(stmt.moduleSpecifier)
|
|
56
|
+
) {
|
|
57
|
+
const spec = stmt.moduleSpecifier.text;
|
|
58
|
+
// Convention: barrel files are "_" or "./_"
|
|
59
|
+
if (spec === "./_" || spec === "_") {
|
|
60
|
+
names.push(stmt.importClause.namedBindings.name.text);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return names;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkNode(
|
|
68
|
+
node: ts.Node,
|
|
69
|
+
context: LintContext,
|
|
70
|
+
diagnostics: LintDiagnostic[],
|
|
71
|
+
barrelVar: string | null,
|
|
72
|
+
namespaceImports: string[],
|
|
73
|
+
): void {
|
|
74
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
75
|
+
const accessedName = node.name.text;
|
|
76
|
+
|
|
77
|
+
// Direct barrel access: $.resourceName
|
|
78
|
+
if (barrelVar && ts.isIdentifier(node.expression) && node.expression.text === barrelVar) {
|
|
79
|
+
if (context.barrelExports && !context.barrelExports.has(accessedName)) {
|
|
80
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
81
|
+
node.getStart(context.sourceFile),
|
|
82
|
+
);
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
file: context.filePath,
|
|
85
|
+
line: line + 1,
|
|
86
|
+
column: character + 1,
|
|
87
|
+
ruleId: "EVL008",
|
|
88
|
+
severity: "error",
|
|
89
|
+
message: `Unresolvable barrel reference — "${accessedName}" is not exported from the barrel`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Namespace access: _.$.resourceName
|
|
95
|
+
if (
|
|
96
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
97
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
98
|
+
namespaceImports.includes(node.expression.expression.text) &&
|
|
99
|
+
node.expression.name.text === "$"
|
|
100
|
+
) {
|
|
101
|
+
if (context.barrelExports && !context.barrelExports.has(accessedName)) {
|
|
102
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
103
|
+
node.getStart(context.sourceFile),
|
|
104
|
+
);
|
|
105
|
+
diagnostics.push({
|
|
106
|
+
file: context.filePath,
|
|
107
|
+
line: line + 1,
|
|
108
|
+
column: character + 1,
|
|
109
|
+
ruleId: "EVL008",
|
|
110
|
+
severity: "error",
|
|
111
|
+
message: `Unresolvable barrel reference — "${accessedName}" is not exported from the barrel`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
ts.forEachChild(node, (child) =>
|
|
118
|
+
checkNode(child, context, diagnostics, barrelVar, namespaceImports),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const evl008UnresolvableBarrelRefRule: LintRule = {
|
|
123
|
+
id: "EVL008",
|
|
124
|
+
severity: "error",
|
|
125
|
+
category: "correctness",
|
|
126
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
127
|
+
// Skip if no barrel exports data is available
|
|
128
|
+
if (!context.barrelExports) return [];
|
|
129
|
+
|
|
130
|
+
const barrelVar = findBarrelVarName(context.sourceFile);
|
|
131
|
+
const namespaceImports = findBarrelNamespaceImports(context.sourceFile);
|
|
132
|
+
|
|
133
|
+
// If this file doesn't use the barrel, nothing to check
|
|
134
|
+
if (!barrelVar && namespaceImports.length === 0) return [];
|
|
135
|
+
|
|
136
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
137
|
+
checkNode(context.sourceFile, context, diagnostics, barrelVar, namespaceImports);
|
|
138
|
+
return diagnostics;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { evl009CompositeNoConstantRule } from "./evl009-composite-no-constant";
|
|
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("EVL009: composite-no-constant", () => {
|
|
12
|
+
test("rule metadata", () => {
|
|
13
|
+
expect(evl009CompositeNoConstantRule.id).toBe("EVL009");
|
|
14
|
+
expect(evl009CompositeNoConstantRule.severity).toBe("warning");
|
|
15
|
+
expect(evl009CompositeNoConstantRule.category).toBe("style");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("flags inline object literal that doesn't reference props", () => {
|
|
19
|
+
const ctx = createContext(`
|
|
20
|
+
const MyComp = Composite((props) => {
|
|
21
|
+
const role = new Role({
|
|
22
|
+
assumeRolePolicyDocument: {
|
|
23
|
+
Version: "2012-10-17",
|
|
24
|
+
Statement: [{ Effect: "Allow", Principal: { Service: "lambda.amazonaws.com" }, Action: "sts:AssumeRole" }],
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
return { role };
|
|
28
|
+
}, "MyComp");
|
|
29
|
+
`);
|
|
30
|
+
const diags = evl009CompositeNoConstantRule.check(ctx);
|
|
31
|
+
expect(diags).toHaveLength(1);
|
|
32
|
+
expect(diags[0].ruleId).toBe("EVL009");
|
|
33
|
+
expect(diags[0].message).toContain("assumeRolePolicyDocument");
|
|
34
|
+
expect(diags[0].message).toContain("_.$.name");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("flags inline array with objects that doesn't reference props", () => {
|
|
38
|
+
const ctx = createContext(`
|
|
39
|
+
const MyComp = Composite((props) => {
|
|
40
|
+
const role = new Role({
|
|
41
|
+
managedPolicyArns: ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws:iam::aws:policy/AmazonS3ReadOnly"],
|
|
42
|
+
});
|
|
43
|
+
return { role };
|
|
44
|
+
}, "MyComp");
|
|
45
|
+
`);
|
|
46
|
+
// Array of simple strings — NOT flagged (no objects inside)
|
|
47
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("flags array containing objects", () => {
|
|
51
|
+
const ctx = createContext(`
|
|
52
|
+
const MyComp = Composite((props) => {
|
|
53
|
+
const role = new Role({
|
|
54
|
+
policies: [{ policyName: "S3Access", policyDocument: { Version: "2012-10-17" } }],
|
|
55
|
+
});
|
|
56
|
+
return { role };
|
|
57
|
+
}, "MyComp");
|
|
58
|
+
`);
|
|
59
|
+
const diags = evl009CompositeNoConstantRule.check(ctx);
|
|
60
|
+
expect(diags).toHaveLength(1);
|
|
61
|
+
expect(diags[0].message).toContain("policies");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("allows barrel refs (_.$.name)", () => {
|
|
65
|
+
const ctx = createContext(`
|
|
66
|
+
const MyComp = _.Composite((props) => {
|
|
67
|
+
const role = new Role({
|
|
68
|
+
assumeRolePolicyDocument: _.$.lambdaTrustPolicy,
|
|
69
|
+
});
|
|
70
|
+
return { role };
|
|
71
|
+
}, "MyComp");
|
|
72
|
+
`);
|
|
73
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("allows props references", () => {
|
|
77
|
+
const ctx = createContext(`
|
|
78
|
+
const MyComp = Composite((props) => {
|
|
79
|
+
const role = new Role({
|
|
80
|
+
policies: props.policies,
|
|
81
|
+
});
|
|
82
|
+
return { role };
|
|
83
|
+
}, "MyComp");
|
|
84
|
+
`);
|
|
85
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("allows object containing props reference", () => {
|
|
89
|
+
const ctx = createContext(`
|
|
90
|
+
const MyComp = Composite((props) => {
|
|
91
|
+
const func = new Function({
|
|
92
|
+
environment: { variables: { NAME: props.name } },
|
|
93
|
+
});
|
|
94
|
+
return { func };
|
|
95
|
+
}, "MyComp");
|
|
96
|
+
`);
|
|
97
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("allows sibling member reference", () => {
|
|
101
|
+
const ctx = createContext(`
|
|
102
|
+
const MyComp = Composite((props) => {
|
|
103
|
+
const role = new Role({ assumeRolePolicyDocument: _.$.trustPolicy });
|
|
104
|
+
const func = new Function({
|
|
105
|
+
config: { roleArn: role.arn },
|
|
106
|
+
});
|
|
107
|
+
return { role, func };
|
|
108
|
+
}, "MyComp");
|
|
109
|
+
`);
|
|
110
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("allows simple string literals (not extractable)", () => {
|
|
114
|
+
const ctx = createContext(`
|
|
115
|
+
const MyComp = Composite((props) => {
|
|
116
|
+
const perm = new Permission({
|
|
117
|
+
action: "lambda:InvokeFunction",
|
|
118
|
+
principal: "apigateway.amazonaws.com",
|
|
119
|
+
});
|
|
120
|
+
return { perm };
|
|
121
|
+
}, "MyComp");
|
|
122
|
+
`);
|
|
123
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("does not flag outside Composite", () => {
|
|
127
|
+
const ctx = createContext(`
|
|
128
|
+
const role = new Role({
|
|
129
|
+
assumeRolePolicyDocument: { Version: "2012-10-17" },
|
|
130
|
+
});
|
|
131
|
+
`);
|
|
132
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("flags multiple extractable constants", () => {
|
|
136
|
+
const ctx = createContext(`
|
|
137
|
+
const MyComp = Composite((props) => {
|
|
138
|
+
const role = new Role({
|
|
139
|
+
trustPolicy: { Version: "2012-10-17", Statement: [] },
|
|
140
|
+
config: { retryAttempts: 3, timeout: 30 },
|
|
141
|
+
});
|
|
142
|
+
return { role };
|
|
143
|
+
}, "MyComp");
|
|
144
|
+
`);
|
|
145
|
+
const diags = evl009CompositeNoConstantRule.check(ctx);
|
|
146
|
+
expect(diags).toHaveLength(2);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("allows array wrapping barrel ref", () => {
|
|
150
|
+
const ctx = createContext(`
|
|
151
|
+
const MyComp = Composite((props) => {
|
|
152
|
+
const role = new Role({
|
|
153
|
+
managedPolicyArns: [_.$.lambdaBasicExecutionArn],
|
|
154
|
+
});
|
|
155
|
+
return { role };
|
|
156
|
+
}, "MyComp");
|
|
157
|
+
`);
|
|
158
|
+
// Array with barrel ref inside — not flagged (contains barrel ref)
|
|
159
|
+
// Also it's an array of identifiers, no objects inside
|
|
160
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
});
|