@herb-tools/linter 0.4.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.
- package/README.md +34 -0
- package/bin/herb-lint +3 -0
- package/dist/herb-lint.js +16505 -0
- package/dist/herb-lint.js.map +1 -0
- package/dist/index.cjs +834 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +820 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +49 -0
- package/dist/src/cli/argument-parser.js +96 -0
- package/dist/src/cli/argument-parser.js.map +1 -0
- package/dist/src/cli/file-processor.js +58 -0
- package/dist/src/cli/file-processor.js.map +1 -0
- package/dist/src/cli/formatters/base-formatter.js +3 -0
- package/dist/src/cli/formatters/base-formatter.js.map +1 -0
- package/dist/src/cli/formatters/detailed-formatter.js +62 -0
- package/dist/src/cli/formatters/detailed-formatter.js.map +1 -0
- package/dist/src/cli/formatters/index.js +4 -0
- package/dist/src/cli/formatters/index.js.map +1 -0
- package/dist/src/cli/formatters/simple-formatter.js +31 -0
- package/dist/src/cli/formatters/simple-formatter.js.map +1 -0
- package/dist/src/cli/index.js +5 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/summary-reporter.js +96 -0
- package/dist/src/cli/summary-reporter.js.map +1 -0
- package/dist/src/cli.js +50 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/default-rules.js +31 -0
- package/dist/src/default-rules.js.map +1 -0
- package/dist/src/herb-lint.js +5 -0
- package/dist/src/herb-lint.js.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/linter.js +39 -0
- package/dist/src/linter.js.map +1 -0
- package/dist/src/rules/erb-no-empty-tags.js +23 -0
- package/dist/src/rules/erb-no-empty-tags.js.map +1 -0
- package/dist/src/rules/erb-no-output-control-flow.js +47 -0
- package/dist/src/rules/erb-no-output-control-flow.js.map +1 -0
- package/dist/src/rules/erb-require-whitespace-inside-tags.js +43 -0
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -0
- package/dist/src/rules/html-anchor-require-href.js +25 -0
- package/dist/src/rules/html-anchor-require-href.js.map +1 -0
- package/dist/src/rules/html-aria-role-heading-requires-level.js +26 -0
- package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -0
- package/dist/src/rules/html-attribute-double-quotes.js +21 -0
- package/dist/src/rules/html-attribute-double-quotes.js.map +1 -0
- package/dist/src/rules/html-attribute-values-require-quotes.js +22 -0
- package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -0
- package/dist/src/rules/html-boolean-attributes-no-value.js +19 -0
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -0
- package/dist/src/rules/html-img-require-alt.js +29 -0
- package/dist/src/rules/html-img-require-alt.js.map +1 -0
- package/dist/src/rules/html-no-block-inside-inline.js +59 -0
- package/dist/src/rules/html-no-block-inside-inline.js.map +1 -0
- package/dist/src/rules/html-no-duplicate-attributes.js +43 -0
- package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -0
- package/dist/src/rules/html-no-empty-headings.js +148 -0
- package/dist/src/rules/html-no-empty-headings.js.map +1 -0
- package/dist/src/rules/html-no-nested-links.js +45 -0
- package/dist/src/rules/html-no-nested-links.js.map +1 -0
- package/dist/src/rules/html-tag-name-lowercase.js +39 -0
- package/dist/src/rules/html-tag-name-lowercase.js.map +1 -0
- package/dist/src/rules/index.js +13 -0
- package/dist/src/rules/index.js.map +1 -0
- package/dist/src/rules/rule-utils.js +198 -0
- package/dist/src/rules/rule-utils.js.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/cli/argument-parser.d.ts +14 -0
- package/dist/types/cli/file-processor.d.ts +21 -0
- package/dist/types/cli/formatters/base-formatter.d.ts +6 -0
- package/dist/types/cli/formatters/detailed-formatter.d.ts +13 -0
- package/dist/types/cli/formatters/index.d.ts +3 -0
- package/dist/types/cli/formatters/simple-formatter.d.ts +7 -0
- package/dist/types/cli/summary-reporter.d.ts +22 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/default-rules.d.ts +2 -0
- package/dist/types/herb-lint.d.ts +2 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/linter.d.ts +18 -0
- package/dist/types/rules/erb-no-empty-tags.d.ts +6 -0
- package/dist/types/rules/erb-no-output-control-flow.d.ts +6 -0
- package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +6 -0
- package/dist/types/rules/html-anchor-require-href.d.ts +6 -0
- package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +6 -0
- package/dist/types/rules/html-attribute-double-quotes.d.ts +6 -0
- package/dist/types/rules/html-attribute-values-require-quotes.d.ts +6 -0
- package/dist/types/rules/html-boolean-attributes-no-value.d.ts +6 -0
- package/dist/types/rules/html-img-require-alt.d.ts +6 -0
- package/dist/types/rules/html-no-block-inside-inline.d.ts +6 -0
- package/dist/types/rules/html-no-duplicate-attributes.d.ts +6 -0
- package/dist/types/rules/html-no-empty-headings.d.ts +6 -0
- package/dist/types/rules/html-no-nested-links.d.ts +6 -0
- package/dist/types/rules/html-tag-name-lowercase.d.ts +6 -0
- package/dist/types/rules/index.d.ts +12 -0
- package/dist/types/rules/rule-utils.d.ts +89 -0
- package/dist/types/src/cli/argument-parser.d.ts +14 -0
- package/dist/types/src/cli/file-processor.d.ts +21 -0
- package/dist/types/src/cli/formatters/base-formatter.d.ts +6 -0
- package/dist/types/src/cli/formatters/detailed-formatter.d.ts +13 -0
- package/dist/types/src/cli/formatters/index.d.ts +3 -0
- package/dist/types/src/cli/formatters/simple-formatter.d.ts +7 -0
- package/dist/types/src/cli/index.d.ts +4 -0
- package/dist/types/src/cli/summary-reporter.d.ts +22 -0
- package/dist/types/src/cli.d.ts +6 -0
- package/dist/types/src/default-rules.d.ts +2 -0
- package/dist/types/src/herb-lint.d.ts +2 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/linter.d.ts +18 -0
- package/dist/types/src/rules/erb-no-empty-tags.d.ts +6 -0
- package/dist/types/src/rules/erb-no-output-control-flow.d.ts +6 -0
- package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +6 -0
- package/dist/types/src/rules/html-anchor-require-href.d.ts +6 -0
- package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +6 -0
- package/dist/types/src/rules/html-attribute-double-quotes.d.ts +6 -0
- package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +6 -0
- package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +6 -0
- package/dist/types/src/rules/html-img-require-alt.d.ts +6 -0
- package/dist/types/src/rules/html-no-block-inside-inline.d.ts +6 -0
- package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +6 -0
- package/dist/types/src/rules/html-no-empty-headings.d.ts +6 -0
- package/dist/types/src/rules/html-no-nested-links.d.ts +6 -0
- package/dist/types/src/rules/html-tag-name-lowercase.d.ts +6 -0
- package/dist/types/src/rules/index.d.ts +12 -0
- package/dist/types/src/rules/rule-utils.d.ts +89 -0
- package/dist/types/src/types.d.ts +26 -0
- package/dist/types/types.d.ts +26 -0
- package/docs/rules/README.md +39 -0
- package/docs/rules/erb-no-empty-tags.md +38 -0
- package/docs/rules/erb-no-output-control-flow.md +45 -0
- package/docs/rules/erb-require-whitespace-inside-tags.md +43 -0
- package/docs/rules/html-anchor-require-href.md +32 -0
- package/docs/rules/html-aria-role-heading-requires-level.md +34 -0
- package/docs/rules/html-attribute-double-quotes.md +43 -0
- package/docs/rules/html-attribute-values-require-quotes.md +43 -0
- package/docs/rules/html-boolean-attributes-no-value.md +39 -0
- package/docs/rules/html-img-require-alt.md +44 -0
- package/docs/rules/html-no-block-inside-inline.md +66 -0
- package/docs/rules/html-no-duplicate-attributes.md +35 -0
- package/docs/rules/html-no-empty-headings.md +78 -0
- package/docs/rules/html-no-nested-links.md +44 -0
- package/docs/rules/html-tag-name-lowercase.md +44 -0
- package/package.json +49 -0
- package/src/cli/argument-parser.ts +125 -0
- package/src/cli/file-processor.ts +86 -0
- package/src/cli/formatters/base-formatter.ts +11 -0
- package/src/cli/formatters/detailed-formatter.ts +74 -0
- package/src/cli/formatters/index.ts +3 -0
- package/src/cli/formatters/simple-formatter.ts +40 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/summary-reporter.ts +127 -0
- package/src/cli.ts +60 -0
- package/src/default-rules.ts +33 -0
- package/src/herb-lint.ts +6 -0
- package/src/index.ts +3 -0
- package/src/linter.ts +50 -0
- package/src/rules/erb-no-empty-tags.ts +34 -0
- package/src/rules/erb-no-output-control-flow.ts +61 -0
- package/src/rules/erb-require-whitespace-inside-tags.ts +61 -0
- package/src/rules/html-anchor-require-href.ts +39 -0
- package/src/rules/html-aria-role-heading-requires-level.ts +44 -0
- package/src/rules/html-attribute-double-quotes.ts +28 -0
- package/src/rules/html-attribute-values-require-quotes.ts +30 -0
- package/src/rules/html-boolean-attributes-no-value.ts +27 -0
- package/src/rules/html-img-require-alt.ts +42 -0
- package/src/rules/html-no-block-inside-inline.ts +84 -0
- package/src/rules/html-no-duplicate-attributes.ts +59 -0
- package/src/rules/html-no-empty-headings.ts +185 -0
- package/src/rules/html-no-nested-links.ts +65 -0
- package/src/rules/html-tag-name-lowercase.ts +50 -0
- package/src/rules/index.ts +12 -0
- package/src/rules/rule-utils.ts +257 -0
- package/src/types.ts +32 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Visitor } from "@herb-tools/core";
|
|
2
|
+
import type { HTMLAttributeNode, HTMLOpenTagNode, HTMLSelfCloseTagNode, Location } from "@herb-tools/core";
|
|
3
|
+
import type { LintOffense, LintSeverity } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Base visitor class that provides common functionality for rule visitors
|
|
6
|
+
*/
|
|
7
|
+
export declare abstract class BaseRuleVisitor extends Visitor {
|
|
8
|
+
readonly offenses: LintOffense[];
|
|
9
|
+
protected ruleName: string;
|
|
10
|
+
constructor(ruleName: string);
|
|
11
|
+
/**
|
|
12
|
+
* Helper method to create a lint offense
|
|
13
|
+
*/
|
|
14
|
+
protected createOffense(message: string, location: Location, severity?: LintSeverity): LintOffense;
|
|
15
|
+
/**
|
|
16
|
+
* Helper method to add an offense to the offenses array
|
|
17
|
+
*/
|
|
18
|
+
protected addOffense(message: string, location: Location, severity?: LintSeverity): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Gets attributes from either an HTMLOpenTagNode or HTMLSelfCloseTagNode
|
|
22
|
+
*/
|
|
23
|
+
export declare function getAttributes(node: HTMLOpenTagNode | HTMLSelfCloseTagNode): any[];
|
|
24
|
+
/**
|
|
25
|
+
* Gets the tag name from an HTML tag node (lowercased)
|
|
26
|
+
*/
|
|
27
|
+
export declare function getTagName(node: HTMLOpenTagNode | HTMLSelfCloseTagNode): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Gets the attribute name from an HTMLAttributeNode (lowercased)
|
|
30
|
+
*/
|
|
31
|
+
export declare function getAttributeName(attributeNode: HTMLAttributeNode): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Gets the attribute value content from an HTMLAttributeValueNode
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAttributeValue(attributeNode: HTMLAttributeNode): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Checks if an attribute has a value
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasAttributeValue(attributeNode: HTMLAttributeNode): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Gets the quote type used for an attribute value
|
|
42
|
+
*/
|
|
43
|
+
export declare function getAttributeValueQuoteType(attributeNode: HTMLAttributeNode): "single" | "double" | "none" | null;
|
|
44
|
+
/**
|
|
45
|
+
* Finds an attribute by name in a list of attributes
|
|
46
|
+
*/
|
|
47
|
+
export declare function findAttributeByName(attributes: any[], attributeName: string): HTMLAttributeNode | null;
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a tag has a specific attribute
|
|
50
|
+
*/
|
|
51
|
+
export declare function hasAttribute(node: HTMLOpenTagNode | HTMLSelfCloseTagNode, attributeName: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Common HTML element categorization
|
|
54
|
+
*/
|
|
55
|
+
export declare const HTML_INLINE_ELEMENTS: Set<string>;
|
|
56
|
+
export declare const HTML_BLOCK_ELEMENTS: Set<string>;
|
|
57
|
+
export declare const HTML_BOOLEAN_ATTRIBUTES: Set<string>;
|
|
58
|
+
export declare const HEADING_TAGS: Set<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Checks if an element is inline
|
|
61
|
+
*/
|
|
62
|
+
export declare function isInlineElement(tagName: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Checks if an element is block-level
|
|
65
|
+
*/
|
|
66
|
+
export declare function isBlockElement(tagName: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Checks if an attribute is a boolean attribute
|
|
69
|
+
*/
|
|
70
|
+
export declare function isBooleanAttribute(attributeName: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Abstract base class for rules that need to check individual attributes on HTML tags
|
|
73
|
+
* Eliminates duplication of visitHTMLOpenTagNode/visitHTMLSelfCloseTagNode patterns
|
|
74
|
+
* and attribute iteration logic. Provides simplified interface with extracted attribute info.
|
|
75
|
+
*/
|
|
76
|
+
export declare abstract class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
77
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void;
|
|
78
|
+
visitHTMLSelfCloseTagNode(node: HTMLSelfCloseTagNode): void;
|
|
79
|
+
private checkAttributesOnNode;
|
|
80
|
+
protected abstract checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: HTMLAttributeNode, parentNode: HTMLOpenTagNode | HTMLSelfCloseTagNode): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Checks if an attribute value is quoted
|
|
84
|
+
*/
|
|
85
|
+
export declare function isAttributeValueQuoted(attributeNode: HTMLAttributeNode): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Iterates over all attributes of a tag node, calling the callback for each attribute
|
|
88
|
+
*/
|
|
89
|
+
export declare function forEachAttribute(node: HTMLOpenTagNode | HTMLSelfCloseTagNode, callback: (attributeNode: HTMLAttributeNode) => void): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ThemeInput } from "@herb-tools/highlighter";
|
|
2
|
+
export interface ParsedArguments {
|
|
3
|
+
pattern: string;
|
|
4
|
+
formatOption: 'simple' | 'detailed';
|
|
5
|
+
showTiming: boolean;
|
|
6
|
+
theme: ThemeInput;
|
|
7
|
+
wrapLines: boolean;
|
|
8
|
+
truncateLines: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class ArgumentParser {
|
|
11
|
+
private readonly usage;
|
|
12
|
+
parse(argv: string[]): ParsedArguments;
|
|
13
|
+
private getFilePattern;
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Diagnostic } from "@herb-tools/core";
|
|
2
|
+
export interface ProcessedFile {
|
|
3
|
+
filename: string;
|
|
4
|
+
diagnostic: Diagnostic;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProcessingResult {
|
|
8
|
+
totalErrors: number;
|
|
9
|
+
totalWarnings: number;
|
|
10
|
+
filesWithIssues: number;
|
|
11
|
+
ruleCount: number;
|
|
12
|
+
allDiagnostics: ProcessedFile[];
|
|
13
|
+
ruleViolations: Map<string, {
|
|
14
|
+
count: number;
|
|
15
|
+
files: Set<string>;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare class FileProcessor {
|
|
19
|
+
private linter;
|
|
20
|
+
processFiles(files: string[]): Promise<ProcessingResult>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Diagnostic } from "@herb-tools/core";
|
|
2
|
+
import type { ProcessedFile } from "../file-processor.js";
|
|
3
|
+
export declare abstract class BaseFormatter {
|
|
4
|
+
abstract format(allDiagnostics: ProcessedFile[], isSingleFile?: boolean): Promise<void>;
|
|
5
|
+
abstract formatFile(filename: string, diagnostics: Diagnostic[]): void;
|
|
6
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ThemeInput } from "@herb-tools/highlighter";
|
|
2
|
+
import { BaseFormatter } from "./base-formatter.js";
|
|
3
|
+
import type { Diagnostic } from "@herb-tools/core";
|
|
4
|
+
import type { ProcessedFile } from "../file-processor.js";
|
|
5
|
+
export declare class DetailedFormatter extends BaseFormatter {
|
|
6
|
+
private highlighter;
|
|
7
|
+
private theme;
|
|
8
|
+
private wrapLines;
|
|
9
|
+
private truncateLines;
|
|
10
|
+
constructor(theme?: ThemeInput, wrapLines?: boolean, truncateLines?: boolean);
|
|
11
|
+
format(allDiagnostics: ProcessedFile[], isSingleFile?: boolean): Promise<void>;
|
|
12
|
+
formatFile(_filename: string, _diagnostics: Diagnostic[]): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BaseFormatter } from "./base-formatter.js";
|
|
2
|
+
import type { Diagnostic } from "@herb-tools/core";
|
|
3
|
+
import type { ProcessedFile } from "../file-processor.js";
|
|
4
|
+
export declare class SimpleFormatter extends BaseFormatter {
|
|
5
|
+
format(allDiagnostics: ProcessedFile[]): Promise<void>;
|
|
6
|
+
formatFile(filename: string, diagnostics: Diagnostic[]): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SummaryData {
|
|
2
|
+
files: string[];
|
|
3
|
+
totalErrors: number;
|
|
4
|
+
totalWarnings: number;
|
|
5
|
+
filesWithViolations: number;
|
|
6
|
+
ruleCount: number;
|
|
7
|
+
startTime: number;
|
|
8
|
+
startDate: Date;
|
|
9
|
+
showTiming: boolean;
|
|
10
|
+
ruleViolations: Map<string, {
|
|
11
|
+
count: number;
|
|
12
|
+
files: Set<string>;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare class SummaryReporter {
|
|
16
|
+
private pluralize;
|
|
17
|
+
displaySummary(data: SummaryData): void;
|
|
18
|
+
displayMostViolatedRules(ruleViolations: Map<string, {
|
|
19
|
+
count: number;
|
|
20
|
+
files: Set<string>;
|
|
21
|
+
}>, limit?: number): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RuleClass, LintResult } from "./types.js";
|
|
2
|
+
import type { DocumentNode } from "@herb-tools/core";
|
|
3
|
+
export declare class Linter {
|
|
4
|
+
private rules;
|
|
5
|
+
private offenses;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new Linter instance.
|
|
8
|
+
* @param rules - Array of rule classes (not instances) to use. If not provided, uses default rules.
|
|
9
|
+
*/
|
|
10
|
+
constructor(rules?: RuleClass[]);
|
|
11
|
+
/**
|
|
12
|
+
* Returns the default set of rule classes used by the linter.
|
|
13
|
+
* @returns Array of rule classes
|
|
14
|
+
*/
|
|
15
|
+
private getDefaultRules;
|
|
16
|
+
getRuleCount(): number;
|
|
17
|
+
lint(document: DocumentNode): LintResult;
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./erb-no-empty-tags.js";
|
|
2
|
+
export * from "./erb-no-output-control-flow.js";
|
|
3
|
+
export * from "./html-anchor-require-href.js";
|
|
4
|
+
export * from "./html-attribute-double-quotes.js";
|
|
5
|
+
export * from "./html-attribute-values-require-quotes.js";
|
|
6
|
+
export * from "./html-boolean-attributes-no-value.js";
|
|
7
|
+
export * from "./html-img-require-alt.js";
|
|
8
|
+
export * from "./html-no-block-inside-inline.js";
|
|
9
|
+
export * from "./html-no-duplicate-attributes.js";
|
|
10
|
+
export * from "./html-no-empty-headings.js";
|
|
11
|
+
export * from "./html-no-nested-links.js";
|
|
12
|
+
export * from "./html-tag-name-lowercase.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Visitor } from "@herb-tools/core";
|
|
2
|
+
import type { HTMLAttributeNode, HTMLOpenTagNode, HTMLSelfCloseTagNode, Location } from "@herb-tools/core";
|
|
3
|
+
import type { LintOffense, LintSeverity } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Base visitor class that provides common functionality for rule visitors
|
|
6
|
+
*/
|
|
7
|
+
export declare abstract class BaseRuleVisitor extends Visitor {
|
|
8
|
+
readonly offenses: LintOffense[];
|
|
9
|
+
protected ruleName: string;
|
|
10
|
+
constructor(ruleName: string);
|
|
11
|
+
/**
|
|
12
|
+
* Helper method to create a lint offense
|
|
13
|
+
*/
|
|
14
|
+
protected createOffense(message: string, location: Location, severity?: LintSeverity): LintOffense;
|
|
15
|
+
/**
|
|
16
|
+
* Helper method to add an offense to the offenses array
|
|
17
|
+
*/
|
|
18
|
+
protected addOffense(message: string, location: Location, severity?: LintSeverity): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Gets attributes from either an HTMLOpenTagNode or HTMLSelfCloseTagNode
|
|
22
|
+
*/
|
|
23
|
+
export declare function getAttributes(node: HTMLOpenTagNode | HTMLSelfCloseTagNode): any[];
|
|
24
|
+
/**
|
|
25
|
+
* Gets the tag name from an HTML tag node (lowercased)
|
|
26
|
+
*/
|
|
27
|
+
export declare function getTagName(node: HTMLOpenTagNode | HTMLSelfCloseTagNode): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Gets the attribute name from an HTMLAttributeNode (lowercased)
|
|
30
|
+
*/
|
|
31
|
+
export declare function getAttributeName(attributeNode: HTMLAttributeNode): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Gets the attribute value content from an HTMLAttributeValueNode
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAttributeValue(attributeNode: HTMLAttributeNode): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Checks if an attribute has a value
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasAttributeValue(attributeNode: HTMLAttributeNode): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Gets the quote type used for an attribute value
|
|
42
|
+
*/
|
|
43
|
+
export declare function getAttributeValueQuoteType(attributeNode: HTMLAttributeNode): "single" | "double" | "none" | null;
|
|
44
|
+
/**
|
|
45
|
+
* Finds an attribute by name in a list of attributes
|
|
46
|
+
*/
|
|
47
|
+
export declare function findAttributeByName(attributes: any[], attributeName: string): HTMLAttributeNode | null;
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a tag has a specific attribute
|
|
50
|
+
*/
|
|
51
|
+
export declare function hasAttribute(node: HTMLOpenTagNode | HTMLSelfCloseTagNode, attributeName: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Common HTML element categorization
|
|
54
|
+
*/
|
|
55
|
+
export declare const HTML_INLINE_ELEMENTS: Set<string>;
|
|
56
|
+
export declare const HTML_BLOCK_ELEMENTS: Set<string>;
|
|
57
|
+
export declare const HTML_BOOLEAN_ATTRIBUTES: Set<string>;
|
|
58
|
+
export declare const HEADING_TAGS: Set<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Checks if an element is inline
|
|
61
|
+
*/
|
|
62
|
+
export declare function isInlineElement(tagName: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Checks if an element is block-level
|
|
65
|
+
*/
|
|
66
|
+
export declare function isBlockElement(tagName: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Checks if an attribute is a boolean attribute
|
|
69
|
+
*/
|
|
70
|
+
export declare function isBooleanAttribute(attributeName: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Abstract base class for rules that need to check individual attributes on HTML tags
|
|
73
|
+
* Eliminates duplication of visitHTMLOpenTagNode/visitHTMLSelfCloseTagNode patterns
|
|
74
|
+
* and attribute iteration logic. Provides simplified interface with extracted attribute info.
|
|
75
|
+
*/
|
|
76
|
+
export declare abstract class AttributeVisitorMixin extends BaseRuleVisitor {
|
|
77
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void;
|
|
78
|
+
visitHTMLSelfCloseTagNode(node: HTMLSelfCloseTagNode): void;
|
|
79
|
+
private checkAttributesOnNode;
|
|
80
|
+
protected abstract checkAttribute(attributeName: string, attributeValue: string | null, attributeNode: HTMLAttributeNode, parentNode: HTMLOpenTagNode | HTMLSelfCloseTagNode): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Checks if an attribute value is quoted
|
|
84
|
+
*/
|
|
85
|
+
export declare function isAttributeValueQuoted(attributeNode: HTMLAttributeNode): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Iterates over all attributes of a tag node, calling the callback for each attribute
|
|
88
|
+
*/
|
|
89
|
+
export declare function forEachAttribute(node: HTMLOpenTagNode | HTMLSelfCloseTagNode, callback: (attributeNode: HTMLAttributeNode) => void): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Node, Diagnostic } from "@herb-tools/core";
|
|
2
|
+
import type { defaultRules } from "./default-rules.js";
|
|
3
|
+
export type LintSeverity = "error" | "warning";
|
|
4
|
+
/**
|
|
5
|
+
* Automatically inferred union type of all available linter rule names.
|
|
6
|
+
* This type extracts the 'name' property from each rule class instance.
|
|
7
|
+
*/
|
|
8
|
+
export type LinterRule = InstanceType<typeof defaultRules[number]>['name'];
|
|
9
|
+
export interface LintOffense extends Diagnostic {
|
|
10
|
+
rule: LinterRule;
|
|
11
|
+
severity: LintSeverity;
|
|
12
|
+
}
|
|
13
|
+
export interface LintResult {
|
|
14
|
+
offenses: LintOffense[];
|
|
15
|
+
errors: number;
|
|
16
|
+
warnings: number;
|
|
17
|
+
}
|
|
18
|
+
export interface Rule {
|
|
19
|
+
name: string;
|
|
20
|
+
check(node: Node): LintOffense[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Type representing a rule class constructor.
|
|
24
|
+
* The Linter accepts rule classes rather than instances for better performance and memory usage.
|
|
25
|
+
*/
|
|
26
|
+
export type RuleClass = new () => Rule;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Node, Diagnostic } from "@herb-tools/core";
|
|
2
|
+
import type { defaultRules } from "./default-rules.js";
|
|
3
|
+
export type LintSeverity = "error" | "warning";
|
|
4
|
+
/**
|
|
5
|
+
* Automatically inferred union type of all available linter rule names.
|
|
6
|
+
* This type extracts the 'name' property from each rule class instance.
|
|
7
|
+
*/
|
|
8
|
+
export type LinterRule = InstanceType<typeof defaultRules[number]>['name'];
|
|
9
|
+
export interface LintOffense extends Diagnostic {
|
|
10
|
+
rule: LinterRule;
|
|
11
|
+
severity: LintSeverity;
|
|
12
|
+
}
|
|
13
|
+
export interface LintResult {
|
|
14
|
+
offenses: LintOffense[];
|
|
15
|
+
errors: number;
|
|
16
|
+
warnings: number;
|
|
17
|
+
}
|
|
18
|
+
export interface Rule {
|
|
19
|
+
name: string;
|
|
20
|
+
check(node: Node): LintOffense[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Type representing a rule class constructor.
|
|
24
|
+
* The Linter accepts rule classes rather than instances for better performance and memory usage.
|
|
25
|
+
*/
|
|
26
|
+
export type RuleClass = new () => Rule;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Linter Rules
|
|
2
|
+
|
|
3
|
+
This page contains documentation for all Herb Linter rules.
|
|
4
|
+
|
|
5
|
+
## Available Rules
|
|
6
|
+
|
|
7
|
+
- [`erb-no-empty-tags`](./erb-no-empty-tags.md) - Disallow empty ERB tags
|
|
8
|
+
- [`erb-no-output-control-flow`](./erb-no-output-control-flow.md) - Prevents outputting control flow blocks
|
|
9
|
+
- [`erb-require-whitespace-inside-tags`](./erb-require-whitespace-inside-tags.md) - Requires whitespace around erb tags
|
|
10
|
+
- [`html-aria-role-heading-requires-level`](./html-aria-role-heading-requires-level.md) - Requires `aria-level` when supplying a `role`
|
|
11
|
+
- [`html-attribute-double-quotes`](./html-attribute-double-quotes.md) - Enforces double quotes for attribute values
|
|
12
|
+
- [`html-attribute-values-require-quotes`](./html-attribute-values-require-quotes.md) - Requires quotes around attribute values
|
|
13
|
+
- [`html-boolean-attributes-no-value`](./html-boolean-attributes-no-value.md) - Prevents values on boolean attributes
|
|
14
|
+
- [`html-img-require-alt`](./html-img-require-alt.md) - Requires alt attributes on img tags
|
|
15
|
+
- [`html-no-block-inside-inline`](./html-no-block-inside-inline.md) - Prevents block-level elements inside inline elements
|
|
16
|
+
- [`html-no-duplicate-attributes`](./html-no-duplicate-attributes.md) - Prevents duplicate attributes on HTML elements
|
|
17
|
+
- [`html-no-nested-links`](./html-no-nested-links.md) - Prevents nested anchor tags
|
|
18
|
+
- [`html-tag-name-lowercase`](./html-tag-name-lowercase.md) - Enforces lowercase tag names in HTML
|
|
19
|
+
|
|
20
|
+
## Contributing
|
|
21
|
+
|
|
22
|
+
To add a new linter rule you can scaffold a new rule by running:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd javascript/packages/linter
|
|
26
|
+
|
|
27
|
+
scripts/generate-rule
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The script creates the documentation, rule stub, and test stub based on the GitHub issue (requires the `linter` label and a `Rule name: [rule-name]` line).
|
|
31
|
+
|
|
32
|
+
Alternatively, you can create one manually:
|
|
33
|
+
|
|
34
|
+
1. Create the rule class implementing the `Rule` interface
|
|
35
|
+
2. Add comprehensive tests in `test/rules/`
|
|
36
|
+
3. Add documentation in `docs/rules/`
|
|
37
|
+
4. Update the main linter to include the rule by default (if appropriate)
|
|
38
|
+
|
|
39
|
+
See [`html-tag-name-lowercase.ts`](https://github.com/marcoroth/herb/blob/main/javascript/packages/linter/src/rules/html-tag-name-lowercase.ts) for an example implementation.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Linter Rule: Disallow empty ERB tags
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-empty-tags`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow ERB tags (`<% %>` or `<%= %>`) that contain no meaningful content i.e., tags that are completely empty or contain only whitespace.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Empty ERB tags serve no purpose and may confuse readers or indicate incomplete code. They clutter the template and may have been left behind accidentally after editing.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<%= user.name %>
|
|
19
|
+
|
|
20
|
+
<% if user.admin? %>
|
|
21
|
+
Admin tools
|
|
22
|
+
<% end %>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 🚫 Bad
|
|
26
|
+
|
|
27
|
+
```erb
|
|
28
|
+
<% %>
|
|
29
|
+
|
|
30
|
+
<%= %>
|
|
31
|
+
|
|
32
|
+
<%
|
|
33
|
+
%>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## References
|
|
37
|
+
|
|
38
|
+
\-
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Linter Rule: Disallow output ERB tags with control flow
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-output-control-flow`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow using output ERB tags (`<%=`) for control flow statements like `if`, `unless`, `case`, `while`, etc. Control flow should be written with regular ERB tags (`<% ... %>`), since these do not produce output directly.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Using `<%=` with control flow is typically a mistake or misunderstanding of ERB behavior. Output tags (`<%=`) are designed to render values into the HTML output, while control flow statements only affect execution and do not produce a value to render. This misuse can result in unexpected output, unnecessary blank spaces, or subtle bugs.
|
|
12
|
+
|
|
13
|
+
Reporting this as a warning can help developers catch likely mistakes while allowing flexibility for rare advanced cases.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```erb
|
|
20
|
+
<% if condition %>
|
|
21
|
+
Content here
|
|
22
|
+
<% end %>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<%= user.name %>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 🚫 Bad
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<%= if condition %>
|
|
33
|
+
Content here
|
|
34
|
+
<% end %>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```erb
|
|
38
|
+
<%= unless user.nil? %>
|
|
39
|
+
Welcome!
|
|
40
|
+
<% end %>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## References
|
|
44
|
+
|
|
45
|
+
* [Inspiration](https://x.com/specialcasedev/status/1935013470069719231)
|