@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,375 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join, dirname, resolve } from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { Severity, RuleConfig } from "./rule";
|
|
5
|
+
import strictPreset from "./presets/strict.json";
|
|
6
|
+
|
|
7
|
+
/** Mapping of built-in preset names to their file paths */
|
|
8
|
+
const BUILTIN_PRESETS: Record<string, string> = {
|
|
9
|
+
"@intentius/chant/lint/presets/strict": resolve(import.meta.dir, "presets/strict.json"),
|
|
10
|
+
"@intentius/chant/lint/presets/relaxed": resolve(import.meta.dir, "presets/relaxed.json"),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// ── Zod schemas for lint config validation ─────────────────────────
|
|
14
|
+
|
|
15
|
+
const SeveritySchema = z.enum(["off", "error", "warning", "info"]);
|
|
16
|
+
|
|
17
|
+
const RuleConfigSchema = z.union([
|
|
18
|
+
SeveritySchema,
|
|
19
|
+
z.tuple([SeveritySchema, z.record(z.string(), z.unknown())]),
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const LintConfigSchema = z.object({
|
|
23
|
+
rules: z.record(z.string(), RuleConfigSchema).optional(),
|
|
24
|
+
extends: z.array(z.string()).optional(),
|
|
25
|
+
overrides: z.array(z.object({
|
|
26
|
+
files: z.array(z.string()),
|
|
27
|
+
rules: z.record(z.string(), RuleConfigSchema),
|
|
28
|
+
})).optional(),
|
|
29
|
+
plugins: z.array(z.string()).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a Zod union error is specifically about an invalid severity string
|
|
34
|
+
* (as opposed to a completely wrong type like a number).
|
|
35
|
+
*/
|
|
36
|
+
function isBadSeverityError(issue: z.ZodIssue, config: unknown, path: readonly (string | number)[]): string | null {
|
|
37
|
+
if (issue.code !== "invalid_union") return null;
|
|
38
|
+
|
|
39
|
+
// Navigate to the value in the config to see what was actually provided
|
|
40
|
+
let value: unknown = config;
|
|
41
|
+
for (const key of path) {
|
|
42
|
+
if (value == null || typeof value !== "object") return null;
|
|
43
|
+
value = (value as any)[key];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If the value is a string, the user meant it as a severity — show the specific severity error
|
|
47
|
+
if (typeof value === "string") {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Format a Zod error into a human-readable message matching existing error patterns.
|
|
55
|
+
*/
|
|
56
|
+
function formatLintConfigError(configPath: string, error: z.ZodError, rawConfig: unknown): string {
|
|
57
|
+
const issue = error.issues[0];
|
|
58
|
+
const path = issue.path;
|
|
59
|
+
|
|
60
|
+
// Cast path to (string | number)[] — Zod's path is PropertyKey[] but only uses string | number
|
|
61
|
+
const p = path as (string | number)[];
|
|
62
|
+
|
|
63
|
+
// rules.{ruleId} — invalid severity or structure
|
|
64
|
+
if (p[0] === "rules" && p.length >= 2) {
|
|
65
|
+
const ruleId = p[1];
|
|
66
|
+
const badValue = isBadSeverityError(issue, rawConfig, p);
|
|
67
|
+
if (badValue !== null) {
|
|
68
|
+
return `Invalid config file ${configPath}: rule "${ruleId}" has invalid severity "${badValue}". Must be "off", "error", "warning", or "info"`;
|
|
69
|
+
}
|
|
70
|
+
return `Invalid config file ${configPath}: rule "${ruleId}" must be a severity string or [severity, options] tuple`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// rules — wrong type
|
|
74
|
+
if (p[0] === "rules" && p.length === 1) {
|
|
75
|
+
return `Invalid config file ${configPath}: rules must be an object`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// extends — wrong type or element type
|
|
79
|
+
if (p[0] === "extends") {
|
|
80
|
+
if (p.length === 1) {
|
|
81
|
+
return `Invalid config file ${configPath}: extends must be an array`;
|
|
82
|
+
}
|
|
83
|
+
return `Invalid config file ${configPath}: extends must be an array of strings`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// overrides[i].* errors
|
|
87
|
+
if (p[0] === "overrides") {
|
|
88
|
+
if (p.length === 1) {
|
|
89
|
+
return `Invalid config file ${configPath}: overrides must be an array`;
|
|
90
|
+
}
|
|
91
|
+
const i = p[1];
|
|
92
|
+
if (p.length === 2) {
|
|
93
|
+
return `Invalid config file ${configPath}: overrides[${i}] must be an object`;
|
|
94
|
+
}
|
|
95
|
+
if (p[2] === "files") {
|
|
96
|
+
if (p.length === 3) {
|
|
97
|
+
return `Invalid config file ${configPath}: overrides[${i}].files must be an array`;
|
|
98
|
+
}
|
|
99
|
+
return `Invalid config file ${configPath}: overrides[${i}].files must be an array of strings`;
|
|
100
|
+
}
|
|
101
|
+
if (p[2] === "rules") {
|
|
102
|
+
if (p.length === 3) {
|
|
103
|
+
return `Invalid config file ${configPath}: overrides[${i}].rules must be an object`;
|
|
104
|
+
}
|
|
105
|
+
if (p.length >= 4) {
|
|
106
|
+
const ruleId = p[3];
|
|
107
|
+
const badValue = isBadSeverityError(issue, rawConfig, p);
|
|
108
|
+
if (badValue !== null) {
|
|
109
|
+
return `Invalid config file ${configPath}: overrides[${i}] rule "${ruleId}" has invalid severity "${badValue}". Must be "off", "error", "warning", or "info"`;
|
|
110
|
+
}
|
|
111
|
+
return `Invalid config file ${configPath}: overrides[${i}] rule "${ruleId}" must be a severity string or [severity, options] tuple`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// plugins — wrong type or element type
|
|
117
|
+
if (p[0] === "plugins") {
|
|
118
|
+
if (p.length === 1) {
|
|
119
|
+
return `Invalid config file ${configPath}: plugins must be an array`;
|
|
120
|
+
}
|
|
121
|
+
return `Invalid config file ${configPath}: plugins must be an array of strings`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fallback
|
|
125
|
+
return `Invalid config file ${configPath}: ${issue.message}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Per-file rule override
|
|
130
|
+
*/
|
|
131
|
+
export interface LintOverride {
|
|
132
|
+
/** Glob patterns to match file paths */
|
|
133
|
+
files: string[];
|
|
134
|
+
/** Rule overrides for matched files */
|
|
135
|
+
rules: Record<string, RuleConfig>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Lint configuration
|
|
140
|
+
*/
|
|
141
|
+
export interface LintConfig {
|
|
142
|
+
/** Rule configurations: rule ID -> severity string or [severity, options] tuple */
|
|
143
|
+
rules?: Record<string, RuleConfig>;
|
|
144
|
+
/** Array of config file paths to extend from */
|
|
145
|
+
extends?: string[];
|
|
146
|
+
/** Per-file rule overrides via glob patterns */
|
|
147
|
+
overrides?: LintOverride[];
|
|
148
|
+
/** Array of plugin file paths to load custom rules from (project-local, not inherited) */
|
|
149
|
+
plugins?: string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Parsed rule configuration with severity and optional options
|
|
154
|
+
*/
|
|
155
|
+
export interface ParsedRuleConfig {
|
|
156
|
+
severity: "off" | Severity;
|
|
157
|
+
options?: Record<string, unknown>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Parse a rule config value into severity and options
|
|
162
|
+
*/
|
|
163
|
+
export function parseRuleConfig(value: RuleConfig): ParsedRuleConfig {
|
|
164
|
+
if (typeof value === "string") {
|
|
165
|
+
return { severity: value as "off" | Severity };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
169
|
+
throw new Error(`Invalid rule config: expected a severity string or [severity, options] tuple`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const [severity, options] = value;
|
|
173
|
+
|
|
174
|
+
if (typeof severity !== "string" || !isValidSeverity(severity)) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Invalid rule config: severity "${severity}" must be "off", "error", "warning", or "info"`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
181
|
+
throw new Error(`Invalid rule config: options must be a plain object`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { severity, options };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Default configuration with all rules enabled at strict preset severities
|
|
189
|
+
*/
|
|
190
|
+
export const DEFAULT_CONFIG: LintConfig = {
|
|
191
|
+
rules: { ...strictPreset.rules } as Record<string, RuleConfig>,
|
|
192
|
+
extends: [],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Validate that a severity string is valid
|
|
197
|
+
*/
|
|
198
|
+
function isValidSeverity(value: string): value is "off" | Severity {
|
|
199
|
+
return value === "off" || value === "error" || value === "warning" || value === "info";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Load and merge a single config file
|
|
204
|
+
*/
|
|
205
|
+
function loadConfigFile(configPath: string, visited: Set<string> = new Set()): LintConfig {
|
|
206
|
+
// Check for circular extends
|
|
207
|
+
if (visited.has(configPath)) {
|
|
208
|
+
throw new Error(`Circular extends detected: ${configPath}`);
|
|
209
|
+
}
|
|
210
|
+
visited.add(configPath);
|
|
211
|
+
|
|
212
|
+
// Read and parse config file
|
|
213
|
+
if (!existsSync(configPath)) {
|
|
214
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let content: string;
|
|
218
|
+
try {
|
|
219
|
+
content = readFileSync(configPath, "utf-8");
|
|
220
|
+
} catch (err) {
|
|
221
|
+
throw new Error(`Failed to read config file ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let config: LintConfig;
|
|
225
|
+
try {
|
|
226
|
+
config = JSON.parse(content);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
throw new Error(`Failed to parse config file ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Validate config structure
|
|
232
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
233
|
+
throw new Error(`Invalid config file ${configPath}: must be an object`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Validate with Zod schema
|
|
237
|
+
const parseResult = LintConfigSchema.safeParse(config);
|
|
238
|
+
if (!parseResult.success) {
|
|
239
|
+
throw new Error(formatLintConfigError(configPath, parseResult.error, config));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Process extends
|
|
243
|
+
let mergedConfig: LintConfig = { rules: {}, extends: [] };
|
|
244
|
+
|
|
245
|
+
if (config.extends && config.extends.length > 0) {
|
|
246
|
+
const baseDir = dirname(configPath);
|
|
247
|
+
|
|
248
|
+
for (const extendPath of config.extends) {
|
|
249
|
+
// Resolve paths: relative, built-in preset, or absolute
|
|
250
|
+
let resolvedPath: string;
|
|
251
|
+
if (extendPath.startsWith(".")) {
|
|
252
|
+
resolvedPath = join(baseDir, extendPath);
|
|
253
|
+
} else if (extendPath.startsWith("@intentius/chant")) {
|
|
254
|
+
const builtinPath = BUILTIN_PRESETS[extendPath];
|
|
255
|
+
if (builtinPath) {
|
|
256
|
+
resolvedPath = builtinPath;
|
|
257
|
+
} else {
|
|
258
|
+
throw new Error(`Unknown preset: ${extendPath} (extended from ${configPath})`);
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
resolvedPath = extendPath;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check if extended config exists
|
|
265
|
+
if (!existsSync(resolvedPath)) {
|
|
266
|
+
throw new Error(`Extended config file not found: ${resolvedPath} (extended from ${configPath})`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Load extended config recursively
|
|
270
|
+
const extendedConfig = loadConfigFile(resolvedPath, visited);
|
|
271
|
+
|
|
272
|
+
// Merge rules (later configs override earlier ones)
|
|
273
|
+
mergedConfig.rules = {
|
|
274
|
+
...mergedConfig.rules,
|
|
275
|
+
...extendedConfig.rules,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Merge current config rules on top
|
|
281
|
+
mergedConfig.rules = {
|
|
282
|
+
...mergedConfig.rules,
|
|
283
|
+
...config.rules,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Preserve overrides from the current config
|
|
287
|
+
if (config.overrides) {
|
|
288
|
+
mergedConfig.overrides = config.overrides;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Preserve plugins from the current config only (not inherited from extends)
|
|
292
|
+
if (config.plugins) {
|
|
293
|
+
mergedConfig.plugins = config.plugins;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return mergedConfig;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Load lint configuration from a directory.
|
|
301
|
+
*
|
|
302
|
+
* Tries `chant.config.ts` first (extracts `lint` property from ChantConfig),
|
|
303
|
+
* then falls back to `chant.config.json` (legacy LintConfig format).
|
|
304
|
+
* Returns default configuration if neither exists.
|
|
305
|
+
*
|
|
306
|
+
* @param dir - Directory path to search for config file
|
|
307
|
+
* @returns Loaded and merged configuration, or default config if not found
|
|
308
|
+
*/
|
|
309
|
+
export function loadConfig(dir: string): LintConfig {
|
|
310
|
+
// Try chant.config.ts first — Bun supports synchronous require() for .ts
|
|
311
|
+
const tsConfigPath = join(dir, "chant.config.ts");
|
|
312
|
+
if (existsSync(tsConfigPath)) {
|
|
313
|
+
try {
|
|
314
|
+
const mod = require(tsConfigPath);
|
|
315
|
+
const config = mod.default ?? mod.config ?? mod;
|
|
316
|
+
if (typeof config === "object" && config !== null) {
|
|
317
|
+
// ChantConfig format: extract lint property
|
|
318
|
+
if ("lint" in config && typeof config.lint === "object") {
|
|
319
|
+
return config.lint as LintConfig;
|
|
320
|
+
}
|
|
321
|
+
// Bare rules at top level
|
|
322
|
+
if ("rules" in config) {
|
|
323
|
+
return config as LintConfig;
|
|
324
|
+
}
|
|
325
|
+
// Config exists but has no lint section — use defaults
|
|
326
|
+
return DEFAULT_CONFIG;
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
// Fall through to JSON
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Fall back to chant.config.json
|
|
334
|
+
const jsonConfigPath = join(dir, "chant.config.json");
|
|
335
|
+
if (!existsSync(jsonConfigPath)) {
|
|
336
|
+
return DEFAULT_CONFIG;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
return loadConfigFile(jsonConfigPath);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Resolve the effective rules for a specific file path by applying overrides.
|
|
348
|
+
*
|
|
349
|
+
* Starts with the base config rules, then iterates through overrides in order.
|
|
350
|
+
* For each override whose file globs match the given path, merges override rules on top.
|
|
351
|
+
*
|
|
352
|
+
* @param config - The loaded lint configuration
|
|
353
|
+
* @param filePath - The file path to resolve rules for (relative to project root)
|
|
354
|
+
* @returns Merged rule configuration with overrides applied
|
|
355
|
+
*/
|
|
356
|
+
export function resolveRulesForFile(config: LintConfig, filePath: string): Record<string, RuleConfig> {
|
|
357
|
+
const rules: Record<string, RuleConfig> = { ...config.rules };
|
|
358
|
+
|
|
359
|
+
if (!config.overrides) {
|
|
360
|
+
return rules;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const override of config.overrides) {
|
|
364
|
+
const matches = override.files.some((pattern) => {
|
|
365
|
+
const glob = new Bun.Glob(pattern);
|
|
366
|
+
return glob.match(filePath);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (matches) {
|
|
370
|
+
Object.assign(rules, override.rules);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return rules;
|
|
375
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
import { rule } from "./declarative";
|
|
4
|
+
import type { RuleSpec } from "./declarative";
|
|
5
|
+
import type { LintContext } from "./rule";
|
|
6
|
+
import { registerCheck } from "./named-checks";
|
|
7
|
+
|
|
8
|
+
function createContext(code: string, filePath = "test.ts"): LintContext {
|
|
9
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
10
|
+
return { sourceFile, entities: [], filePath, lexicon: undefined };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("declarative rule()", () => {
|
|
14
|
+
test("creates a rule with correct metadata", () => {
|
|
15
|
+
const spec: RuleSpec = {
|
|
16
|
+
id: "TEST001",
|
|
17
|
+
severity: "warning",
|
|
18
|
+
category: "style",
|
|
19
|
+
selector: "string-literal",
|
|
20
|
+
message: "Found a string literal",
|
|
21
|
+
};
|
|
22
|
+
const r = rule(spec);
|
|
23
|
+
expect(r.id).toBe("TEST001");
|
|
24
|
+
expect(r.severity).toBe("warning");
|
|
25
|
+
expect(r.category).toBe("style");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("reports diagnostics for matching nodes", () => {
|
|
29
|
+
const spec: RuleSpec = {
|
|
30
|
+
id: "TEST002",
|
|
31
|
+
severity: "error",
|
|
32
|
+
category: "correctness",
|
|
33
|
+
selector: "string-literal",
|
|
34
|
+
message: "No string literals allowed",
|
|
35
|
+
};
|
|
36
|
+
const r = rule(spec);
|
|
37
|
+
const ctx = createContext(`const a = "hello"; const b = "world";`);
|
|
38
|
+
const diags = r.check(ctx);
|
|
39
|
+
expect(diags).toHaveLength(2);
|
|
40
|
+
expect(diags[0].ruleId).toBe("TEST002");
|
|
41
|
+
expect(diags[0].severity).toBe("error");
|
|
42
|
+
expect(diags[0].message).toBe("No string literals allowed");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("returns no diagnostics when nothing matches", () => {
|
|
46
|
+
const spec: RuleSpec = {
|
|
47
|
+
id: "TEST003",
|
|
48
|
+
severity: "warning",
|
|
49
|
+
category: "style",
|
|
50
|
+
selector: "resource",
|
|
51
|
+
message: "Found a resource",
|
|
52
|
+
};
|
|
53
|
+
const r = rule(spec);
|
|
54
|
+
const ctx = createContext(`const a = 42;`);
|
|
55
|
+
expect(r.check(ctx)).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("message template replaces {node}", () => {
|
|
59
|
+
const spec: RuleSpec = {
|
|
60
|
+
id: "TEST004",
|
|
61
|
+
severity: "info",
|
|
62
|
+
category: "style",
|
|
63
|
+
selector: "string-literal",
|
|
64
|
+
message: "Found: {node}",
|
|
65
|
+
};
|
|
66
|
+
const r = rule(spec);
|
|
67
|
+
const ctx = createContext(`const a = "hello";`);
|
|
68
|
+
const diags = r.check(ctx);
|
|
69
|
+
expect(diags[0].message).toBe('Found: "hello"');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("match.pattern filters by regex", () => {
|
|
73
|
+
const spec: RuleSpec = {
|
|
74
|
+
id: "TEST005",
|
|
75
|
+
severity: "warning",
|
|
76
|
+
category: "style",
|
|
77
|
+
selector: "string-literal",
|
|
78
|
+
match: { pattern: /world/ },
|
|
79
|
+
message: "Found world",
|
|
80
|
+
};
|
|
81
|
+
const r = rule(spec);
|
|
82
|
+
const ctx = createContext(`const a = "hello"; const b = "world";`);
|
|
83
|
+
const diags = r.check(ctx);
|
|
84
|
+
expect(diags).toHaveLength(1);
|
|
85
|
+
expect(diags[0].message).toBe("Found world");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("match.check uses named check", () => {
|
|
89
|
+
registerCheck("always-true", () => true);
|
|
90
|
+
registerCheck("always-false", () => false);
|
|
91
|
+
|
|
92
|
+
const specTrue: RuleSpec = {
|
|
93
|
+
id: "TEST006A",
|
|
94
|
+
severity: "warning",
|
|
95
|
+
category: "style",
|
|
96
|
+
selector: "string-literal",
|
|
97
|
+
match: { check: "always-true" },
|
|
98
|
+
message: "matches",
|
|
99
|
+
};
|
|
100
|
+
const specFalse: RuleSpec = {
|
|
101
|
+
id: "TEST006B",
|
|
102
|
+
severity: "warning",
|
|
103
|
+
category: "style",
|
|
104
|
+
selector: "string-literal",
|
|
105
|
+
match: { check: "always-false" },
|
|
106
|
+
message: "no match",
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const ctx = createContext(`const a = "hello";`);
|
|
110
|
+
expect(rule(specTrue).check(ctx)).toHaveLength(1);
|
|
111
|
+
expect(rule(specFalse).check(ctx)).toHaveLength(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("validate function filters nodes", () => {
|
|
115
|
+
const spec: RuleSpec = {
|
|
116
|
+
id: "TEST007",
|
|
117
|
+
severity: "warning",
|
|
118
|
+
category: "style",
|
|
119
|
+
selector: "string-literal",
|
|
120
|
+
message: "Found string",
|
|
121
|
+
validate: (node, _ctx) => {
|
|
122
|
+
return ts.isStringLiteral(node) && node.text === "keep";
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const r = rule(spec);
|
|
126
|
+
const ctx = createContext(`const a = "keep"; const b = "skip";`);
|
|
127
|
+
const diags = r.check(ctx);
|
|
128
|
+
expect(diags).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("fix with replace kind", () => {
|
|
132
|
+
const spec: RuleSpec = {
|
|
133
|
+
id: "TEST008",
|
|
134
|
+
severity: "warning",
|
|
135
|
+
category: "style",
|
|
136
|
+
selector: "string-literal",
|
|
137
|
+
message: "Replace string",
|
|
138
|
+
fix: { kind: "replace", text: '"replaced"' },
|
|
139
|
+
};
|
|
140
|
+
const r = rule(spec);
|
|
141
|
+
const ctx = createContext(`const a = "hello";`);
|
|
142
|
+
const diags = r.check(ctx);
|
|
143
|
+
expect(diags).toHaveLength(1);
|
|
144
|
+
expect(diags[0].fix).toBeDefined();
|
|
145
|
+
expect(diags[0].fix!.replacement).toBe('"replaced"');
|
|
146
|
+
expect(diags[0].fix!.kind).toBe("replace");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("fix with delete kind", () => {
|
|
150
|
+
const spec: RuleSpec = {
|
|
151
|
+
id: "TEST009",
|
|
152
|
+
severity: "warning",
|
|
153
|
+
category: "style",
|
|
154
|
+
selector: "string-literal",
|
|
155
|
+
message: "Delete string",
|
|
156
|
+
fix: { kind: "delete" },
|
|
157
|
+
};
|
|
158
|
+
const r = rule(spec);
|
|
159
|
+
const ctx = createContext(`const a = "hello";`);
|
|
160
|
+
const diags = r.check(ctx);
|
|
161
|
+
expect(diags[0].fix!.replacement).toBe("");
|
|
162
|
+
expect(diags[0].fix!.kind).toBe("delete");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("fix with resolve function", () => {
|
|
166
|
+
const spec: RuleSpec = {
|
|
167
|
+
id: "TEST010",
|
|
168
|
+
severity: "warning",
|
|
169
|
+
category: "style",
|
|
170
|
+
selector: "string-literal",
|
|
171
|
+
message: "Transform string",
|
|
172
|
+
fix: {
|
|
173
|
+
kind: "replace",
|
|
174
|
+
resolve: (node, sf) => `"${(node as ts.StringLiteral).text.toUpperCase()}"`,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
const r = rule(spec);
|
|
178
|
+
const ctx = createContext(`const a = "hello";`);
|
|
179
|
+
const diags = r.check(ctx);
|
|
180
|
+
expect(diags[0].fix!.replacement).toBe('"HELLO"');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("correct line and column positions", () => {
|
|
184
|
+
const code = [
|
|
185
|
+
`const x = 1;`,
|
|
186
|
+
`const a = "target";`,
|
|
187
|
+
`const y = 2;`,
|
|
188
|
+
].join("\n");
|
|
189
|
+
const spec: RuleSpec = {
|
|
190
|
+
id: "TEST011",
|
|
191
|
+
severity: "warning",
|
|
192
|
+
category: "style",
|
|
193
|
+
selector: "string-literal",
|
|
194
|
+
message: "Found",
|
|
195
|
+
};
|
|
196
|
+
const r = rule(spec);
|
|
197
|
+
const ctx = createContext(code);
|
|
198
|
+
const diags = r.check(ctx);
|
|
199
|
+
expect(diags).toHaveLength(1);
|
|
200
|
+
expect(diags[0].line).toBe(2);
|
|
201
|
+
expect(diags[0].file).toBe("test.ts");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("produces same diagnostics as equivalent imperative rule", () => {
|
|
205
|
+
// Declarative version
|
|
206
|
+
const declarativeRule = rule({
|
|
207
|
+
id: "CMP001",
|
|
208
|
+
severity: "warning",
|
|
209
|
+
category: "correctness",
|
|
210
|
+
selector: "resource",
|
|
211
|
+
message: "Found resource instantiation: {node}",
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Imperative equivalent
|
|
215
|
+
const imperativeRule = {
|
|
216
|
+
id: "CMP001",
|
|
217
|
+
severity: "warning" as const,
|
|
218
|
+
category: "correctness" as const,
|
|
219
|
+
check(context: LintContext) {
|
|
220
|
+
const diagnostics: { file: string; line: number; column: number; ruleId: string; severity: "warning"; message: string }[] = [];
|
|
221
|
+
const sf = context.sourceFile;
|
|
222
|
+
function visit(node: ts.Node) {
|
|
223
|
+
if (ts.isNewExpression(node)) {
|
|
224
|
+
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
225
|
+
diagnostics.push({
|
|
226
|
+
file: context.filePath,
|
|
227
|
+
line: line + 1,
|
|
228
|
+
column: character + 1,
|
|
229
|
+
ruleId: "CMP001",
|
|
230
|
+
severity: "warning",
|
|
231
|
+
message: `Found resource instantiation: ${node.getText(sf)}`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
ts.forEachChild(node, visit);
|
|
235
|
+
}
|
|
236
|
+
visit(sf);
|
|
237
|
+
return diagnostics;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const code = `const b = new Bucket({ name: "test" });\nconst t = new Table({});`;
|
|
242
|
+
const ctx = createContext(code);
|
|
243
|
+
|
|
244
|
+
const declDiags = declarativeRule.check(ctx);
|
|
245
|
+
const impDiags = imperativeRule.check(ctx);
|
|
246
|
+
|
|
247
|
+
expect(declDiags).toHaveLength(impDiags.length);
|
|
248
|
+
for (let i = 0; i < declDiags.length; i++) {
|
|
249
|
+
expect(declDiags[i].ruleId).toBe(impDiags[i].ruleId);
|
|
250
|
+
expect(declDiags[i].severity).toBe(impDiags[i].severity);
|
|
251
|
+
expect(declDiags[i].line).toBe(impDiags[i].line);
|
|
252
|
+
expect(declDiags[i].column).toBe(impDiags[i].column);
|
|
253
|
+
expect(declDiags[i].message).toBe(impDiags[i].message);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|