diff --git a/src/core/core.ts b/src/core/core.ts
index 6299880f3..cd78c6cfd 100644
--- a/src/core/core.ts
+++ b/src/core/core.ts
@@ -1,7 +1,7 @@
import HTMLParser from './htmlparser'
-import Reporter from './reporter'
+import Reporter, { ReportMessageCallback } from './reporter'
import * as HTMLRules from './rules'
-import { Hint, Rule, Ruleset } from './types'
+import { Hint, isRuleSeverity, Rule, Ruleset, RuleSeverity } from './types'
export interface FormatOptions {
colors?: boolean
@@ -11,16 +11,16 @@ export interface FormatOptions {
class HTMLHintCore {
public rules: { [id: string]: Rule } = {}
public readonly defaultRuleset: Ruleset = {
- 'tagname-lowercase': true,
- 'attr-lowercase': true,
- 'attr-value-double-quotes': true,
- 'doctype-first': true,
- 'tag-pair': true,
- 'spec-char-escape': true,
- 'id-unique': true,
- 'src-not-empty': true,
- 'attr-no-duplication': true,
- 'title-require': true,
+ 'tagname-lowercase': 'error',
+ 'attr-lowercase': 'error',
+ 'attr-value-double-quotes': 'error',
+ 'doctype-first': 'error',
+ 'tag-pair': 'error',
+ 'spec-char-escape': 'error',
+ 'id-unique': 'error',
+ 'src-not-empty': 'error',
+ 'attr-no-duplication': 'error',
+ 'title-require': 'error',
}
public addRule(rule: Rule) {
@@ -37,18 +37,17 @@ class HTMLHintCore {
/^\s*/i,
(all, strRuleset: string) => {
// For example:
- // all is ''
- // strRuleset is 'alt-require:true'
+ // all is ''
+ // strRuleset is 'alt-require:warn'
strRuleset.replace(
/(?:^|,)\s*([^:,]+)\s*(?:\:\s*([^,\s]+))?/g,
(all, ruleId: string, value: string | undefined) => {
// For example:
- // all is 'alt-require:true'
+ // all is 'alt-require:warn'
// ruleId is 'alt-require'
- // value is 'true'
+ // value is 'warn'
- ruleset[ruleId] =
- value !== undefined && value.length > 0 ? JSON.parse(value) : true
+ ruleset[ruleId] = isRuleSeverity(value) ? value : 'error'
return ''
}
@@ -66,8 +65,19 @@ class HTMLHintCore {
for (const id in ruleset) {
rule = rules[id]
- if (rule !== undefined && ruleset[id] !== false) {
- rule.init(parser, reporter, ruleset[id])
+ const ruleConfig = ruleset[id]
+ const ruleSeverity: RuleSeverity = Array.isArray(ruleConfig)
+ ? ruleConfig[0]
+ : ruleConfig
+ if (rule !== undefined && ruleSeverity !== 'off') {
+ const reportMessageCallback: ReportMessageCallback = reporter[
+ ruleSeverity
+ ].bind(reporter)
+ rule.init(
+ parser,
+ reportMessageCallback,
+ Array.isArray(ruleConfig) ? ruleConfig[1] : undefined
+ )
}
}
diff --git a/src/core/reporter.ts b/src/core/reporter.ts
index de85f2976..5e7c6b110 100644
--- a/src/core/reporter.ts
+++ b/src/core/reporter.ts
@@ -1,5 +1,13 @@
import { Hint, ReportType, Rule, Ruleset } from './types'
+export type ReportMessageCallback = (
+ message: string,
+ line: number,
+ col: number,
+ rule: Rule,
+ raw: string
+) => void
+
export default class Reporter {
public html: string
public lines: string[]
diff --git a/src/core/rules/alt-require.ts b/src/core/rules/alt-require.ts
index cc51c4a9a..49716c5d1 100644
--- a/src/core/rules/alt-require.ts
+++ b/src/core/rules/alt-require.ts
@@ -4,7 +4,7 @@ export default {
id: 'alt-require',
description:
'The alt attribute of an element must be present and alt attribute of area[href] and input[type=image] must have a value.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
parser.addListener('tagstart', (event) => {
const tagName = event.tagName.toLowerCase()
const mapAttrs = parser.getMapAttrs(event.attrs)
@@ -12,7 +12,7 @@ export default {
let selector
if (tagName === 'img' && !('alt' in mapAttrs)) {
- reporter.warn(
+ reportMessageCallback(
'An alt attribute must be present on elements.',
event.line,
col,
@@ -25,7 +25,7 @@ export default {
) {
if (!('alt' in mapAttrs) || mapAttrs['alt'] === '') {
selector = tagName === 'area' ? 'area[href]' : 'input[type=image]'
- reporter.warn(
+ reportMessageCallback(
`The alt attribute of ${selector} must have a value.`,
event.line,
col,
diff --git a/src/core/rules/attr-lowercase.ts b/src/core/rules/attr-lowercase.ts
index d8fb51d28..e910ddc40 100644
--- a/src/core/rules/attr-lowercase.ts
+++ b/src/core/rules/attr-lowercase.ts
@@ -1,4 +1,4 @@
-import { Rule } from '../types'
+import { Rule, RuleConfig } from '../types'
/**
* testAgainstStringOrRegExp
@@ -42,8 +42,12 @@ function testAgainstStringOrRegExp(value: string, comparison: string | RegExp) {
export default {
id: 'attr-lowercase',
description: 'All attribute names must be in lowercase.',
- init(parser, reporter, options) {
- const exceptions = Array.isArray(options) ? options : []
+ init(
+ parser,
+ reportMessageCallback,
+ options?: { exceptions: Array }
+ ) {
+ const exceptions = options?.exceptions ?? []
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
@@ -58,7 +62,7 @@ export default {
!exceptions.find((exp) => testAgainstStringOrRegExp(attrName, exp)) &&
attrName !== attrName.toLowerCase()
) {
- reporter.error(
+ reportMessageCallback(
`The attribute name of [ ${attrName} ] must be in lowercase.`,
event.line,
col + attr.index,
diff --git a/src/core/rules/attr-no-duplication.ts b/src/core/rules/attr-no-duplication.ts
index f7b4e4d9c..8545715b1 100644
--- a/src/core/rules/attr-no-duplication.ts
+++ b/src/core/rules/attr-no-duplication.ts
@@ -3,7 +3,7 @@ import { Rule } from '../types'
export default {
id: 'attr-no-duplication',
description: 'Elements cannot have duplicate attributes.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
let attr
@@ -17,7 +17,7 @@ export default {
attrName = attr.name
if (mapAttrName[attrName] === true) {
- reporter.error(
+ reportMessageCallback(
`Duplicate of attribute name [ ${attr.name} ] was found.`,
event.line,
col + attr.index,
diff --git a/src/core/rules/attr-no-unnecessary-whitespace.ts b/src/core/rules/attr-no-unnecessary-whitespace.ts
index bacc0f2e5..82a7d3844 100644
--- a/src/core/rules/attr-no-unnecessary-whitespace.ts
+++ b/src/core/rules/attr-no-unnecessary-whitespace.ts
@@ -3,8 +3,8 @@ import { Rule } from '../types'
export default {
id: 'attr-no-unnecessary-whitespace',
description: 'No spaces between attribute names and values.',
- init(parser, reporter, options) {
- const exceptions: string[] = Array.isArray(options) ? options : []
+ init(parser, reportMessageCallback, options?: { exceptions: string[] }) {
+ const exceptions: string[] = options?.exceptions ?? []
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
@@ -14,7 +14,7 @@ export default {
if (exceptions.indexOf(attrs[i].name) === -1) {
const match = /(\s*)=(\s*)/.exec(attrs[i].raw.trim())
if (match && (match[1].length !== 0 || match[2].length !== 0)) {
- reporter.error(
+ reportMessageCallback(
`The attribute '${attrs[i].name}' must not have spaces between the name and value.`,
event.line,
col + attrs[i].index,
diff --git a/src/core/rules/attr-sorted.ts b/src/core/rules/attr-sorted.ts
index a1f85df98..c311717d0 100644
--- a/src/core/rules/attr-sorted.ts
+++ b/src/core/rules/attr-sorted.ts
@@ -3,7 +3,7 @@ import { Rule } from '../types'
export default {
id: 'attr-sorted',
description: 'Attribute tags must be in proper order.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
const orderMap: { [key: string]: number } = {}
const sortOrder = [
'class',
@@ -45,7 +45,7 @@ export default {
})
if (originalAttrs !== JSON.stringify(listOfAttributes)) {
- reporter.error(
+ reportMessageCallback(
`Inaccurate order ${originalAttrs} should be in hierarchy ${JSON.stringify(
listOfAttributes
)} `,
diff --git a/src/core/rules/attr-unsafe-chars.ts b/src/core/rules/attr-unsafe-chars.ts
index c41b3c6d6..c1df80c75 100644
--- a/src/core/rules/attr-unsafe-chars.ts
+++ b/src/core/rules/attr-unsafe-chars.ts
@@ -3,7 +3,7 @@ import { Rule } from '../types'
export default {
id: 'attr-unsafe-chars',
description: 'Attribute values cannot contain unsafe chars.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
let attr
@@ -21,7 +21,7 @@ export default {
const unsafeCode = escape(match[0])
.replace(/%u/, '\\u')
.replace(/%/, '\\x')
- reporter.warn(
+ reportMessageCallback(
`The value of attribute [ ${attr.name} ] cannot contain an unsafe char [ ${unsafeCode} ].`,
event.line,
col + attr.index,
diff --git a/src/core/rules/attr-value-double-quotes.ts b/src/core/rules/attr-value-double-quotes.ts
index 659c8f946..e6c2cef33 100644
--- a/src/core/rules/attr-value-double-quotes.ts
+++ b/src/core/rules/attr-value-double-quotes.ts
@@ -3,7 +3,7 @@ import { Rule } from '../types'
export default {
id: 'attr-value-double-quotes',
description: 'Attribute values must be in double quotes.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
let attr
@@ -16,7 +16,7 @@ export default {
(attr.value !== '' && attr.quote !== '"') ||
(attr.value === '' && attr.quote === "'")
) {
- reporter.error(
+ reportMessageCallback(
`The value of attribute [ ${attr.name} ] must be in double quotes.`,
event.line,
col + attr.index,
diff --git a/src/core/rules/attr-value-not-empty.ts b/src/core/rules/attr-value-not-empty.ts
index 65157af79..9a2735064 100644
--- a/src/core/rules/attr-value-not-empty.ts
+++ b/src/core/rules/attr-value-not-empty.ts
@@ -3,7 +3,7 @@ import { Rule } from '../types'
export default {
id: 'attr-value-not-empty',
description: 'All attributes must have values.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
let attr
@@ -13,7 +13,7 @@ export default {
attr = attrs[i]
if (attr.quote === '' && attr.value === '') {
- reporter.warn(
+ reportMessageCallback(
`The attribute [ ${attr.name} ] must have a value.`,
event.line,
col + attr.index,
diff --git a/src/core/rules/attr-value-single-quotes.ts b/src/core/rules/attr-value-single-quotes.ts
index 740c2616a..59d900734 100644
--- a/src/core/rules/attr-value-single-quotes.ts
+++ b/src/core/rules/attr-value-single-quotes.ts
@@ -3,7 +3,7 @@ import { Rule } from '../types'
export default {
id: 'attr-value-single-quotes',
description: 'Attribute values must be in single quotes.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
let attr
@@ -16,7 +16,7 @@ export default {
(attr.value !== '' && attr.quote !== "'") ||
(attr.value === '' && attr.quote === '"')
) {
- reporter.error(
+ reportMessageCallback(
`The value of attribute [ ${attr.name} ] must be in single quotes.`,
event.line,
col + attr.index,
diff --git a/src/core/rules/attr-whitespace.ts b/src/core/rules/attr-whitespace.ts
index 0a2494e80..e53b13b43 100644
--- a/src/core/rules/attr-whitespace.ts
+++ b/src/core/rules/attr-whitespace.ts
@@ -4,10 +4,8 @@ export default {
id: 'attr-whitespace',
description:
'All attributes should be separated by only one space and not have leading/trailing whitespace.',
- init(parser, reporter, options) {
- const exceptions: Array = Array.isArray(options)
- ? options
- : []
+ init(parser, reportMessageCallback, options?: { exceptions: string[] }) {
+ const exceptions: string[] = options?.exceptions ?? []
parser.addListener('tagstart', (event) => {
const attrs = event.attrs
@@ -24,7 +22,7 @@ export default {
// Check first and last characters for spaces
if (elem.value.trim() !== elem.value) {
- reporter.error(
+ reportMessageCallback(
`The attributes of [ ${attrName} ] must not have trailing whitespace.`,
event.line,
col + attr.index,
@@ -34,7 +32,7 @@ export default {
}
if (elem.value.replace(/ +(?= )/g, '') !== elem.value) {
- reporter.error(
+ reportMessageCallback(
`The attributes of [ ${attrName} ] must be separated by only one space.`,
event.line,
col + attr.index,
diff --git a/src/core/rules/doctype-first.ts b/src/core/rules/doctype-first.ts
index 1f6286181..61c847e32 100644
--- a/src/core/rules/doctype-first.ts
+++ b/src/core/rules/doctype-first.ts
@@ -4,7 +4,7 @@ import { Rule } from '../types'
export default {
id: 'doctype-first',
description: 'Doctype must be declared first.',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
const allEvent: Listener = (event) => {
if (
event.type === 'start' ||
@@ -17,7 +17,7 @@ export default {
(event.type !== 'comment' && event.long === false) ||
/^DOCTYPE\s+/i.test(event.content) === false
) {
- reporter.error(
+ reportMessageCallback(
'Doctype must be declared first.',
event.line,
event.col,
diff --git a/src/core/rules/doctype-html5.ts b/src/core/rules/doctype-html5.ts
index c6e438ddc..30c9d9fca 100644
--- a/src/core/rules/doctype-html5.ts
+++ b/src/core/rules/doctype-html5.ts
@@ -4,13 +4,13 @@ import { Rule } from '../types'
export default {
id: 'doctype-html5',
description: 'Invalid doctype. Use: ""',
- init(parser, reporter) {
+ init(parser, reportMessageCallback) {
const onComment: Listener = (event) => {
if (
event.long === false &&
event.content.toLowerCase() !== 'doctype html'
) {
- reporter.warn(
+ reportMessageCallback(
'Invalid doctype. Use: ""',
event.line,
event.col,
diff --git a/src/core/rules/head-script-disabled.ts b/src/core/rules/head-script-disabled.ts
index 5b3ab6be7..62e53327a 100644
--- a/src/core/rules/head-script-disabled.ts
+++ b/src/core/rules/head-script-disabled.ts
@@ -4,7 +4,7 @@ import { Rule } from '../types'
export default {
id: 'head-script-disabled',
description: 'The