Skip to content

Commit 3218808

Browse files
Add support for disabling rules via HTML comments (#1767)
* Add support for disabling rules via HTML comments Introduces parsing of htmlhint-disable and htmlhint-enable comments to selectively disable rules or all rules on specific lines. Updates the Reporter to respect these disable comments, preventing messages for disabled rules. Documentation and tests updated to reflect new functionality. * Update test/core.spec.js Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 7e70a23 commit 3218808

File tree

9 files changed

+466
-8
lines changed

9 files changed

+466
-8
lines changed

AGENTS.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Agents
2+
3+
<!-- https://agents.md -->
4+
5+
## Markdown Code Guide
6+
7+
- Markdown should be formatted with Prettier.
8+
- There should be a line break before the first list item.
9+
- There should be a line break after headings.
10+
11+
## YAML Code Guide
12+
13+
- YML files should begin with --- on the first line.
14+
- YML should be formatted with Prettier.
15+
16+
## Communication (MANDATORY)
17+
18+
- No apologies - State facts and solutions directly.
19+
- Concise style - Professional, avoid repetition and filler.
20+
- Single chunk edits - All file edits in one operation.
21+
- Real file links only - No placeholder files.
22+
- No unnecessary confirmations - Use available context.
23+
24+
## Quality & Validation (MANDATORY)
25+
26+
- Never assume commands worked without verification.
27+
- 98%+ confidence threshold for definitive claims.
28+
- Immediate re-investigation when findings don't match expectations.
29+
- Cross-tool validation when tools fail.
30+
31+
## Code Standards (MANDATORY)
32+
33+
- No emojis in code or documentation.
34+
- Only implement what's requested.
35+
- Preserve existing structures - Don't remove unrelated code.

dist/core/core.js

Lines changed: 87 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/core/reporter.js

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/core.ts

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import HTMLParser from './htmlparser'
22
import Reporter from './reporter'
33
import * as HTMLRules from './rules'
4-
import { Hint, Rule, Ruleset } from './types'
4+
import { Hint, Rule, Ruleset, DisabledRulesMap } from './types'
55

66
export interface FormatOptions {
77
colors?: boolean
@@ -58,8 +58,11 @@ class HTMLHintCore {
5858
}
5959
)
6060

61+
// Parse disable/enable comments
62+
const disabledRulesMap = this.parseDisableComments(html)
63+
6164
const parser = new HTMLParser()
62-
const reporter = new Reporter(html, ruleset)
65+
const reporter = new Reporter(html, ruleset, disabledRulesMap)
6366

6467
const rules = this.rules
6568
let rule: Rule
@@ -76,6 +79,114 @@ class HTMLHintCore {
7679
return reporter.messages
7780
}
7881

82+
private parseDisableComments(html: string): DisabledRulesMap {
83+
const disabledRulesMap: DisabledRulesMap = {}
84+
const lines = html.split(/\r?\n/)
85+
const regComment =
86+
/<!--\s*htmlhint-(disable|enable)(?:-next-line)?(?:\s+([^\r\n]+?))?\s*-->/gi
87+
88+
// Find all disable/enable comments and their positions
89+
const comments: Array<{
90+
line: number
91+
command: string
92+
isNextLine: boolean
93+
rulesStr?: string
94+
}> = []
95+
96+
let match: RegExpExecArray | null
97+
while ((match = regComment.exec(html)) !== null) {
98+
// Calculate line number from match position
99+
const beforeMatch = html.substring(0, match.index)
100+
const lineNumber = beforeMatch.split(/\r?\n/).length
101+
const command = match[1].toLowerCase()
102+
const isNextLine = match[0].includes('-next-line')
103+
const rulesStr = match[2]?.trim()
104+
105+
comments.push({
106+
line: lineNumber,
107+
command,
108+
isNextLine,
109+
rulesStr,
110+
})
111+
}
112+
113+
// Process comments in order
114+
let currentDisabledRules: Set<string> | null = null
115+
let isAllDisabled = false
116+
117+
for (let i = 0; i < lines.length; i++) {
118+
const line = i + 1
119+
120+
// Check if there's a comment on this line
121+
const commentOnLine = comments.find((c) => c.line === line)
122+
if (commentOnLine) {
123+
if (commentOnLine.command === 'disable') {
124+
if (commentOnLine.isNextLine) {
125+
// htmlhint-disable-next-line
126+
const nextLine = line + 1
127+
if (commentOnLine.rulesStr) {
128+
// Specific rules disabled
129+
const rules = commentOnLine.rulesStr
130+
.split(/\s+/)
131+
.filter((r) => r.length > 0)
132+
if (!disabledRulesMap[nextLine]) {
133+
disabledRulesMap[nextLine] = {}
134+
}
135+
if (!disabledRulesMap[nextLine].rules) {
136+
disabledRulesMap[nextLine].rules = new Set()
137+
}
138+
rules.forEach((r) => disabledRulesMap[nextLine].rules!.add(r))
139+
} else {
140+
// All rules disabled
141+
if (!disabledRulesMap[nextLine]) {
142+
disabledRulesMap[nextLine] = {}
143+
}
144+
disabledRulesMap[nextLine].all = true
145+
}
146+
} else {
147+
// htmlhint-disable
148+
if (commentOnLine.rulesStr) {
149+
// Specific rules disabled
150+
const rules = commentOnLine.rulesStr
151+
.split(/\s+/)
152+
.filter((r) => r.length > 0)
153+
currentDisabledRules = new Set(rules)
154+
isAllDisabled = false
155+
} else {
156+
// All rules disabled
157+
currentDisabledRules = null
158+
isAllDisabled = true
159+
}
160+
}
161+
} else if (commentOnLine.command === 'enable') {
162+
// htmlhint-enable
163+
currentDisabledRules = null
164+
isAllDisabled = false
165+
}
166+
}
167+
168+
// Apply current disable state to this line (if not already set by next-line)
169+
if (currentDisabledRules !== null || isAllDisabled) {
170+
if (!disabledRulesMap[line]) {
171+
disabledRulesMap[line] = {}
172+
}
173+
// Don't override if already set by next-line comment
174+
if (isAllDisabled && disabledRulesMap[line].all !== true) {
175+
disabledRulesMap[line].all = true
176+
} else if (currentDisabledRules) {
177+
if (!disabledRulesMap[line].rules) {
178+
disabledRulesMap[line].rules = new Set()
179+
}
180+
currentDisabledRules.forEach((r) =>
181+
disabledRulesMap[line].rules!.add(r)
182+
)
183+
}
184+
}
185+
}
186+
187+
return disabledRulesMap
188+
}
189+
79190
public format(arrMessages: Hint[], options: FormatOptions = {}) {
80191
const arrLogs: string[] = []
81192
const colors = {

src/core/reporter.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1-
import { Hint, ReportType, Rule, Ruleset } from './types'
1+
import { Hint, ReportType, Rule, Ruleset, DisabledRulesMap } from './types'
22

33
export default class Reporter {
44
public html: string
55
public lines: string[]
66
public brLen: number
77
public ruleset: Ruleset
88
public messages: Hint[]
9+
private disabledRulesMap: DisabledRulesMap
910

10-
public constructor(html: string, ruleset: Ruleset) {
11+
public constructor(
12+
html: string,
13+
ruleset: Ruleset,
14+
disabledRulesMap: DisabledRulesMap = {}
15+
) {
1116
this.html = html
1217
this.lines = html.split(/\r?\n/)
1318
const match = /\r?\n/.exec(html)
1419

1520
this.brLen = match !== null ? match[0].length : 0
1621
this.ruleset = ruleset
1722
this.messages = []
23+
this.disabledRulesMap = disabledRulesMap
1824
}
1925

2026
public info(
@@ -55,6 +61,19 @@ export default class Reporter {
5561
rule: Rule,
5662
raw: string
5763
) {
64+
// Check if rule is disabled for this line
65+
const lineDisabled = this.disabledRulesMap[line]
66+
if (lineDisabled) {
67+
if (lineDisabled.all === true) {
68+
// All rules disabled for this line
69+
return
70+
}
71+
if (lineDisabled.rules && lineDisabled.rules.has(rule.id)) {
72+
// This specific rule is disabled for this line
73+
return
74+
}
75+
}
76+
5877
const lines = this.lines
5978
const brLen = this.brLen
6079
let evidence = ''

src/core/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,10 @@ export interface Hint {
7979
col: number
8080
rule: Rule
8181
}
82+
83+
export interface DisabledRulesMap {
84+
[line: number]: {
85+
all?: boolean
86+
rules?: Set<string>
87+
}
88+
}

0 commit comments

Comments
 (0)