@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.
Files changed (271) hide show
  1. package/README.md +365 -0
  2. package/package.json +22 -0
  3. package/src/attrref.test.ts +148 -0
  4. package/src/attrref.ts +50 -0
  5. package/src/barrel.test.ts +157 -0
  6. package/src/barrel.ts +101 -0
  7. package/src/bench.test.ts +227 -0
  8. package/src/build.test.ts +437 -0
  9. package/src/build.ts +425 -0
  10. package/src/builder.test.ts +312 -0
  11. package/src/builder.ts +56 -0
  12. package/src/child-project.ts +44 -0
  13. package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
  14. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
  15. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
  16. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
  17. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
  18. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
  19. package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
  20. package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
  21. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
  22. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
  23. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
  24. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
  25. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
  26. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
  27. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
  28. package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
  29. package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
  30. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
  31. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
  32. package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
  33. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
  34. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
  35. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
  36. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
  37. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
  38. package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
  39. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
  40. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
  41. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
  42. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
  43. package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
  44. package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
  45. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
  46. package/src/cli/commands/build.test.ts +149 -0
  47. package/src/cli/commands/build.ts +344 -0
  48. package/src/cli/commands/diff.test.ts +148 -0
  49. package/src/cli/commands/diff.ts +221 -0
  50. package/src/cli/commands/doctor.test.ts +239 -0
  51. package/src/cli/commands/doctor.ts +224 -0
  52. package/src/cli/commands/import.test.ts +379 -0
  53. package/src/cli/commands/import.ts +335 -0
  54. package/src/cli/commands/init-lexicon.test.ts +297 -0
  55. package/src/cli/commands/init-lexicon.ts +993 -0
  56. package/src/cli/commands/init.test.ts +317 -0
  57. package/src/cli/commands/init.ts +505 -0
  58. package/src/cli/commands/licenses.ts +165 -0
  59. package/src/cli/commands/lint.test.ts +332 -0
  60. package/src/cli/commands/lint.ts +408 -0
  61. package/src/cli/commands/list.test.ts +100 -0
  62. package/src/cli/commands/list.ts +108 -0
  63. package/src/cli/commands/update.test.ts +38 -0
  64. package/src/cli/commands/update.ts +207 -0
  65. package/src/cli/conflict-check.test.ts +255 -0
  66. package/src/cli/conflict-check.ts +89 -0
  67. package/src/cli/debug.ts +8 -0
  68. package/src/cli/format.test.ts +140 -0
  69. package/src/cli/format.ts +133 -0
  70. package/src/cli/handlers/build.ts +58 -0
  71. package/src/cli/handlers/dev.ts +38 -0
  72. package/src/cli/handlers/init.ts +46 -0
  73. package/src/cli/handlers/lint.ts +36 -0
  74. package/src/cli/handlers/misc.ts +57 -0
  75. package/src/cli/handlers/serve.ts +26 -0
  76. package/src/cli/index.ts +3 -0
  77. package/src/cli/lsp/capabilities.ts +46 -0
  78. package/src/cli/lsp/diagnostics.ts +52 -0
  79. package/src/cli/lsp/server.test.ts +618 -0
  80. package/src/cli/lsp/server.ts +393 -0
  81. package/src/cli/main.test.ts +257 -0
  82. package/src/cli/main.ts +224 -0
  83. package/src/cli/mcp/resources/context.ts +59 -0
  84. package/src/cli/mcp/server.test.ts +747 -0
  85. package/src/cli/mcp/server.ts +402 -0
  86. package/src/cli/mcp/tools/build.ts +117 -0
  87. package/src/cli/mcp/tools/import.ts +48 -0
  88. package/src/cli/mcp/tools/lint.ts +45 -0
  89. package/src/cli/plugins.test.ts +31 -0
  90. package/src/cli/plugins.ts +94 -0
  91. package/src/cli/registry.ts +73 -0
  92. package/src/cli/reporters/stylish.test.ts +282 -0
  93. package/src/cli/reporters/stylish.ts +186 -0
  94. package/src/cli/watch.test.ts +81 -0
  95. package/src/cli/watch.ts +101 -0
  96. package/src/codegen/case.test.ts +30 -0
  97. package/src/codegen/case.ts +11 -0
  98. package/src/codegen/coverage.ts +167 -0
  99. package/src/codegen/docs.ts +634 -0
  100. package/src/codegen/fetch.test.ts +119 -0
  101. package/src/codegen/fetch.ts +261 -0
  102. package/src/codegen/generate-registry.test.ts +118 -0
  103. package/src/codegen/generate-registry.ts +107 -0
  104. package/src/codegen/generate-runtime-index.test.ts +81 -0
  105. package/src/codegen/generate-runtime-index.ts +99 -0
  106. package/src/codegen/generate-typescript.test.ts +146 -0
  107. package/src/codegen/generate-typescript.ts +161 -0
  108. package/src/codegen/generate.ts +206 -0
  109. package/src/codegen/json-patch.test.ts +113 -0
  110. package/src/codegen/json-patch.ts +151 -0
  111. package/src/codegen/json-schema.test.ts +196 -0
  112. package/src/codegen/json-schema.ts +209 -0
  113. package/src/codegen/naming.ts +201 -0
  114. package/src/codegen/package.ts +161 -0
  115. package/src/codegen/rollback.test.ts +92 -0
  116. package/src/codegen/rollback.ts +115 -0
  117. package/src/codegen/topo-sort.test.ts +69 -0
  118. package/src/codegen/topo-sort.ts +46 -0
  119. package/src/codegen/typecheck.test.ts +37 -0
  120. package/src/codegen/typecheck.ts +74 -0
  121. package/src/codegen/validate.test.ts +86 -0
  122. package/src/codegen/validate.ts +143 -0
  123. package/src/composite.test.ts +426 -0
  124. package/src/composite.ts +243 -0
  125. package/src/config.test.ts +91 -0
  126. package/src/config.ts +87 -0
  127. package/src/declarable.test.ts +160 -0
  128. package/src/declarable.ts +47 -0
  129. package/src/detectLexicon.test.ts +236 -0
  130. package/src/detectLexicon.ts +37 -0
  131. package/src/discovery/cache.test.ts +78 -0
  132. package/src/discovery/cache.ts +86 -0
  133. package/src/discovery/collect.test.ts +269 -0
  134. package/src/discovery/collect.ts +51 -0
  135. package/src/discovery/cycles.test.ts +238 -0
  136. package/src/discovery/cycles.ts +107 -0
  137. package/src/discovery/files.test.ts +154 -0
  138. package/src/discovery/files.ts +61 -0
  139. package/src/discovery/graph.test.ts +476 -0
  140. package/src/discovery/graph.ts +150 -0
  141. package/src/discovery/import.test.ts +199 -0
  142. package/src/discovery/import.ts +20 -0
  143. package/src/discovery/index.test.ts +272 -0
  144. package/src/discovery/index.ts +132 -0
  145. package/src/discovery/resolve.test.ts +267 -0
  146. package/src/discovery/resolve.ts +54 -0
  147. package/src/errors.test.ts +138 -0
  148. package/src/errors.ts +86 -0
  149. package/src/import/base-parser.test.ts +67 -0
  150. package/src/import/base-parser.ts +48 -0
  151. package/src/import/generator.ts +21 -0
  152. package/src/import/ir-utils.test.ts +103 -0
  153. package/src/import/ir-utils.ts +87 -0
  154. package/src/import/parser.ts +41 -0
  155. package/src/index.ts +60 -0
  156. package/src/intrinsic-interpolation.test.ts +91 -0
  157. package/src/intrinsic-interpolation.ts +89 -0
  158. package/src/intrinsic.test.ts +69 -0
  159. package/src/intrinsic.ts +43 -0
  160. package/src/lexicon-integrity.test.ts +94 -0
  161. package/src/lexicon-integrity.ts +69 -0
  162. package/src/lexicon-manifest.test.ts +101 -0
  163. package/src/lexicon-manifest.ts +71 -0
  164. package/src/lexicon-output.test.ts +182 -0
  165. package/src/lexicon-output.ts +82 -0
  166. package/src/lexicon-schema.test.ts +239 -0
  167. package/src/lexicon-schema.ts +144 -0
  168. package/src/lexicon.ts +212 -0
  169. package/src/lint/config-overrides.test.ts +254 -0
  170. package/src/lint/config.test.ts +644 -0
  171. package/src/lint/config.ts +375 -0
  172. package/src/lint/declarative.test.ts +256 -0
  173. package/src/lint/declarative.ts +187 -0
  174. package/src/lint/engine.test.ts +465 -0
  175. package/src/lint/engine.ts +172 -0
  176. package/src/lint/named-checks.test.ts +37 -0
  177. package/src/lint/named-checks.ts +33 -0
  178. package/src/lint/parser.test.ts +129 -0
  179. package/src/lint/parser.ts +42 -0
  180. package/src/lint/post-synth.test.ts +113 -0
  181. package/src/lint/post-synth.ts +76 -0
  182. package/src/lint/presets/relaxed.json +19 -0
  183. package/src/lint/presets/strict.json +19 -0
  184. package/src/lint/rule-loader.test.ts +67 -0
  185. package/src/lint/rule-loader.ts +67 -0
  186. package/src/lint/rule-options.test.ts +141 -0
  187. package/src/lint/rule.test.ts +196 -0
  188. package/src/lint/rule.ts +98 -0
  189. package/src/lint/rules/barrel-import-style.test.ts +80 -0
  190. package/src/lint/rules/barrel-import-style.ts +59 -0
  191. package/src/lint/rules/composite-scope.ts +55 -0
  192. package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
  193. package/src/lint/rules/cor017-composite-name-match.ts +108 -0
  194. package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
  195. package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
  196. package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
  197. package/src/lint/rules/declarable-naming-convention.ts +70 -0
  198. package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
  199. package/src/lint/rules/enforce-barrel-import.ts +81 -0
  200. package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
  201. package/src/lint/rules/enforce-barrel-ref.ts +75 -0
  202. package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
  203. package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
  204. package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
  205. package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
  206. package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
  207. package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
  208. package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
  209. package/src/lint/rules/evl004-spread-non-const.ts +111 -0
  210. package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
  211. package/src/lint/rules/evl005-resource-block-body.ts +49 -0
  212. package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
  213. package/src/lint/rules/evl006-barrel-usage.ts +95 -0
  214. package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
  215. package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
  216. package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
  217. package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
  218. package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
  219. package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
  220. package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
  221. package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
  222. package/src/lint/rules/export-required.test.ts +213 -0
  223. package/src/lint/rules/export-required.ts +158 -0
  224. package/src/lint/rules/file-declarable-limit.test.ts +148 -0
  225. package/src/lint/rules/file-declarable-limit.ts +96 -0
  226. package/src/lint/rules/flat-declarations.test.ts +210 -0
  227. package/src/lint/rules/flat-declarations.ts +70 -0
  228. package/src/lint/rules/index.ts +99 -0
  229. package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
  230. package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
  231. package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
  232. package/src/lint/rules/no-redundant-type-import.ts +85 -0
  233. package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
  234. package/src/lint/rules/no-redundant-value-cast.ts +46 -0
  235. package/src/lint/rules/no-string-ref.test.ts +100 -0
  236. package/src/lint/rules/no-string-ref.ts +66 -0
  237. package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
  238. package/src/lint/rules/no-unused-declarable-import.ts +103 -0
  239. package/src/lint/rules/no-unused-declarable.test.ts +134 -0
  240. package/src/lint/rules/no-unused-declarable.ts +118 -0
  241. package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
  242. package/src/lint/rules/prefer-namespace-import.ts +63 -0
  243. package/src/lint/rules/single-concern-file.test.ts +156 -0
  244. package/src/lint/rules/single-concern-file.ts +98 -0
  245. package/src/lint/rules/stale-barrel-types.ts +60 -0
  246. package/src/lint/selectors.test.ts +113 -0
  247. package/src/lint/selectors.ts +188 -0
  248. package/src/lsp/lexicon-providers.ts +191 -0
  249. package/src/lsp/types.ts +79 -0
  250. package/src/mcp/types.ts +22 -0
  251. package/src/project/scan.test.ts +178 -0
  252. package/src/project/scan.ts +182 -0
  253. package/src/project/sync.test.ts +87 -0
  254. package/src/project/sync.ts +46 -0
  255. package/src/project-validation.test.ts +64 -0
  256. package/src/project-validation.ts +79 -0
  257. package/src/pseudo-parameter.test.ts +39 -0
  258. package/src/pseudo-parameter.ts +47 -0
  259. package/src/runtime.ts +68 -0
  260. package/src/serializer-walker.test.ts +124 -0
  261. package/src/serializer-walker.ts +83 -0
  262. package/src/serializer.ts +42 -0
  263. package/src/sort.test.ts +290 -0
  264. package/src/sort.ts +58 -0
  265. package/src/stack-output.ts +82 -0
  266. package/src/types.test.ts +307 -0
  267. package/src/types.ts +46 -0
  268. package/src/utils.test.ts +195 -0
  269. package/src/utils.ts +46 -0
  270. package/src/validation.test.ts +308 -0
  271. package/src/validation.ts +50 -0
@@ -0,0 +1,187 @@
1
+ import * as ts from "typescript";
2
+ import type { LintRule, LintContext, LintDiagnostic, LintFix, Severity, Category } from "./rule";
3
+ import { resolveSelector } from "./selectors";
4
+ import { getNamedCheck } from "./named-checks";
5
+
6
+ /**
7
+ * Condition for matching nodes within a selector result.
8
+ */
9
+ export interface MatchCondition {
10
+ /** RegExp pattern to match the node's text */
11
+ pattern?: RegExp;
12
+ /** Selector name — only match if node is within a parent matching this selector */
13
+ within?: string;
14
+ /** Require specific child selector(s) to be present */
15
+ require?: string | string[];
16
+ /** Named check to evaluate */
17
+ check?: string;
18
+ }
19
+
20
+ /**
21
+ * Declarative fix description.
22
+ */
23
+ export interface DeclarativeFix {
24
+ /** Kind of fix operation */
25
+ kind: "replace" | "insert-before" | "insert-after" | "delete";
26
+ /** Static replacement text */
27
+ text?: string;
28
+ /** Dynamic resolve function for the replacement text */
29
+ resolve?: (node: ts.Node, sf: ts.SourceFile) => string;
30
+ }
31
+
32
+ /**
33
+ * Specification for a declarative lint rule.
34
+ */
35
+ export interface RuleSpec {
36
+ /** Unique rule ID */
37
+ id: string;
38
+ /** Severity level */
39
+ severity: Severity;
40
+ /** Category for grouping */
41
+ category: Category;
42
+ /** Selector name (or compound) to find target nodes */
43
+ selector: string;
44
+ /** Optional match condition to further filter nodes */
45
+ match?: MatchCondition;
46
+ /** Message template. `{node}` is replaced with the node's text. */
47
+ message: string;
48
+ /** Optional fix to apply */
49
+ fix?: DeclarativeFix;
50
+ /** Optional custom validation function */
51
+ validate?: (node: ts.Node, context: LintContext) => boolean;
52
+ }
53
+
54
+ /**
55
+ * Build a standard LintRule from a declarative RuleSpec.
56
+ */
57
+ export function rule(spec: RuleSpec): LintRule {
58
+ const selectorFn = resolveSelector(spec.selector);
59
+
60
+ return {
61
+ id: spec.id,
62
+ severity: spec.severity,
63
+ category: spec.category,
64
+ check(context: LintContext): LintDiagnostic[] {
65
+ const diagnostics: LintDiagnostic[] = [];
66
+ const sf = context.sourceFile;
67
+ let nodes = selectorFn(sf);
68
+
69
+ // Apply match conditions
70
+ if (spec.match) {
71
+ nodes = nodes.filter((node) => matchNode(node, spec.match!, sf, context));
72
+ }
73
+
74
+ // Apply custom validation
75
+ if (spec.validate) {
76
+ nodes = nodes.filter((node) => spec.validate!(node, context));
77
+ }
78
+
79
+ for (const node of nodes) {
80
+ const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
81
+ const nodeText = node.getText(sf);
82
+ const message = spec.message.replace(/\{node\}/g, nodeText);
83
+
84
+ const diagnostic: LintDiagnostic = {
85
+ file: context.filePath,
86
+ line: line + 1,
87
+ column: character + 1,
88
+ ruleId: spec.id,
89
+ severity: spec.severity,
90
+ message,
91
+ };
92
+
93
+ if (spec.fix) {
94
+ diagnostic.fix = buildFix(node, spec.fix, sf);
95
+ }
96
+
97
+ diagnostics.push(diagnostic);
98
+ }
99
+
100
+ return diagnostics;
101
+ },
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Check if a node matches the given condition.
107
+ */
108
+ function matchNode(
109
+ node: ts.Node,
110
+ condition: MatchCondition,
111
+ sf: ts.SourceFile,
112
+ context: LintContext,
113
+ ): boolean {
114
+ // Pattern match
115
+ if (condition.pattern) {
116
+ const text = node.getText(sf);
117
+ if (!condition.pattern.test(text)) return false;
118
+ }
119
+
120
+ // Within match — node must be inside a parent matching the selector
121
+ if (condition.within) {
122
+ const parentSelector = resolveSelector(condition.within);
123
+ const parents = parentSelector(sf);
124
+ const isWithin = parents.some((parent) => isAncestorOf(parent, node));
125
+ if (!isWithin) return false;
126
+ }
127
+
128
+ // Require match — node must contain children matching the selector(s)
129
+ if (condition.require) {
130
+ const reqs = Array.isArray(condition.require) ? condition.require : [condition.require];
131
+ for (const req of reqs) {
132
+ const childSelector = resolveSelector(req);
133
+ const children = childSelector(sf);
134
+ const hasChild = children.some((child) => isAncestorOf(node, child));
135
+ if (!hasChild) return false;
136
+ }
137
+ }
138
+
139
+ // Named check
140
+ if (condition.check) {
141
+ const checkFn = getNamedCheck(condition.check);
142
+ if (!checkFn) {
143
+ throw new Error(`Unknown named check: "${condition.check}"`);
144
+ }
145
+ if (!checkFn(node, context)) return false;
146
+ }
147
+
148
+ return true;
149
+ }
150
+
151
+ /**
152
+ * Check if `ancestor` is an ancestor of `node`.
153
+ */
154
+ function isAncestorOf(ancestor: ts.Node, node: ts.Node): boolean {
155
+ let current = node.parent;
156
+ while (current) {
157
+ if (current === ancestor) return true;
158
+ current = current.parent;
159
+ }
160
+ return false;
161
+ }
162
+
163
+ /**
164
+ * Build a LintFix from a declarative fix spec.
165
+ */
166
+ function buildFix(node: ts.Node, fix: DeclarativeFix, sf: ts.SourceFile): LintFix {
167
+ const start = node.getStart(sf);
168
+ const end = node.getEnd();
169
+
170
+ switch (fix.kind) {
171
+ case "replace": {
172
+ const text = fix.resolve ? fix.resolve(node, sf) : (fix.text ?? "");
173
+ return { range: [start, end], replacement: text, kind: "replace" };
174
+ }
175
+ case "insert-before": {
176
+ const text = fix.resolve ? fix.resolve(node, sf) : (fix.text ?? "");
177
+ return { range: [start, start], replacement: text, kind: "insert-before" };
178
+ }
179
+ case "insert-after": {
180
+ const text = fix.resolve ? fix.resolve(node, sf) : (fix.text ?? "");
181
+ return { range: [end, end], replacement: text, kind: "insert-after" };
182
+ }
183
+ case "delete": {
184
+ return { range: [start, end], replacement: "", kind: "delete" };
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,465 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { runLint } from "./engine";
3
+ import type { LintRule, LintContext, LintDiagnostic } from "./rule";
4
+ import { withTestDir } from "@intentius/chant-test-utils";
5
+ import { writeFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+
8
+ describe("runLint", () => {
9
+ test("executes rules and returns diagnostics", async () => {
10
+ await withTestDir(async (testDir) => {
11
+ const testFile = join(testDir, "test.ts");
12
+ await writeFile(testFile, "const x = 1;");
13
+
14
+ const mockRule: LintRule = {
15
+ id: "test-rule",
16
+ severity: "error",
17
+ category: "correctness",
18
+ check: (context: LintContext): LintDiagnostic[] => {
19
+ return [
20
+ {
21
+ file: context.filePath,
22
+ line: 1,
23
+ column: 1,
24
+ ruleId: "test-rule",
25
+ severity: "error",
26
+ message: "Test error",
27
+ },
28
+ ];
29
+ },
30
+ };
31
+
32
+ const diagnostics = await runLint([testFile], [mockRule]);
33
+
34
+ expect(diagnostics).toHaveLength(1);
35
+ expect(diagnostics[0].ruleId).toBe("test-rule");
36
+ expect(diagnostics[0].message).toBe("Test error");
37
+ });
38
+ });
39
+
40
+ test("supports file-level disable comment for all rules", async () => {
41
+ await withTestDir(async (testDir) => {
42
+ const testFile = join(testDir, "test.ts");
43
+ await writeFile(
44
+ testFile,
45
+ `// chant-disable
46
+ const x = 1;`
47
+ );
48
+
49
+ const mockRule: LintRule = {
50
+ id: "test-rule",
51
+ severity: "error",
52
+ category: "correctness",
53
+ check: (context: LintContext): LintDiagnostic[] => {
54
+ return [
55
+ {
56
+ file: context.filePath,
57
+ line: 2,
58
+ column: 1,
59
+ ruleId: "test-rule",
60
+ severity: "error",
61
+ message: "Test error",
62
+ },
63
+ ];
64
+ },
65
+ };
66
+
67
+ const diagnostics = await runLint([testFile], [mockRule]);
68
+
69
+ expect(diagnostics).toHaveLength(0);
70
+ });
71
+ });
72
+
73
+ test("supports file-level disable comment for specific rules", async () => {
74
+ await withTestDir(async (testDir) => {
75
+ const testFile = join(testDir, "test.ts");
76
+ await writeFile(
77
+ testFile,
78
+ `// chant-disable test-rule-1
79
+ const x = 1;`
80
+ );
81
+
82
+ const mockRule1: LintRule = {
83
+ id: "test-rule-1",
84
+ severity: "error",
85
+ category: "correctness",
86
+ check: (context: LintContext): LintDiagnostic[] => {
87
+ return [
88
+ {
89
+ file: context.filePath,
90
+ line: 2,
91
+ column: 1,
92
+ ruleId: "test-rule-1",
93
+ severity: "error",
94
+ message: "Test error 1",
95
+ },
96
+ ];
97
+ },
98
+ };
99
+
100
+ const mockRule2: LintRule = {
101
+ id: "test-rule-2",
102
+ severity: "error",
103
+ category: "correctness",
104
+ check: (context: LintContext): LintDiagnostic[] => {
105
+ return [
106
+ {
107
+ file: context.filePath,
108
+ line: 2,
109
+ column: 1,
110
+ ruleId: "test-rule-2",
111
+ severity: "error",
112
+ message: "Test error 2",
113
+ },
114
+ ];
115
+ },
116
+ };
117
+
118
+ const diagnostics = await runLint([testFile], [mockRule1, mockRule2]);
119
+
120
+ expect(diagnostics).toHaveLength(1);
121
+ expect(diagnostics[0].ruleId).toBe("test-rule-2");
122
+ });
123
+ });
124
+
125
+ test("supports disable-line comment", async () => {
126
+ await withTestDir(async (testDir) => {
127
+ const testFile = join(testDir, "test.ts");
128
+ await writeFile(
129
+ testFile,
130
+ `const x = 1; // chant-disable-line
131
+ const y = 2;`
132
+ );
133
+
134
+ const mockRule: LintRule = {
135
+ id: "test-rule",
136
+ severity: "error",
137
+ category: "correctness",
138
+ check: (context: LintContext): LintDiagnostic[] => {
139
+ return [
140
+ {
141
+ file: context.filePath,
142
+ line: 1,
143
+ column: 1,
144
+ ruleId: "test-rule",
145
+ severity: "error",
146
+ message: "Test error line 1",
147
+ },
148
+ {
149
+ file: context.filePath,
150
+ line: 2,
151
+ column: 1,
152
+ ruleId: "test-rule",
153
+ severity: "error",
154
+ message: "Test error line 2",
155
+ },
156
+ ];
157
+ },
158
+ };
159
+
160
+ const diagnostics = await runLint([testFile], [mockRule]);
161
+
162
+ expect(diagnostics).toHaveLength(1);
163
+ expect(diagnostics[0].line).toBe(2);
164
+ });
165
+ });
166
+
167
+ test("supports disable-next-line comment", async () => {
168
+ await withTestDir(async (testDir) => {
169
+ const testFile = join(testDir, "test.ts");
170
+ await writeFile(
171
+ testFile,
172
+ `// chant-disable-next-line
173
+ const x = 1;
174
+ const y = 2;`
175
+ );
176
+
177
+ const mockRule: LintRule = {
178
+ id: "test-rule",
179
+ severity: "error",
180
+ category: "correctness",
181
+ check: (context: LintContext): LintDiagnostic[] => {
182
+ return [
183
+ {
184
+ file: context.filePath,
185
+ line: 2,
186
+ column: 1,
187
+ ruleId: "test-rule",
188
+ severity: "error",
189
+ message: "Test error line 2",
190
+ },
191
+ {
192
+ file: context.filePath,
193
+ line: 3,
194
+ column: 1,
195
+ ruleId: "test-rule",
196
+ severity: "error",
197
+ message: "Test error line 3",
198
+ },
199
+ ];
200
+ },
201
+ };
202
+
203
+ const diagnostics = await runLint([testFile], [mockRule]);
204
+
205
+ expect(diagnostics).toHaveLength(1);
206
+ expect(diagnostics[0].line).toBe(3);
207
+ });
208
+ });
209
+
210
+ test("supports disable-line with specific rule", async () => {
211
+ await withTestDir(async (testDir) => {
212
+ const testFile = join(testDir, "test.ts");
213
+ await writeFile(testFile, `const x = 1; // chant-disable-line test-rule-1`);
214
+
215
+ const mockRule1: LintRule = {
216
+ id: "test-rule-1",
217
+ severity: "error",
218
+ category: "correctness",
219
+ check: (context: LintContext): LintDiagnostic[] => {
220
+ return [
221
+ {
222
+ file: context.filePath,
223
+ line: 1,
224
+ column: 1,
225
+ ruleId: "test-rule-1",
226
+ severity: "error",
227
+ message: "Test error 1",
228
+ },
229
+ ];
230
+ },
231
+ };
232
+
233
+ const mockRule2: LintRule = {
234
+ id: "test-rule-2",
235
+ severity: "error",
236
+ category: "correctness",
237
+ check: (context: LintContext): LintDiagnostic[] => {
238
+ return [
239
+ {
240
+ file: context.filePath,
241
+ line: 1,
242
+ column: 1,
243
+ ruleId: "test-rule-2",
244
+ severity: "error",
245
+ message: "Test error 2",
246
+ },
247
+ ];
248
+ },
249
+ };
250
+
251
+ const diagnostics = await runLint([testFile], [mockRule1, mockRule2]);
252
+
253
+ expect(diagnostics).toHaveLength(1);
254
+ expect(diagnostics[0].ruleId).toBe("test-rule-2");
255
+ });
256
+ });
257
+
258
+ test("ignores non-existent rule IDs in disable comments", async () => {
259
+ await withTestDir(async (testDir) => {
260
+ const testFile = join(testDir, "test.ts");
261
+ await writeFile(
262
+ testFile,
263
+ `// chant-disable non-existent-rule
264
+ const x = 1;`
265
+ );
266
+
267
+ const mockRule: LintRule = {
268
+ id: "test-rule",
269
+ severity: "error",
270
+ category: "correctness",
271
+ check: (context: LintContext): LintDiagnostic[] => {
272
+ return [
273
+ {
274
+ file: context.filePath,
275
+ line: 2,
276
+ column: 1,
277
+ ruleId: "test-rule",
278
+ severity: "error",
279
+ message: "Test error",
280
+ },
281
+ ];
282
+ },
283
+ };
284
+
285
+ const diagnostics = await runLint([testFile], [mockRule]);
286
+
287
+ expect(diagnostics).toHaveLength(1);
288
+ expect(diagnostics[0].ruleId).toBe("test-rule");
289
+ });
290
+ });
291
+
292
+ test("supports multiple disable comments for same rule", async () => {
293
+ await withTestDir(async (testDir) => {
294
+ const testFile = join(testDir, "test.ts");
295
+ await writeFile(
296
+ testFile,
297
+ `// chant-disable-next-line test-rule
298
+ const x = 1;
299
+ // chant-disable-next-line test-rule
300
+ const y = 2;
301
+ const z = 3;`
302
+ );
303
+
304
+ const mockRule: LintRule = {
305
+ id: "test-rule",
306
+ severity: "error",
307
+ category: "correctness",
308
+ check: (context: LintContext): LintDiagnostic[] => {
309
+ return [
310
+ {
311
+ file: context.filePath,
312
+ line: 2,
313
+ column: 1,
314
+ ruleId: "test-rule",
315
+ severity: "error",
316
+ message: "Test error line 2",
317
+ },
318
+ {
319
+ file: context.filePath,
320
+ line: 4,
321
+ column: 1,
322
+ ruleId: "test-rule",
323
+ severity: "error",
324
+ message: "Test error line 4",
325
+ },
326
+ {
327
+ file: context.filePath,
328
+ line: 5,
329
+ column: 1,
330
+ ruleId: "test-rule",
331
+ severity: "error",
332
+ message: "Test error line 5",
333
+ },
334
+ ];
335
+ },
336
+ };
337
+
338
+ const diagnostics = await runLint([testFile], [mockRule]);
339
+
340
+ expect(diagnostics).toHaveLength(1);
341
+ expect(diagnostics[0].line).toBe(5);
342
+ });
343
+ });
344
+
345
+ test("processes multiple files", async () => {
346
+ await withTestDir(async (testDir) => {
347
+ const testFile1 = join(testDir, "test1.ts");
348
+ const testFile2 = join(testDir, "test2.ts");
349
+ await writeFile(testFile1, "const x = 1;");
350
+ await writeFile(testFile2, "const y = 2;");
351
+
352
+ const mockRule: LintRule = {
353
+ id: "test-rule",
354
+ severity: "error",
355
+ category: "correctness",
356
+ check: (context: LintContext): LintDiagnostic[] => {
357
+ return [
358
+ {
359
+ file: context.filePath,
360
+ line: 1,
361
+ column: 1,
362
+ ruleId: "test-rule",
363
+ severity: "error",
364
+ message: "Test error",
365
+ },
366
+ ];
367
+ },
368
+ };
369
+
370
+ const diagnostics = await runLint([testFile1, testFile2], [mockRule]);
371
+
372
+ expect(diagnostics).toHaveLength(2);
373
+ expect(diagnostics.map((d) => d.file).sort()).toEqual([testFile1, testFile2].sort());
374
+ });
375
+ });
376
+
377
+ test("handles parsing errors gracefully", async () => {
378
+ await withTestDir(async (testDir) => {
379
+ const testFile = join(testDir, "invalid.ts");
380
+ await writeFile(testFile, "const x = ;");
381
+
382
+ const mockRule: LintRule = {
383
+ id: "test-rule",
384
+ severity: "error",
385
+ category: "correctness",
386
+ check: (): LintDiagnostic[] => {
387
+ return [];
388
+ },
389
+ };
390
+
391
+ const diagnostics = await runLint([testFile], [mockRule]);
392
+ expect(diagnostics).toHaveLength(0);
393
+ });
394
+ });
395
+
396
+ test("supports multiple rules with disable comment", async () => {
397
+ await withTestDir(async (testDir) => {
398
+ const testFile = join(testDir, "test.ts");
399
+ await writeFile(
400
+ testFile,
401
+ `// chant-disable test-rule-1 test-rule-2
402
+ const x = 1;`
403
+ );
404
+
405
+ const mockRule1: LintRule = {
406
+ id: "test-rule-1",
407
+ severity: "error",
408
+ category: "correctness",
409
+ check: (context: LintContext): LintDiagnostic[] => {
410
+ return [
411
+ {
412
+ file: context.filePath,
413
+ line: 2,
414
+ column: 1,
415
+ ruleId: "test-rule-1",
416
+ severity: "error",
417
+ message: "Test error 1",
418
+ },
419
+ ];
420
+ },
421
+ };
422
+
423
+ const mockRule2: LintRule = {
424
+ id: "test-rule-2",
425
+ severity: "error",
426
+ category: "correctness",
427
+ check: (context: LintContext): LintDiagnostic[] => {
428
+ return [
429
+ {
430
+ file: context.filePath,
431
+ line: 2,
432
+ column: 1,
433
+ ruleId: "test-rule-2",
434
+ severity: "error",
435
+ message: "Test error 2",
436
+ },
437
+ ];
438
+ },
439
+ };
440
+
441
+ const mockRule3: LintRule = {
442
+ id: "test-rule-3",
443
+ severity: "error",
444
+ category: "correctness",
445
+ check: (context: LintContext): LintDiagnostic[] => {
446
+ return [
447
+ {
448
+ file: context.filePath,
449
+ line: 2,
450
+ column: 1,
451
+ ruleId: "test-rule-3",
452
+ severity: "error",
453
+ message: "Test error 3",
454
+ },
455
+ ];
456
+ },
457
+ };
458
+
459
+ const diagnostics = await runLint([testFile], [mockRule1, mockRule2, mockRule3]);
460
+
461
+ expect(diagnostics).toHaveLength(1);
462
+ expect(diagnostics[0].ruleId).toBe("test-rule-3");
463
+ });
464
+ });
465
+ });