@herb-tools/linter 0.4.2 → 0.5.0

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 (202) hide show
  1. package/README.md +221 -10
  2. package/dist/herb-lint.js +817 -292
  3. package/dist/herb-lint.js.map +1 -1
  4. package/dist/index.cjs +360 -81
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +355 -83
  7. package/dist/index.js.map +1 -1
  8. package/dist/package.json +4 -4
  9. package/dist/src/cli/argument-parser.js +28 -18
  10. package/dist/src/cli/argument-parser.js.map +1 -1
  11. package/dist/src/cli/file-processor.js +21 -17
  12. package/dist/src/cli/file-processor.js.map +1 -1
  13. package/dist/src/cli/formatters/detailed-formatter.js +9 -9
  14. package/dist/src/cli/formatters/detailed-formatter.js.map +1 -1
  15. package/dist/src/cli/formatters/github-actions-formatter.js +50 -0
  16. package/dist/src/cli/formatters/github-actions-formatter.js.map +1 -0
  17. package/dist/src/cli/formatters/index.js +2 -0
  18. package/dist/src/cli/formatters/index.js.map +1 -1
  19. package/dist/src/cli/formatters/json-formatter.js +58 -0
  20. package/dist/src/cli/formatters/json-formatter.js.map +1 -0
  21. package/dist/src/cli/formatters/simple-formatter.js +15 -15
  22. package/dist/src/cli/formatters/simple-formatter.js.map +1 -1
  23. package/dist/src/cli/output-manager.js +120 -0
  24. package/dist/src/cli/output-manager.js.map +1 -0
  25. package/dist/src/cli/summary-reporter.js +22 -22
  26. package/dist/src/cli/summary-reporter.js.map +1 -1
  27. package/dist/src/cli.js +41 -26
  28. package/dist/src/cli.js.map +1 -1
  29. package/dist/src/default-rules.js +8 -0
  30. package/dist/src/default-rules.js.map +1 -1
  31. package/dist/src/linter.js +37 -6
  32. package/dist/src/linter.js.map +1 -1
  33. package/dist/src/rules/erb-no-empty-tags.js +5 -4
  34. package/dist/src/rules/erb-no-empty-tags.js.map +1 -1
  35. package/dist/src/rules/erb-no-output-control-flow.js +5 -4
  36. package/dist/src/rules/erb-no-output-control-flow.js.map +1 -1
  37. package/dist/src/rules/erb-prefer-image-tag-helper.js +93 -0
  38. package/dist/src/rules/erb-prefer-image-tag-helper.js.map +1 -0
  39. package/dist/src/rules/erb-require-whitespace-inside-tags.js +5 -4
  40. package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -1
  41. package/dist/src/rules/erb-requires-trailing-newline.js +22 -0
  42. package/dist/src/rules/erb-requires-trailing-newline.js.map +1 -0
  43. package/dist/src/rules/html-anchor-require-href.js +5 -4
  44. package/dist/src/rules/html-anchor-require-href.js.map +1 -1
  45. package/dist/src/rules/html-aria-attribute-must-be-valid.js +5 -4
  46. package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +1 -1
  47. package/dist/src/rules/html-aria-level-must-be-valid.js +27 -0
  48. package/dist/src/rules/html-aria-level-must-be-valid.js.map +1 -0
  49. package/dist/src/rules/html-aria-role-heading-requires-level.js +5 -4
  50. package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -1
  51. package/dist/src/rules/html-aria-role-must-be-valid.js +5 -4
  52. package/dist/src/rules/html-aria-role-must-be-valid.js.map +1 -1
  53. package/dist/src/rules/html-attribute-double-quotes.js +5 -4
  54. package/dist/src/rules/html-attribute-double-quotes.js.map +1 -1
  55. package/dist/src/rules/html-attribute-values-require-quotes.js +5 -4
  56. package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -1
  57. package/dist/src/rules/html-boolean-attributes-no-value.js +5 -4
  58. package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -1
  59. package/dist/src/rules/html-img-require-alt.js +5 -4
  60. package/dist/src/rules/html-img-require-alt.js.map +1 -1
  61. package/dist/src/rules/html-no-block-inside-inline.js +7 -6
  62. package/dist/src/rules/html-no-block-inside-inline.js.map +1 -1
  63. package/dist/src/rules/html-no-duplicate-attributes.js +5 -4
  64. package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -1
  65. package/dist/src/rules/html-no-duplicate-ids.js +5 -4
  66. package/dist/src/rules/html-no-duplicate-ids.js.map +1 -1
  67. package/dist/src/rules/html-no-empty-headings.js +5 -4
  68. package/dist/src/rules/html-no-empty-headings.js.map +1 -1
  69. package/dist/src/rules/html-no-nested-links.js +5 -4
  70. package/dist/src/rules/html-no-nested-links.js.map +1 -1
  71. package/dist/src/rules/html-tag-name-lowercase.js +5 -4
  72. package/dist/src/rules/html-tag-name-lowercase.js.map +1 -1
  73. package/dist/src/rules/index.js +3 -0
  74. package/dist/src/rules/index.js.map +1 -1
  75. package/dist/src/rules/parser-no-errors.js +18 -0
  76. package/dist/src/rules/parser-no-errors.js.map +1 -0
  77. package/dist/src/rules/rule-utils.js +125 -2
  78. package/dist/src/rules/rule-utils.js.map +1 -1
  79. package/dist/src/rules/svg-tag-name-capitalization.js +5 -4
  80. package/dist/src/rules/svg-tag-name-capitalization.js.map +1 -1
  81. package/dist/src/types.js +15 -1
  82. package/dist/src/types.js.map +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/dist/types/cli/argument-parser.d.ts +2 -1
  85. package/dist/types/cli/file-processor.d.ts +6 -5
  86. package/dist/types/cli/formatters/base-formatter.d.ts +2 -2
  87. package/dist/types/cli/formatters/detailed-formatter.d.ts +2 -2
  88. package/dist/types/cli/formatters/github-actions-formatter.d.ts +12 -0
  89. package/dist/types/cli/formatters/index.d.ts +2 -0
  90. package/dist/types/cli/formatters/json-formatter.d.ts +42 -0
  91. package/dist/types/cli/formatters/simple-formatter.d.ts +2 -2
  92. package/dist/types/cli/output-manager.d.ts +31 -0
  93. package/dist/types/cli/summary-reporter.d.ts +3 -3
  94. package/dist/types/cli.d.ts +3 -1
  95. package/dist/types/linter.d.ts +20 -5
  96. package/dist/types/rules/erb-no-empty-tags.d.ts +5 -4
  97. package/dist/types/rules/erb-no-output-control-flow.d.ts +5 -4
  98. package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +7 -0
  99. package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +5 -4
  100. package/dist/types/rules/erb-requires-trailing-newline.d.ts +6 -0
  101. package/dist/types/rules/html-anchor-require-href.d.ts +5 -4
  102. package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +5 -4
  103. package/dist/types/rules/html-aria-level-must-be-valid.d.ts +7 -0
  104. package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +5 -4
  105. package/dist/types/rules/html-aria-role-must-be-valid.d.ts +5 -4
  106. package/dist/types/rules/html-attribute-double-quotes.d.ts +5 -4
  107. package/dist/types/rules/html-attribute-values-require-quotes.d.ts +5 -4
  108. package/dist/types/rules/html-boolean-attributes-no-value.d.ts +5 -4
  109. package/dist/types/rules/html-img-require-alt.d.ts +5 -4
  110. package/dist/types/rules/html-no-block-inside-inline.d.ts +5 -4
  111. package/dist/types/rules/html-no-duplicate-attributes.d.ts +5 -4
  112. package/dist/types/rules/html-no-duplicate-ids.d.ts +5 -4
  113. package/dist/types/rules/html-no-empty-headings.d.ts +5 -4
  114. package/dist/types/rules/html-no-nested-links.d.ts +5 -4
  115. package/dist/types/rules/html-tag-name-lowercase.d.ts +5 -4
  116. package/dist/types/rules/index.d.ts +3 -0
  117. package/dist/types/rules/parser-no-errors.d.ts +8 -0
  118. package/dist/types/rules/rule-utils.d.ts +73 -4
  119. package/dist/types/rules/svg-tag-name-capitalization.d.ts +5 -4
  120. package/dist/types/src/cli/argument-parser.d.ts +2 -1
  121. package/dist/types/src/cli/file-processor.d.ts +6 -5
  122. package/dist/types/src/cli/formatters/base-formatter.d.ts +2 -2
  123. package/dist/types/src/cli/formatters/detailed-formatter.d.ts +2 -2
  124. package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +12 -0
  125. package/dist/types/src/cli/formatters/index.d.ts +2 -0
  126. package/dist/types/src/cli/formatters/json-formatter.d.ts +42 -0
  127. package/dist/types/src/cli/formatters/simple-formatter.d.ts +2 -2
  128. package/dist/types/src/cli/output-manager.d.ts +31 -0
  129. package/dist/types/src/cli/summary-reporter.d.ts +3 -3
  130. package/dist/types/src/cli.d.ts +3 -1
  131. package/dist/types/src/linter.d.ts +20 -5
  132. package/dist/types/src/rules/erb-no-empty-tags.d.ts +5 -4
  133. package/dist/types/src/rules/erb-no-output-control-flow.d.ts +5 -4
  134. package/dist/types/src/rules/erb-prefer-image-tag-helper.d.ts +7 -0
  135. package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +5 -4
  136. package/dist/types/src/rules/erb-requires-trailing-newline.d.ts +6 -0
  137. package/dist/types/src/rules/html-anchor-require-href.d.ts +5 -4
  138. package/dist/types/src/rules/html-aria-attribute-must-be-valid.d.ts +5 -4
  139. package/dist/types/src/rules/html-aria-level-must-be-valid.d.ts +7 -0
  140. package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +5 -4
  141. package/dist/types/src/rules/html-aria-role-must-be-valid.d.ts +5 -4
  142. package/dist/types/src/rules/html-attribute-double-quotes.d.ts +5 -4
  143. package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +5 -4
  144. package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +5 -4
  145. package/dist/types/src/rules/html-img-require-alt.d.ts +5 -4
  146. package/dist/types/src/rules/html-no-block-inside-inline.d.ts +5 -4
  147. package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +5 -4
  148. package/dist/types/src/rules/html-no-duplicate-ids.d.ts +5 -4
  149. package/dist/types/src/rules/html-no-empty-headings.d.ts +5 -4
  150. package/dist/types/src/rules/html-no-nested-links.d.ts +5 -4
  151. package/dist/types/src/rules/html-tag-name-lowercase.d.ts +5 -4
  152. package/dist/types/src/rules/index.d.ts +3 -0
  153. package/dist/types/src/rules/parser-no-errors.d.ts +8 -0
  154. package/dist/types/src/rules/rule-utils.d.ts +73 -4
  155. package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +5 -4
  156. package/dist/types/src/types.d.ts +50 -7
  157. package/dist/types/types.d.ts +50 -7
  158. package/docs/rules/README.md +5 -1
  159. package/docs/rules/erb-prefer-image-tag-helper.md +65 -0
  160. package/docs/rules/erb-requires-trailing-newline.md +37 -0
  161. package/docs/rules/html-anchor-require-href.md +1 -1
  162. package/docs/rules/html-aria-level-must-be-valid.md +37 -0
  163. package/docs/rules/parser-no-errors.md +84 -0
  164. package/package.json +4 -4
  165. package/src/cli/argument-parser.ts +33 -19
  166. package/src/cli/file-processor.ts +27 -21
  167. package/src/cli/formatters/base-formatter.ts +2 -2
  168. package/src/cli/formatters/detailed-formatter.ts +9 -9
  169. package/src/cli/formatters/github-actions-formatter.ts +70 -0
  170. package/src/cli/formatters/index.ts +2 -0
  171. package/src/cli/formatters/json-formatter.ts +107 -0
  172. package/src/cli/formatters/simple-formatter.ts +15 -15
  173. package/src/cli/output-manager.ts +143 -0
  174. package/src/cli/summary-reporter.ts +24 -24
  175. package/src/cli.ts +48 -31
  176. package/src/default-rules.ts +8 -0
  177. package/src/linter.ts +42 -8
  178. package/src/rules/erb-no-empty-tags.ts +7 -6
  179. package/src/rules/erb-no-output-control-flow.ts +8 -6
  180. package/src/rules/erb-prefer-image-tag-helper.ts +124 -0
  181. package/src/rules/erb-require-whitespace-inside-tags.ts +7 -6
  182. package/src/rules/erb-requires-trailing-newline.ts +29 -0
  183. package/src/rules/html-anchor-require-href.ts +7 -6
  184. package/src/rules/html-aria-attribute-must-be-valid.ts +7 -7
  185. package/src/rules/html-aria-level-must-be-valid.ts +42 -0
  186. package/src/rules/html-aria-role-heading-requires-level.ts +7 -6
  187. package/src/rules/html-aria-role-must-be-valid.ts +7 -6
  188. package/src/rules/html-attribute-double-quotes.ts +7 -6
  189. package/src/rules/html-attribute-values-require-quotes.ts +7 -6
  190. package/src/rules/html-boolean-attributes-no-value.ts +7 -6
  191. package/src/rules/html-img-require-alt.ts +7 -6
  192. package/src/rules/html-no-block-inside-inline.ts +9 -8
  193. package/src/rules/html-no-duplicate-attributes.ts +7 -6
  194. package/src/rules/html-no-duplicate-ids.ts +7 -7
  195. package/src/rules/html-no-empty-headings.ts +7 -6
  196. package/src/rules/html-no-nested-links.ts +7 -6
  197. package/src/rules/html-tag-name-lowercase.ts +7 -6
  198. package/src/rules/index.ts +3 -0
  199. package/src/rules/parser-no-errors.ts +25 -0
  200. package/src/rules/rule-utils.ts +156 -4
  201. package/src/rules/svg-tag-name-capitalization.ts +7 -6
  202. package/src/types.ts +61 -7
@@ -0,0 +1,25 @@
1
+ import { ParserRule } from "../types.js"
2
+
3
+ import type { LintOffense } from "../types.js"
4
+ import type { ParseResult, HerbError } from "@herb-tools/core"
5
+
6
+ export class ParserNoErrorsRule extends ParserRule {
7
+ name = "parser-no-errors"
8
+
9
+ check(result: ParseResult): LintOffense[] {
10
+ return result.recursiveErrors().map(error =>
11
+ this.herbErrorToLintOffense(error)
12
+ )
13
+ }
14
+
15
+ private herbErrorToLintOffense(error: HerbError): LintOffense {
16
+ return {
17
+ message: `${error.message} (\`${error.type}\`)`,
18
+ location: error.location,
19
+ severity: error.severity,
20
+ rule: this.name,
21
+ code: this.name,
22
+ source: "linter"
23
+ }
24
+ }
25
+ }
@@ -1,5 +1,7 @@
1
1
  import {
2
- Visitor
2
+ Visitor,
3
+ Position,
4
+ Location
3
5
  } from "@herb-tools/core"
4
6
 
5
7
  import type {
@@ -10,9 +12,11 @@ import type {
10
12
  HTMLOpenTagNode,
11
13
  HTMLSelfCloseTagNode,
12
14
  LiteralNode,
13
- Location
15
+ LexResult,
16
+ Token
14
17
  } from "@herb-tools/core"
15
- import type { LintOffense, LintSeverity, } from "../types.js"
18
+ import type { LintOffense, LintSeverity, LintContext } from "../types.js"
19
+ import { DEFAULT_LINT_CONTEXT } from "../types.js"
16
20
 
17
21
  /**
18
22
  * Base visitor class that provides common functionality for rule visitors
@@ -20,11 +24,13 @@ import type { LintOffense, LintSeverity, } from "../types.js"
20
24
  export abstract class BaseRuleVisitor extends Visitor {
21
25
  public readonly offenses: LintOffense[] = []
22
26
  protected ruleName: string
27
+ protected context: LintContext
23
28
 
24
- constructor(ruleName: string) {
29
+ constructor(ruleName: string, context?: Partial<LintContext>) {
25
30
  super()
26
31
 
27
32
  this.ruleName = ruleName
33
+ this.context = { ...DEFAULT_LINT_CONTEXT, ...context }
28
34
  }
29
35
 
30
36
  /**
@@ -299,6 +305,22 @@ export const ARIA_ATTRIBUTES = new Set([
299
305
  'aria-valuetext',
300
306
  ])
301
307
 
308
+ /**
309
+ * Helper function to create a location at the end of the source with a 1-character range
310
+ */
311
+ export function createEndOfFileLocation(source: string): Location {
312
+ const lines = source.split('\n')
313
+ const lastLineNumber = lines.length
314
+ const lastLine = lines[lines.length - 1]
315
+ const lastColumnNumber = lastLine.length
316
+
317
+ const startColumn = lastColumnNumber > 0 ? lastColumnNumber - 1 : 0
318
+ const start = new Position(lastLineNumber, startColumn)
319
+ const end = new Position(lastLineNumber, lastColumnNumber)
320
+
321
+ return new Location(start, end)
322
+ }
323
+
302
324
  /**
303
325
  * Checks if an element is inline
304
326
  */
@@ -326,6 +348,10 @@ export function isBooleanAttribute(attributeName: string): boolean {
326
348
  * and attribute iteration logic. Provides simplified interface with extracted attribute info.
327
349
  */
328
350
  export abstract class AttributeVisitorMixin extends BaseRuleVisitor {
351
+ constructor(ruleName: string, context?: Partial<LintContext>) {
352
+ super(ruleName, context)
353
+ }
354
+
329
355
  visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
330
356
  this.checkAttributesOnNode(node)
331
357
  super.visitHTMLOpenTagNode(node)
@@ -383,3 +409,129 @@ export function forEachAttribute(
383
409
  }
384
410
  }
385
411
  }
412
+
413
+ /**
414
+ * Base lexer visitor class that provides common functionality for lexer-based rule visitors
415
+ */
416
+ export abstract class BaseLexerRuleVisitor {
417
+ public readonly offenses: LintOffense[] = []
418
+ protected ruleName: string
419
+ protected context: LintContext
420
+
421
+ constructor(ruleName: string, context?: Partial<LintContext>) {
422
+ this.ruleName = ruleName
423
+ this.context = { ...DEFAULT_LINT_CONTEXT, ...context }
424
+ }
425
+
426
+ /**
427
+ * Helper method to create a lint offense for lexer rules
428
+ */
429
+ protected createOffense(message: string, location: Location, severity: LintSeverity = "error"): LintOffense {
430
+ return {
431
+ rule: this.ruleName,
432
+ code: this.ruleName,
433
+ source: "Herb Linter",
434
+ message,
435
+ location,
436
+ severity,
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Helper method to add an offense to the offenses array
442
+ */
443
+ protected addOffense(message: string, location: Location, severity: LintSeverity = "error"): void {
444
+ this.offenses.push(this.createOffense(message, location, severity))
445
+ }
446
+
447
+ /**
448
+ * Main entry point for lexer rule visitors
449
+ * @param lexResult - The lexer result containing tokens and source
450
+ */
451
+ visit(lexResult: LexResult): void {
452
+ this.visitTokens(lexResult.value.tokens)
453
+ }
454
+
455
+ /**
456
+ * Visit all tokens
457
+ * Override this method to implement token-level checks
458
+ */
459
+ protected visitTokens(tokens: Token[]): void {
460
+ for (const token of tokens) {
461
+ this.visitToken(token)
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Visit individual tokens
467
+ * Override this method to implement per-token checks
468
+ */
469
+ protected visitToken(_token: Token): void {
470
+ // Default implementation does nothing
471
+ }
472
+
473
+ }
474
+
475
+ /**
476
+ * Base source visitor class that provides common functionality for source-based rule visitors
477
+ */
478
+ export abstract class BaseSourceRuleVisitor {
479
+ public readonly offenses: LintOffense[] = []
480
+ protected ruleName: string
481
+ protected context: LintContext
482
+
483
+ constructor(ruleName: string, context?: Partial<LintContext>) {
484
+ this.ruleName = ruleName
485
+ this.context = { ...DEFAULT_LINT_CONTEXT, ...context }
486
+ }
487
+
488
+ /**
489
+ * Helper method to create a lint offense for source rules
490
+ */
491
+ protected createOffense(message: string, location: Location, severity: LintSeverity = "error"): LintOffense {
492
+ return {
493
+ rule: this.ruleName as any, // Type assertion for compatibility
494
+ code: this.ruleName,
495
+ source: "Herb Linter",
496
+ message,
497
+ location,
498
+ severity,
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Helper method to add an offense to the offenses array
504
+ */
505
+ protected addOffense(message: string, location: Location, severity: LintSeverity = "error"): void {
506
+ this.offenses.push(this.createOffense(message, location, severity))
507
+ }
508
+
509
+ /**
510
+ * Main entry point for source rule visitors
511
+ * @param source - The raw source code
512
+ */
513
+ visit(source: string): void {
514
+ this.visitSource(source)
515
+ }
516
+
517
+ /**
518
+ * Visit the source code directly
519
+ * Override this method to implement source-level checks
520
+ */
521
+ protected abstract visitSource(source: string): void
522
+
523
+ /**
524
+ * Helper method to create a location for a specific position in the source
525
+ */
526
+ protected createLocationAt(source: string, position: number): Location {
527
+ const beforePosition = source.substring(0, position)
528
+ const lines = beforePosition.split('\n')
529
+ const line = lines.length
530
+ const column = lines[lines.length - 1].length + 1
531
+
532
+ const start = new Position(line, column)
533
+ const end = new Position(line, column)
534
+
535
+ return new Location(start, end)
536
+ }
537
+ }
@@ -1,7 +1,8 @@
1
1
  import { BaseRuleVisitor, SVG_CAMEL_CASE_ELEMENTS, SVG_LOWERCASE_TO_CAMELCASE } from "./rule-utils.js"
2
2
 
3
- import type { Rule, LintOffense } from "../types.js"
4
- import type { HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, HTMLSelfCloseTagNode, Node } from "@herb-tools/core"
3
+ import { ParserRule } from "../types.js"
4
+ import type { LintOffense, LintContext } from "../types.js"
5
+ import type { HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, HTMLSelfCloseTagNode, ParseResult } from "@herb-tools/core"
5
6
 
6
7
  class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor {
7
8
  private insideSVG = false
@@ -62,12 +63,12 @@ class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor {
62
63
  }
63
64
  }
64
65
 
65
- export class SVGTagNameCapitalizationRule implements Rule {
66
+ export class SVGTagNameCapitalizationRule extends ParserRule {
66
67
  name = "svg-tag-name-capitalization"
67
68
 
68
- check(node: Node): LintOffense[] {
69
- const visitor = new SVGTagNameCapitalizationVisitor(this.name)
70
- visitor.visit(node)
69
+ check(result: ParseResult, context?: Partial<LintContext>): LintOffense[] {
70
+ const visitor = new SVGTagNameCapitalizationVisitor(this.name, context)
71
+ visitor.visit(result.value)
71
72
  return visitor.offenses
72
73
  }
73
74
  }
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Node, Diagnostic } from "@herb-tools/core"
1
+ import { Diagnostic, LexResult, ParseResult } from "@herb-tools/core"
2
2
  import type { defaultRules } from "./default-rules.js"
3
3
 
4
- export type LintSeverity = "error" | "warning"
4
+ export type LintSeverity = "error" | "warning" | "info" | "hint"
5
5
 
6
6
  /**
7
7
  * Automatically inferred union type of all available linter rule names.
@@ -20,13 +20,67 @@ export interface LintResult {
20
20
  warnings: number
21
21
  }
22
22
 
23
- export interface Rule {
24
- name: string
25
- check(node: Node): LintOffense[]
23
+ export abstract class ParserRule {
24
+ static type = "parser" as const
25
+ abstract name: string
26
+ abstract check(result: ParseResult, context?: Partial<LintContext>): LintOffense[]
27
+ }
28
+
29
+ export abstract class LexerRule {
30
+ static type = "lexer" as const
31
+ abstract name: string
32
+ abstract check(lexResult: LexResult, context?: Partial<LintContext>): LintOffense[]
33
+ }
34
+
35
+ export interface LexerRuleConstructor {
36
+ type: "lexer"
37
+ new (): LexerRule
38
+ }
39
+
40
+ /**
41
+ * Complete lint context with all properties defined.
42
+ * Use Partial<LintContext> when passing context to rules.
43
+ */
44
+ export interface LintContext {
45
+ fileName: string | undefined
26
46
  }
27
47
 
28
48
  /**
29
- * Type representing a rule class constructor.
49
+ * Default context object with all keys defined but set to undefined
50
+ */
51
+ export const DEFAULT_LINT_CONTEXT: LintContext = {
52
+ fileName: undefined
53
+ } as const
54
+
55
+ export abstract class SourceRule {
56
+ static type = "source" as const
57
+ abstract name: string
58
+ abstract check(source: string, context?: Partial<LintContext>): LintOffense[]
59
+ }
60
+
61
+ export interface SourceRuleConstructor {
62
+ type: "source"
63
+ new (): SourceRule
64
+ }
65
+
66
+ /**
67
+ * Type representing a parser/AST rule class constructor.
30
68
  * The Linter accepts rule classes rather than instances for better performance and memory usage.
69
+ * Parser rules are the default and don't require static properties.
70
+ */
71
+ export type ParserRuleClass = (new () => ParserRule) & {
72
+ type?: "parser"
73
+ }
74
+
75
+ export type LexerRuleClass = LexerRuleConstructor
76
+ export type SourceRuleClass = SourceRuleConstructor
77
+
78
+ /**
79
+ * Union type for any rule instance (Parser/AST, Lexer, or Source)
80
+ */
81
+ export type Rule = ParserRule | LexerRule | SourceRule
82
+
83
+ /**
84
+ * Union type for any rule class (Parser/AST, Lexer, or Source)
31
85
  */
32
- export type RuleClass = new () => Rule
86
+ export type RuleClass = ParserRuleClass | LexerRuleClass | SourceRuleClass