@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,67 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { loadLocalRules } from "./rule-loader";
|
|
5
|
+
|
|
6
|
+
const TEST_DIR = join(import.meta.dir, "__test_rules_fixture__");
|
|
7
|
+
const RULES_DIR = join(TEST_DIR, ".chant", "rules");
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
mkdirSync(RULES_DIR, { recursive: true });
|
|
11
|
+
|
|
12
|
+
// Write a valid rule file
|
|
13
|
+
writeFileSync(
|
|
14
|
+
join(RULES_DIR, "my-rule.ts"),
|
|
15
|
+
`
|
|
16
|
+
import type { LintRule, LintContext, LintDiagnostic } from "../../../rule";
|
|
17
|
+
|
|
18
|
+
export const myCustomRule: LintRule = {
|
|
19
|
+
id: "LOCAL001",
|
|
20
|
+
severity: "warning",
|
|
21
|
+
category: "style",
|
|
22
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
23
|
+
return [];
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Non-rule export should be ignored
|
|
28
|
+
export const notARule = "just a string";
|
|
29
|
+
`,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
if (existsSync(TEST_DIR)) {
|
|
35
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("rule-loader", () => {
|
|
40
|
+
test("loads rules from .chant/rules/ directory", async () => {
|
|
41
|
+
const rules = await loadLocalRules(TEST_DIR);
|
|
42
|
+
expect(rules).toHaveLength(1);
|
|
43
|
+
expect(rules[0].id).toBe("LOCAL001");
|
|
44
|
+
expect(rules[0].severity).toBe("warning");
|
|
45
|
+
expect(rules[0].category).toBe("style");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("returns empty array when .chant/rules/ does not exist", async () => {
|
|
49
|
+
const rules = await loadLocalRules("/tmp/nonexistent-project-dir");
|
|
50
|
+
expect(rules).toHaveLength(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("ignores test files in .chant/rules/", async () => {
|
|
54
|
+
// Write a test file that should be ignored
|
|
55
|
+
writeFileSync(
|
|
56
|
+
join(RULES_DIR, "my-rule.test.ts"),
|
|
57
|
+
`export const testRule = { id: "SKIP", severity: "error", category: "style", check() { return []; } };`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const rules = await loadLocalRules(TEST_DIR);
|
|
61
|
+
expect(rules).toHaveLength(1);
|
|
62
|
+
expect(rules[0].id).toBe("LOCAL001");
|
|
63
|
+
|
|
64
|
+
// Clean up
|
|
65
|
+
rmSync(join(RULES_DIR, "my-rule.test.ts"));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readdirSync, existsSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import type { LintRule } from "./rule";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type guard to check if a value conforms to the LintRule interface.
|
|
7
|
+
*/
|
|
8
|
+
function isLintRule(value: unknown): value is LintRule {
|
|
9
|
+
return (
|
|
10
|
+
typeof value === "object" &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
"id" in value &&
|
|
13
|
+
typeof (value as Record<string, unknown>).id === "string" &&
|
|
14
|
+
"severity" in value &&
|
|
15
|
+
"category" in value &&
|
|
16
|
+
"check" in value &&
|
|
17
|
+
typeof (value as Record<string, unknown>).check === "function"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load local lint rules from `.chant/rules/` directory.
|
|
23
|
+
*
|
|
24
|
+
* Scans for `.ts` files, dynamically imports each, and collects
|
|
25
|
+
* all exports that conform to the LintRule interface.
|
|
26
|
+
*
|
|
27
|
+
* @param projectDir - Root directory of the project
|
|
28
|
+
* @returns Array of LintRule objects found in `.chant/rules/`
|
|
29
|
+
*/
|
|
30
|
+
export async function loadLocalRules(projectDir: string): Promise<LintRule[]> {
|
|
31
|
+
const rulesDir = join(projectDir, ".chant", "rules");
|
|
32
|
+
|
|
33
|
+
if (!existsSync(rulesDir)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const rules: LintRule[] = [];
|
|
38
|
+
let entries: string[];
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
entries = readdirSync(rulesDir);
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tsFiles = entries.filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts") && !f.endsWith(".spec.ts"));
|
|
47
|
+
|
|
48
|
+
for (const file of tsFiles) {
|
|
49
|
+
const filePath = resolve(rulesDir, file);
|
|
50
|
+
let mod: Record<string, unknown>;
|
|
51
|
+
try {
|
|
52
|
+
mod = await import(filePath);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Failed to load rule file "${file}": ${err instanceof Error ? err.message : String(err)}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const value of Object.values(mod)) {
|
|
60
|
+
if (isLintRule(value)) {
|
|
61
|
+
rules.push(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return rules;
|
|
67
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { parseRuleConfig, loadConfig } from "./config";
|
|
3
|
+
import { fileDeclarableLimitRule } from "./rules/file-declarable-limit";
|
|
4
|
+
import * as ts from "typescript";
|
|
5
|
+
import type { LintContext } from "./rule";
|
|
6
|
+
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
10
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
11
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function makeNewExprs(names: string[]): string {
|
|
15
|
+
return names.map((n) => `const x = new ${n}({ name: "a" });`).join("\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TEST_DIR = join(import.meta.dir, "__test_rule_options__");
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("parseRuleConfig", () => {
|
|
29
|
+
test("parses string severity", () => {
|
|
30
|
+
const result = parseRuleConfig("warning");
|
|
31
|
+
expect(result).toEqual({ severity: "warning" });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("parses 'off' string", () => {
|
|
35
|
+
const result = parseRuleConfig("off");
|
|
36
|
+
expect(result).toEqual({ severity: "off" });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("parses [severity, options] tuple", () => {
|
|
40
|
+
const result = parseRuleConfig(["warning", { max: 12 }]);
|
|
41
|
+
expect(result).toEqual({ severity: "warning", options: { max: 12 } });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("throws for invalid tuple length", () => {
|
|
45
|
+
expect(() => parseRuleConfig([] as unknown as [string, Record<string, unknown>])).toThrow(
|
|
46
|
+
/expected a severity string or \[severity, options\] tuple/
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("throws for invalid severity in tuple", () => {
|
|
51
|
+
expect(() => parseRuleConfig(["invalid" as "error", { max: 1 }])).toThrow(
|
|
52
|
+
/severity "invalid" must be/
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("throws when options is not a plain object", () => {
|
|
57
|
+
expect(() => parseRuleConfig(["warning", [1, 2] as unknown as Record<string, unknown>])).toThrow(
|
|
58
|
+
/options must be a plain object/
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("throws when options is null", () => {
|
|
63
|
+
expect(() => parseRuleConfig(["warning", null as unknown as Record<string, unknown>])).toThrow(
|
|
64
|
+
/options must be a plain object/
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("COR009 with options", () => {
|
|
70
|
+
test("uses default limit (8) when no options", () => {
|
|
71
|
+
const code = makeNewExprs(["A", "B", "C", "D", "E", "F", "G", "H", "I"]);
|
|
72
|
+
const context = createContext(code);
|
|
73
|
+
const diagnostics = fileDeclarableLimitRule.check(context);
|
|
74
|
+
|
|
75
|
+
expect(diagnostics).toHaveLength(1);
|
|
76
|
+
expect(diagnostics[0].message).toContain("limit: 8");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("respects custom max from options", () => {
|
|
80
|
+
const code = makeNewExprs(["A", "B", "C", "D", "E", "F", "G", "H", "I"]);
|
|
81
|
+
const context = createContext(code);
|
|
82
|
+
|
|
83
|
+
// With max: 12, 9 instances should not trigger
|
|
84
|
+
const diagnostics = fileDeclarableLimitRule.check(context, { max: 12 });
|
|
85
|
+
expect(diagnostics).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("custom max triggers when exceeded", () => {
|
|
89
|
+
const code = makeNewExprs(["A", "B", "C", "D", "E"]);
|
|
90
|
+
const context = createContext(code);
|
|
91
|
+
|
|
92
|
+
// With max: 4, 5 instances should trigger
|
|
93
|
+
const diagnostics = fileDeclarableLimitRule.check(context, { max: 4 });
|
|
94
|
+
expect(diagnostics).toHaveLength(1);
|
|
95
|
+
expect(diagnostics[0].message).toContain("limit: 4");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("config loading with tuple format", () => {
|
|
100
|
+
test("loads config with string severity", () => {
|
|
101
|
+
writeFileSync(
|
|
102
|
+
join(TEST_DIR, "chant.config.json"),
|
|
103
|
+
JSON.stringify({ rules: { COR009: "error" } })
|
|
104
|
+
);
|
|
105
|
+
const config = loadConfig(TEST_DIR);
|
|
106
|
+
expect(config.rules?.["COR009"]).toBe("error");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("loads config with [severity, options] tuple", () => {
|
|
110
|
+
writeFileSync(
|
|
111
|
+
join(TEST_DIR, "chant.config.json"),
|
|
112
|
+
JSON.stringify({ rules: { COR009: ["warning", { max: 12 }] } })
|
|
113
|
+
);
|
|
114
|
+
const config = loadConfig(TEST_DIR);
|
|
115
|
+
expect(config.rules?.["COR009"]).toEqual(["warning", { max: 12 }]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("rejects invalid tuple format in config", () => {
|
|
119
|
+
writeFileSync(
|
|
120
|
+
join(TEST_DIR, "chant.config.json"),
|
|
121
|
+
JSON.stringify({ rules: { COR009: ["warning"] } })
|
|
122
|
+
);
|
|
123
|
+
expect(() => loadConfig(TEST_DIR)).toThrow(/must be a severity string or \[severity, options\] tuple/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("rejects non-object options in config tuple", () => {
|
|
127
|
+
writeFileSync(
|
|
128
|
+
join(TEST_DIR, "chant.config.json"),
|
|
129
|
+
JSON.stringify({ rules: { COR009: ["warning", "not-an-object"] } })
|
|
130
|
+
);
|
|
131
|
+
expect(() => loadConfig(TEST_DIR)).toThrow(/must be a severity string or \[severity, options\] tuple/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("rejects non-string/non-tuple rule values", () => {
|
|
135
|
+
writeFileSync(
|
|
136
|
+
join(TEST_DIR, "chant.config.json"),
|
|
137
|
+
JSON.stringify({ rules: { COR009: 42 } })
|
|
138
|
+
);
|
|
139
|
+
expect(() => loadConfig(TEST_DIR)).toThrow(/must be a severity string or \[severity, options\] tuple/);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import type {
|
|
3
|
+
LintRule,
|
|
4
|
+
LintContext,
|
|
5
|
+
LintDiagnostic,
|
|
6
|
+
LintFix,
|
|
7
|
+
Severity,
|
|
8
|
+
Category,
|
|
9
|
+
} from "./rule";
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
|
|
12
|
+
describe("LintRule interfaces", () => {
|
|
13
|
+
test("creates mock LintRule with all required methods", () => {
|
|
14
|
+
const mockRule: LintRule = {
|
|
15
|
+
id: "test-rule",
|
|
16
|
+
severity: "warning" as Severity,
|
|
17
|
+
category: "correctness" as Category,
|
|
18
|
+
check: (context: LintContext) => [],
|
|
19
|
+
fix: (context: LintContext) => [],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
expect(mockRule.id).toBe("test-rule");
|
|
23
|
+
expect(mockRule.severity).toBe("warning");
|
|
24
|
+
expect(mockRule.category).toBe("correctness");
|
|
25
|
+
expect(typeof mockRule.check).toBe("function");
|
|
26
|
+
expect(typeof mockRule.fix).toBe("function");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("creates LintContext with undefined lexicon for core rules", () => {
|
|
30
|
+
const sourceFile = ts.createSourceFile(
|
|
31
|
+
"test.ts",
|
|
32
|
+
"const x = 1;",
|
|
33
|
+
ts.ScriptTarget.Latest,
|
|
34
|
+
true
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const context: LintContext = {
|
|
38
|
+
sourceFile,
|
|
39
|
+
entities: [],
|
|
40
|
+
filePath: "test.ts",
|
|
41
|
+
lexicon: undefined,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
expect(context.sourceFile).toBe(sourceFile);
|
|
45
|
+
expect(context.entities).toEqual([]);
|
|
46
|
+
expect(context.filePath).toBe("test.ts");
|
|
47
|
+
expect(context.lexicon).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("creates LintContext with lexicon for lexicon-specific rules", () => {
|
|
51
|
+
const sourceFile = ts.createSourceFile(
|
|
52
|
+
"test.ts",
|
|
53
|
+
"const x = 1;",
|
|
54
|
+
ts.ScriptTarget.Latest,
|
|
55
|
+
true
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const context: LintContext = {
|
|
59
|
+
sourceFile,
|
|
60
|
+
entities: [{ name: "TestEntity" }],
|
|
61
|
+
filePath: "test.ts",
|
|
62
|
+
lexicon: "my-domain",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
expect(context.lexicon).toBe("my-domain");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("creates LintDiagnostic without fix", () => {
|
|
69
|
+
const diagnostic: LintDiagnostic = {
|
|
70
|
+
file: "/src/test.ts",
|
|
71
|
+
line: 10,
|
|
72
|
+
column: 5,
|
|
73
|
+
ruleId: "no-unused-vars",
|
|
74
|
+
severity: "error",
|
|
75
|
+
message: "Variable 'x' is declared but never used",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
expect(diagnostic.file).toBe("/src/test.ts");
|
|
79
|
+
expect(diagnostic.line).toBe(10);
|
|
80
|
+
expect(diagnostic.column).toBe(5);
|
|
81
|
+
expect(diagnostic.ruleId).toBe("no-unused-vars");
|
|
82
|
+
expect(diagnostic.severity).toBe("error");
|
|
83
|
+
expect(diagnostic.message).toBe("Variable 'x' is declared but never used");
|
|
84
|
+
expect(diagnostic.fix).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("creates LintDiagnostic with fix", () => {
|
|
88
|
+
const fix: LintFix = {
|
|
89
|
+
range: [100, 110],
|
|
90
|
+
replacement: "const y = 1;",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const diagnostic: LintDiagnostic = {
|
|
94
|
+
file: "/src/test.ts",
|
|
95
|
+
line: 10,
|
|
96
|
+
column: 5,
|
|
97
|
+
ruleId: "prefer-const",
|
|
98
|
+
severity: "warning",
|
|
99
|
+
message: "Use 'const' instead of 'let'",
|
|
100
|
+
fix,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
expect(diagnostic.fix).toBe(fix);
|
|
104
|
+
expect(diagnostic.fix?.range).toEqual([100, 110]);
|
|
105
|
+
expect(diagnostic.fix?.replacement).toBe("const y = 1;");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("LintDiagnostic serializes to JSON correctly", () => {
|
|
109
|
+
const diagnostic: LintDiagnostic = {
|
|
110
|
+
file: "/src/app.ts",
|
|
111
|
+
line: 42,
|
|
112
|
+
column: 15,
|
|
113
|
+
ruleId: "no-console",
|
|
114
|
+
severity: "info",
|
|
115
|
+
message: "Unexpected console statement",
|
|
116
|
+
fix: {
|
|
117
|
+
range: [500, 520],
|
|
118
|
+
replacement: "",
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const json = JSON.parse(JSON.stringify(diagnostic));
|
|
123
|
+
|
|
124
|
+
expect(json).toEqual({
|
|
125
|
+
file: "/src/app.ts",
|
|
126
|
+
line: 42,
|
|
127
|
+
column: 15,
|
|
128
|
+
ruleId: "no-console",
|
|
129
|
+
severity: "info",
|
|
130
|
+
message: "Unexpected console statement",
|
|
131
|
+
fix: {
|
|
132
|
+
range: [500, 520],
|
|
133
|
+
replacement: "",
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("LintFix has valid source positions", () => {
|
|
139
|
+
const fix: LintFix = {
|
|
140
|
+
range: [0, 10],
|
|
141
|
+
replacement: "new code",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
expect(fix.range[0]).toBeGreaterThanOrEqual(0);
|
|
145
|
+
expect(fix.range[1]).toBeGreaterThan(fix.range[0]);
|
|
146
|
+
expect(typeof fix.replacement).toBe("string");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("LintRule can be implemented without fix method", () => {
|
|
150
|
+
const ruleWithoutFix: LintRule = {
|
|
151
|
+
id: "no-fix-rule",
|
|
152
|
+
severity: "error",
|
|
153
|
+
category: "style",
|
|
154
|
+
check: () => [],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
expect(ruleWithoutFix.fix).toBeUndefined();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("all severity levels are valid", () => {
|
|
161
|
+
const severities: Severity[] = ["error", "warning", "info"];
|
|
162
|
+
|
|
163
|
+
severities.forEach((severity) => {
|
|
164
|
+
const diagnostic: LintDiagnostic = {
|
|
165
|
+
file: "test.ts",
|
|
166
|
+
line: 1,
|
|
167
|
+
column: 1,
|
|
168
|
+
ruleId: "test",
|
|
169
|
+
severity,
|
|
170
|
+
message: "test",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
expect(diagnostic.severity).toBe(severity);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("all categories are valid", () => {
|
|
178
|
+
const categories: Category[] = [
|
|
179
|
+
"correctness",
|
|
180
|
+
"style",
|
|
181
|
+
"performance",
|
|
182
|
+
"security",
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
categories.forEach((category) => {
|
|
186
|
+
const rule: LintRule = {
|
|
187
|
+
id: "test",
|
|
188
|
+
severity: "error",
|
|
189
|
+
category,
|
|
190
|
+
check: () => [],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
expect(rule.category).toBe(category);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
package/src/lint/rule.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Severity level for lint diagnostics
|
|
5
|
+
*/
|
|
6
|
+
export type Severity = "error" | "warning" | "info";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Category for grouping related lint rules
|
|
10
|
+
*/
|
|
11
|
+
export type Category = "correctness" | "style" | "performance" | "security";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A fix that can be applied to source code
|
|
15
|
+
*/
|
|
16
|
+
export interface LintFix {
|
|
17
|
+
/** [start, end] positions in the source file */
|
|
18
|
+
range: [number, number];
|
|
19
|
+
/** Text to replace the range with */
|
|
20
|
+
replacement: string;
|
|
21
|
+
/** Kind of fix operation */
|
|
22
|
+
kind?: "replace" | "insert-before" | "insert-after" | "delete" | "write-file";
|
|
23
|
+
/** Additional parameters for the fix */
|
|
24
|
+
params?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A diagnostic message from a lint rule
|
|
29
|
+
*/
|
|
30
|
+
export interface LintDiagnostic {
|
|
31
|
+
/** File path where the issue was found */
|
|
32
|
+
file: string;
|
|
33
|
+
/** Line number (1-based) */
|
|
34
|
+
line: number;
|
|
35
|
+
/** Column number (1-based) */
|
|
36
|
+
column: number;
|
|
37
|
+
/** ID of the rule that produced this diagnostic */
|
|
38
|
+
ruleId: string;
|
|
39
|
+
/** Severity level */
|
|
40
|
+
severity: Severity;
|
|
41
|
+
/** Human-readable message */
|
|
42
|
+
message: string;
|
|
43
|
+
/** Optional fix that can be applied */
|
|
44
|
+
fix?: LintFix;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Context provided to lint rules during checking
|
|
49
|
+
*/
|
|
50
|
+
export interface LintContext {
|
|
51
|
+
/** Parsed TypeScript source file */
|
|
52
|
+
sourceFile: ts.SourceFile;
|
|
53
|
+
/** Discovered entities in the file */
|
|
54
|
+
entities: unknown[];
|
|
55
|
+
/** Path to the file being linted */
|
|
56
|
+
filePath: string;
|
|
57
|
+
/** Optional lexicon context (undefined for core rules) */
|
|
58
|
+
lexicon?: string;
|
|
59
|
+
/** Export names from the barrel file (for EVL008) */
|
|
60
|
+
barrelExports?: Set<string>;
|
|
61
|
+
/** All project exports keyed by name (for EVL008) */
|
|
62
|
+
projectExports?: Map<string, { file: string; className: string }>;
|
|
63
|
+
/** Project scan result (for COR016) */
|
|
64
|
+
projectScan?: import("../project/scan").ProjectScan;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Options for extending the lint context with project-level information
|
|
69
|
+
*/
|
|
70
|
+
export interface LintRunOptions {
|
|
71
|
+
/** Export names from the barrel file */
|
|
72
|
+
barrelExports?: Set<string>;
|
|
73
|
+
/** All project exports keyed by name */
|
|
74
|
+
projectExports?: Map<string, { file: string; className: string }>;
|
|
75
|
+
/** Project scan result (for COR016) */
|
|
76
|
+
projectScan?: import("../project/scan").ProjectScan;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Configuration value for a rule: either a severity string or a [severity, options] tuple
|
|
81
|
+
*/
|
|
82
|
+
export type RuleConfig = Severity | "off" | [Severity | "off", Record<string, unknown>];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A lint rule that can check code and optionally provide fixes
|
|
86
|
+
*/
|
|
87
|
+
export interface LintRule {
|
|
88
|
+
/** Unique identifier for this rule */
|
|
89
|
+
id: string;
|
|
90
|
+
/** Severity level for diagnostics from this rule */
|
|
91
|
+
severity: Severity;
|
|
92
|
+
/** Category for grouping */
|
|
93
|
+
category: Category;
|
|
94
|
+
/** Check the code and return diagnostics */
|
|
95
|
+
check(context: LintContext, options?: Record<string, unknown>): LintDiagnostic[];
|
|
96
|
+
/** Optionally provide fixes for issues found */
|
|
97
|
+
fix?(context: LintContext): LintFix[];
|
|
98
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { barrelImportStyleRule } from "./barrel-import-style";
|
|
4
|
+
import type { LintContext } from "../rule";
|
|
5
|
+
|
|
6
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
7
|
+
const sourceFile = ts.createSourceFile(
|
|
8
|
+
filePath,
|
|
9
|
+
code,
|
|
10
|
+
ts.ScriptTarget.Latest,
|
|
11
|
+
true
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
sourceFile,
|
|
16
|
+
entities: [],
|
|
17
|
+
filePath,
|
|
18
|
+
lexicon: undefined,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("COR002: barrel-import-style", () => {
|
|
23
|
+
test("rule metadata", () => {
|
|
24
|
+
expect(barrelImportStyleRule.id).toBe("COR002");
|
|
25
|
+
expect(barrelImportStyleRule.severity).toBe("error");
|
|
26
|
+
expect(barrelImportStyleRule.category).toBe("style");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("flags named import from ./_", () => {
|
|
30
|
+
const code = `import { bucketEncryption } from "./_";`;
|
|
31
|
+
const context = createContext(code);
|
|
32
|
+
const diagnostics = barrelImportStyleRule.check(context);
|
|
33
|
+
|
|
34
|
+
expect(diagnostics).toHaveLength(1);
|
|
35
|
+
expect(diagnostics[0].ruleId).toBe("COR002");
|
|
36
|
+
expect(diagnostics[0].severity).toBe("error");
|
|
37
|
+
expect(diagnostics[0].message).toBe(
|
|
38
|
+
`Use namespace import for local barrel — replace with: import * as _ from "./_"`
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("allows namespace import from ./_", () => {
|
|
43
|
+
const code = `import * as _ from "./_";`;
|
|
44
|
+
const context = createContext(code);
|
|
45
|
+
const diagnostics = barrelImportStyleRule.check(context);
|
|
46
|
+
expect(diagnostics).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("allows type-only import from ./_", () => {
|
|
50
|
+
const code = `import type { Config } from "./_";`;
|
|
51
|
+
const context = createContext(code);
|
|
52
|
+
const diagnostics = barrelImportStyleRule.check(context);
|
|
53
|
+
expect(diagnostics).toHaveLength(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("does not flag imports from other relative paths", () => {
|
|
57
|
+
const code = `import { helper } from "./utils";`;
|
|
58
|
+
const context = createContext(code);
|
|
59
|
+
const diagnostics = barrelImportStyleRule.check(context);
|
|
60
|
+
expect(diagnostics).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("does not flag imports from packages", () => {
|
|
64
|
+
const code = `import { useState } from "react";`;
|
|
65
|
+
const context = createContext(code);
|
|
66
|
+
const diagnostics = barrelImportStyleRule.check(context);
|
|
67
|
+
expect(diagnostics).toHaveLength(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("reports correct line and column numbers", () => {
|
|
71
|
+
const code = `import { foo } from "./_";`;
|
|
72
|
+
const context = createContext(code);
|
|
73
|
+
const diagnostics = barrelImportStyleRule.check(context);
|
|
74
|
+
|
|
75
|
+
expect(diagnostics).toHaveLength(1);
|
|
76
|
+
expect(diagnostics[0].line).toBe(1);
|
|
77
|
+
expect(diagnostics[0].column).toBe(1);
|
|
78
|
+
expect(diagnostics[0].file).toBe("test.ts");
|
|
79
|
+
});
|
|
80
|
+
});
|