Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Agents

<!-- https://agents.md -->

## Markdown Code Guide

- Markdown should be formatted with Prettier.
- There should be a line break before the first list item.
- There should be a line break after headings.

## YAML Code Guide

- YML files should begin with --- on the first line.
- YML should be formatted with Prettier.

## Communication (MANDATORY)

- No apologies - State facts and solutions directly.
- Concise style - Professional, avoid repetition and filler.
- Single chunk edits - All file edits in one operation.
- Real file links only - No placeholder files.
- No unnecessary confirmations - Use available context.

## Quality & Validation (MANDATORY)

- Never assume commands worked without verification.
- 98%+ confidence threshold for definitive claims.
- Immediate re-investigation when findings don't match expectations.
- Cross-tool validation when tools fail.

## Code Standards (MANDATORY)

- No emojis in code or documentation.
- Only implement what's requested.
- Preserve existing structures - Don't remove unrelated code.
89 changes: 87 additions & 2 deletions dist/core/core.js

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions dist/core/reporter.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 113 additions & 2 deletions src/core/core.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import HTMLParser from './htmlparser'
import Reporter from './reporter'
import * as HTMLRules from './rules'
import { Hint, Rule, Ruleset } from './types'
import { Hint, Rule, Ruleset, DisabledRulesMap } from './types'

export interface FormatOptions {
colors?: boolean
Expand Down Expand Up @@ -58,8 +58,11 @@
}
)

// Parse disable/enable comments
const disabledRulesMap = this.parseDisableComments(html)

const parser = new HTMLParser()
const reporter = new Reporter(html, ruleset)
const reporter = new Reporter(html, ruleset, disabledRulesMap)

const rules = this.rules
let rule: Rule
Expand All @@ -76,6 +79,114 @@
return reporter.messages
}

private parseDisableComments(html: string): DisabledRulesMap {
const disabledRulesMap: DisabledRulesMap = {}
const lines = html.split(/\r?\n/)
const regComment =
/<!--\s*htmlhint-(disable|enable)(?:-next-line)?(?:\s+([^\r\n]+?))?\s*-->/gi

// Find all disable/enable comments and their positions
const comments: Array<{
line: number
command: string
isNextLine: boolean
rulesStr?: string
}> = []

let match: RegExpExecArray | null
while ((match = regComment.exec(html)) !== null) {
// Calculate line number from match position
const beforeMatch = html.substring(0, match.index)
const lineNumber = beforeMatch.split(/\r?\n/).length
const command = match[1].toLowerCase()
const isNextLine = match[0].includes('-next-line')
const rulesStr = match[2]?.trim()

comments.push({
line: lineNumber,
command,
isNextLine,
rulesStr,
})
}

// Process comments in order
let currentDisabledRules: Set<string> | null = null
let isAllDisabled = false

for (let i = 0; i < lines.length; i++) {
const line = i + 1

// Check if there's a comment on this line
const commentOnLine = comments.find((c) => c.line === line)
if (commentOnLine) {
if (commentOnLine.command === 'disable') {
if (commentOnLine.isNextLine) {
// htmlhint-disable-next-line
const nextLine = line + 1
if (commentOnLine.rulesStr) {
// Specific rules disabled
const rules = commentOnLine.rulesStr
.split(/\s+/)
.filter((r) => r.length > 0)
if (!disabledRulesMap[nextLine]) {
disabledRulesMap[nextLine] = {}
}
if (!disabledRulesMap[nextLine].rules) {
disabledRulesMap[nextLine].rules = new Set()
}
rules.forEach((r) => disabledRulesMap[nextLine].rules!.add(r))
} else {
// All rules disabled
if (!disabledRulesMap[nextLine]) {
disabledRulesMap[nextLine] = {}
}
disabledRulesMap[nextLine].all = true
}
} else {
// htmlhint-disable
if (commentOnLine.rulesStr) {
// Specific rules disabled
const rules = commentOnLine.rulesStr
.split(/\s+/)
.filter((r) => r.length > 0)
currentDisabledRules = new Set(rules)
isAllDisabled = false
} else {
// All rules disabled
currentDisabledRules = null
isAllDisabled = true
}
}
} else if (commentOnLine.command === 'enable') {
// htmlhint-enable
currentDisabledRules = null
isAllDisabled = false
}
}

// Apply current disable state to this line (if not already set by next-line)
if (currentDisabledRules !== null || isAllDisabled) {
if (!disabledRulesMap[line]) {
disabledRulesMap[line] = {}
}
// Don't override if already set by next-line comment
if (isAllDisabled && disabledRulesMap[line].all !== true) {
disabledRulesMap[line].all = true
} else if (currentDisabledRules) {
if (!disabledRulesMap[line].rules) {
disabledRulesMap[line].rules = new Set()
}
currentDisabledRules.forEach((r) =>
disabledRulesMap[line].rules!.add(r)
)
}
}
}

return disabledRulesMap
}

public format(arrMessages: Hint[], options: FormatOptions = {}) {
const arrLogs: string[] = []
const colors = {
Expand Down
23 changes: 21 additions & 2 deletions src/core/reporter.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { Hint, ReportType, Rule, Ruleset } from './types'
import { Hint, ReportType, Rule, Ruleset, DisabledRulesMap } from './types'

export default class Reporter {
public html: string
public lines: string[]
public brLen: number
public ruleset: Ruleset
public messages: Hint[]
private disabledRulesMap: DisabledRulesMap

public constructor(html: string, ruleset: Ruleset) {
public constructor(
html: string,
ruleset: Ruleset,
disabledRulesMap: DisabledRulesMap = {}
) {
this.html = html
this.lines = html.split(/\r?\n/)
const match = /\r?\n/.exec(html)

this.brLen = match !== null ? match[0].length : 0
this.ruleset = ruleset
this.messages = []
this.disabledRulesMap = disabledRulesMap
}

public info(
Expand Down Expand Up @@ -55,6 +61,19 @@ export default class Reporter {
rule: Rule,
raw: string
) {
// Check if rule is disabled for this line
const lineDisabled = this.disabledRulesMap[line]
if (lineDisabled) {
if (lineDisabled.all === true) {
// All rules disabled for this line
return
}
if (lineDisabled.rules && lineDisabled.rules.has(rule.id)) {
// This specific rule is disabled for this line
return
}
}

const lines = this.lines
const brLen = this.brLen
let evidence = ''
Expand Down
7 changes: 7 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,10 @@ export interface Hint {
col: number
rule: Rule
}

export interface DisabledRulesMap {
[line: number]: {
all?: boolean
rules?: Set<string>
}
}
Loading