From eba4689e0e1e0e626c9a52a4d0a92cdd27f14340 Mon Sep 17 00:00:00 2001 From: coliff Date: Thu, 5 Jun 2025 12:52:31 +0900 Subject: [PATCH 1/3] feat: Add SARIF formatter --- .cspell.json | 1 + package-lock.json | 58 +++- package.json | 1 + src/cli/formatters/sarif.sarif | 535 ++++++++++++++++++++++++++++++ src/cli/formatters/sarif.ts | 77 +++++ test/cli/formatters/sarif.sarif | 535 ++++++++++++++++++++++++++++++ test/cli/formatters/sarif.spec.js | 67 ++++ 7 files changed, 1272 insertions(+), 2 deletions(-) create mode 100644 src/cli/formatters/sarif.sarif create mode 100644 src/cli/formatters/sarif.ts create mode 100644 test/cli/formatters/sarif.sarif create mode 100644 test/cli/formatters/sarif.spec.js diff --git a/.cspell.json b/.cspell.json index 4841ed09b..b63732f29 100644 --- a/.cspell.json +++ b/.cspell.json @@ -47,6 +47,7 @@ "ruleid", "rulesdir", "ruleset", + "sarif", "selfclosing", "sometag", "specialchars", diff --git a/package-lock.json b/package-lock.json index 741e9b5d8..3f7433a6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "glob": "^8.1.0", "is-glob": "^4.0.3", "node-fetch": "^2.7.0", + "node-sarif-builder": "^2.0.3", "strip-json-comments": "3.1.1", "xml": "1.0.1" }, @@ -1949,6 +1950,12 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", @@ -3643,6 +3650,20 @@ "node": ">= 6" } }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3902,8 +3923,7 @@ "node_modules/graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, "node_modules/graphemer": { "version": "1.4.0", @@ -5132,6 +5152,18 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -5397,6 +5429,19 @@ "dev": true, "license": "MIT" }, + "node_modules/node-sarif-builder": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-2.0.3.tgz", + "integrity": "sha512-Pzr3rol8fvhG/oJjIq2NTVB0vmdNNlz22FENhhPojYRZ4/ee08CfK4YuKmuL54V9MLhI1kpzxfOJ/63LzmZzDg==", + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.4", + "fs-extra": "^10.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6462,6 +6507,15 @@ "node": ">=4.2.0" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", diff --git a/package.json b/package.json index 3c292ba0c..b28aca791 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "glob": "^8.1.0", "is-glob": "^4.0.3", "node-fetch": "^2.7.0", + "node-sarif-builder": "^2.0.3", "strip-json-comments": "3.1.1", "xml": "1.0.1" }, diff --git a/src/cli/formatters/sarif.sarif b/src/cli/formatters/sarif.sarif new file mode 100644 index 000000000..6fb9c6c51 --- /dev/null +++ b/src/cli/formatters/sarif.sarif @@ -0,0 +1,535 @@ +{ + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "HTMLHint", + "rules": [ + { + "id": "attr-value-double-quotes", + "shortDescription": { + "text": "Attribute values must be in double quotes." + }, + "helpUri": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + }, + { + "id": "attr-no-duplication", + "shortDescription": { + "text": "Elements cannot have duplicate attributes." + }, + "helpUri": "https://htmlhint.com/docs/user-guide/rules/attr-no-duplication" + }, + { + "id": "tag-pair", + "shortDescription": { + "text": "Tag must be paired." + }, + "helpUri": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + }, + { + "id": "spec-char-escape", + "shortDescription": { + "text": "Special characters must be escaped." + }, + "helpUri": "https://htmlhint.com/docs/user-guide/rules/spec-char-escape" + } + ], + "version": "1.1.4", + "informationUri": "https://htmlhint.com/" + } + }, + "results": [ + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 8, + "startColumn": 7, + "endLine": 8, + "endColumn": 7 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 8, + "startColumn": 14, + "endLine": 8, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "Duplicate of attribute name [ bad ] was found." + }, + "ruleId": "attr-no-duplication", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 8, + "startColumn": 14, + "endLine": 8, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 1 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 9, + "startColumn": 7, + "endLine": 9, + "endColumn": 7 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 9, + "startColumn": 14, + "endLine": 9, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "Duplicate of attribute name [ bad ] was found." + }, + "ruleId": "attr-no-duplication", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 9, + "startColumn": 14, + "endLine": 9, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 1 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 10, + "startColumn": 22, + "endLine": 10, + "endColumn": 22 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Special characters must be escaped : [ < ]." + }, + "ruleId": "spec-char-escape", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 11, + "startColumn": 3, + "endLine": 11, + "endColumn": 3 + } + } + } + ], + "ruleIndex": 3 + }, + { + "level": "error", + "message": { + "text": "Special characters must be escaped : [ > ]." + }, + "ruleId": "spec-char-escape", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 11, + "startColumn": 18, + "endLine": 11, + "endColumn": 18 + } + } + } + ], + "ruleIndex": 3 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 13, + "startColumn": 11, + "endLine": 13, + "endColumn": 11 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 14, + "startColumn": 9, + "endLine": 14, + "endColumn": 9 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 15, + "startColumn": 7, + "endLine": 15, + "endColumn": 7 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 16, + "startColumn": 5, + "endLine": 16, + "endColumn": 5 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 17, + "startColumn": 3, + "endLine": 17, + "endColumn": 3 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ class ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 21, + "startColumn": 15, + "endLine": 21, + "endColumn": 15 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ what ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 21, + "startColumn": 24, + "endLine": 21, + "endColumn": 24 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ something ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 21, + "startColumn": 32, + "endLine": 21, + "endColumn": 32 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 25, + "startColumn": 3, + "endLine": 25, + "endColumn": 3 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 26, + "startColumn": 1, + "endLine": 26, + "endColumn": 1 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "test/cli/formatters/example.html", + "index": 0 + }, + "region": { + "startLine": 27, + "startColumn": 1, + "endLine": 27, + "endColumn": 1 + } + } + } + ], + "ruleIndex": 2 + } + ], + "artifacts": [ + { + "sourceLanguage": "HTML", + "location": { + "uri": "test/cli/formatters/example.html" + } + } + ] + } + ] +} diff --git a/src/cli/formatters/sarif.ts b/src/cli/formatters/sarif.ts new file mode 100644 index 000000000..a8371c9cf --- /dev/null +++ b/src/cli/formatters/sarif.ts @@ -0,0 +1,77 @@ +import { FormatterCallback } from '../formatter' +import { + SarifBuilder, + SarifResultBuilder, + SarifRuleBuilder, + SarifRunBuilder, +} from 'node-sarif-builder' +import * as path from 'path' +import { pathToFileURL } from 'url' +import { Result } from 'sarif' + +const pkg = require('../../../package.json') + +const sarifFormatter: FormatterCallback = function (formatter) { + formatter.on('end', (event) => { + const arrAllMessages = event.arrAllMessages + + // SARIF builder + const sarifBuilder = new SarifBuilder() + + // SARIF Run builder + const sarifRunBuilder = new SarifRunBuilder().initSimple({ + toolDriverName: 'HTMLHint', + toolDriverVersion: pkg.version, + url: 'https://htmlhint.com/', + }) + + // SARIF rules + const addedRuleSet = new Set() + arrAllMessages.forEach((result) => { + result.messages.forEach((message) => { + const rule = message.rule + if (addedRuleSet.has(rule.id)) { + return + } + addedRuleSet.add(rule.id) + const sarifRuleBuilder = new SarifRuleBuilder().initSimple({ + ruleId: rule.id, + shortDescriptionText: rule.description, + helpUri: rule.link, + }) + sarifRunBuilder.addRule(sarifRuleBuilder) + }) + }) + + // Add SARIF results (individual errors) + arrAllMessages.forEach((result) => { + result.messages.forEach((message) => { + const sarifResultBuilder = new SarifResultBuilder() + const ruleId = message.rule.id + const sarifResultInit = { + level: + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + message.type === 'info' + ? 'note' + : (message.type.toString() as Result.level), + messageText: message.message, + ruleId: ruleId, + fileUri: path + .relative(process.cwd(), result.file) + .replace(/\\/g, '/'), + startLine: message.line, + startColumn: message.col, + endLine: message.line, + endColumn: message.col, + } as const + sarifResultBuilder.initSimple(sarifResultInit) + sarifRunBuilder.addResult(sarifResultBuilder) + }) + }) + + sarifBuilder.addRun(sarifRunBuilder) + console.log(sarifBuilder.buildSarifJsonString({ indent: true })) + }) +} + +module.exports = sarifFormatter diff --git a/test/cli/formatters/sarif.sarif b/test/cli/formatters/sarif.sarif new file mode 100644 index 000000000..30522fbbf --- /dev/null +++ b/test/cli/formatters/sarif.sarif @@ -0,0 +1,535 @@ +{ + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "HTMLHint", + "rules": [ + { + "id": "attr-value-double-quotes", + "shortDescription": { + "text": "Attribute values must be in double quotes." + }, + "helpUri": "https://htmlhint.com/rules/attr-value-double-quotes" + }, + { + "id": "attr-no-duplication", + "shortDescription": { + "text": "Elements cannot have duplicate attributes." + }, + "helpUri": "https://htmlhint.com/rules/attr-no-duplication" + }, + { + "id": "tag-pair", + "shortDescription": { + "text": "Tag must be paired." + }, + "helpUri": "https://htmlhint.com/rules/tag-pair" + }, + { + "id": "spec-char-escape", + "shortDescription": { + "text": "Special characters must be escaped." + }, + "helpUri": "https://htmlhint.com/rules/spec-char-escape" + } + ], + "version": "1.4.0", + "informationUri": "https://htmlhint.com/" + } + }, + "results": [ + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 8, + "startColumn": 7, + "endLine": 8, + "endColumn": 7 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 8, + "startColumn": 14, + "endLine": 8, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "Duplicate of attribute name [ bad ] was found." + }, + "ruleId": "attr-no-duplication", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 8, + "startColumn": 14, + "endLine": 8, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 1 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 9, + "startColumn": 7, + "endLine": 9, + "endColumn": 7 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ bad ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 9, + "startColumn": 14, + "endLine": 9, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "Duplicate of attribute name [ bad ] was found." + }, + "ruleId": "attr-no-duplication", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 9, + "startColumn": 14, + "endLine": 9, + "endColumn": 14 + } + } + } + ], + "ruleIndex": 1 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 10, + "startColumn": 22, + "endLine": 10, + "endColumn": 22 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Special characters must be escaped : [ < ]." + }, + "ruleId": "spec-char-escape", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 11, + "startColumn": 3, + "endLine": 11, + "endColumn": 3 + } + } + } + ], + "ruleIndex": 3 + }, + { + "level": "error", + "message": { + "text": "Special characters must be escaped : [ > ]." + }, + "ruleId": "spec-char-escape", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 11, + "startColumn": 18, + "endLine": 11, + "endColumn": 18 + } + } + } + ], + "ruleIndex": 3 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 13, + "startColumn": 11, + "endLine": 13, + "endColumn": 11 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 14, + "startColumn": 9, + "endLine": 14, + "endColumn": 9 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 15, + "startColumn": 7, + "endLine": 15, + "endColumn": 7 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 16, + "startColumn": 5, + "endLine": 16, + "endColumn": 5 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 17, + "startColumn": 3, + "endLine": 17, + "endColumn": 3 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ class ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 21, + "startColumn": 15, + "endLine": 21, + "endColumn": 15 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ what ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 21, + "startColumn": 24, + "endLine": 21, + "endColumn": 24 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "The value of attribute [ something ] must be in double quotes." + }, + "ruleId": "attr-value-double-quotes", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 21, + "startColumn": 32, + "endLine": 21, + "endColumn": 32 + } + } + } + ], + "ruleIndex": 0 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 25, + "startColumn": 3, + "endLine": 25, + "endColumn": 3 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 26, + "startColumn": 1, + "endLine": 26, + "endColumn": 1 + } + } + } + ], + "ruleIndex": 2 + }, + { + "level": "error", + "message": { + "text": "Tag must be paired, no start tag: [ ]" + }, + "ruleId": "tag-pair", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "{{path}}", + "index": 0 + }, + "region": { + "startLine": 27, + "startColumn": 1, + "endLine": 27, + "endColumn": 1 + } + } + } + ], + "ruleIndex": 2 + } + ], + "artifacts": [ + { + "sourceLanguage": "HTML", + "location": { + "uri": "{{path}}" + } + } + ] + } + ] +} diff --git a/test/cli/formatters/sarif.spec.js b/test/cli/formatters/sarif.spec.js new file mode 100644 index 000000000..ae4fb94d1 --- /dev/null +++ b/test/cli/formatters/sarif.spec.js @@ -0,0 +1,67 @@ +const ChildProcess = require('child_process') +const fs = require('fs') +const path = require('path') +const os = require('os') + +describe('CLI', () => { + describe('Formatter: sarif', () => { + it('should have stdout output with formatter sarif', (done) => { + const expectedFileContent = fs + .readFileSync(path.resolve(__dirname, 'sarif.sarif'), 'utf8') + .replace(/\{\{path\}\}/g, 'test/cli/formatters/example.html') + + const expected = JSON.parse(expectedFileContent) + + var child = ChildProcess.spawn('node', [ + path.resolve(__dirname, '../../../bin/htmlhint'), + path.resolve(__dirname, 'example.html'), + '--format', + 'sarif', + ]) + + child.stdout.on('data', function (stdout) { + expect(stdout).not.toBe('') + + if (os.platform() !== 'darwin') { + const jsonStdout = JSON.parse(stdout) + expect(typeof jsonStdout).toBe('object') + expect( + jsonStdout['runs'][0]['artifacts'][0]['location']['uri'] + ).toContain('example.html') + + const stdoutResults = jsonStdout['runs'][0]['results'] + const stdoutRules = jsonStdout['runs'][0]['tool']['driver']['rules'] + + expect(stdoutResults).toBeInstanceOf(Array) + expect(stdoutResults.length).toBe( + expected['runs'][0]['results'].length + ) + + expect(stdoutRules).toBeInstanceOf(Array) + expect(stdoutRules.length).toBe( + expected['runs'][0]['tool']['driver']['rules'].length + ) + + for (let i = 0; i < stdoutResults.length; i++) { + expect(stdoutResults[i]).toEqual(expected['runs'][0]['results'][i]) + } + + for (let i = 0; i < stdoutRules.length; i++) { + expect(stdoutRules[i]).toEqual( + expected['runs'][0]['tool']['driver']['rules'][i] + ) + } + } + }) + + child.stderr.on('data', function (stderr) { + expect(stderr).toBe('') + }) + + child.on('close', function (code) { + expect(code).toBe(1) + done() + }) + }) + }) +}) From a8e0490e83ca9c67d9a851f8dd85c076aa9acdd1 Mon Sep 17 00:00:00 2001 From: coliff Date: Thu, 5 Jun 2025 12:57:31 +0900 Subject: [PATCH 2/3] fix lint --- src/cli/formatters/sarif.ts | 1 - website/src/content/docs/usage/options.md | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/formatters/sarif.ts b/src/cli/formatters/sarif.ts index a8371c9cf..a46bbaf5f 100644 --- a/src/cli/formatters/sarif.ts +++ b/src/cli/formatters/sarif.ts @@ -6,7 +6,6 @@ import { SarifRunBuilder, } from 'node-sarif-builder' import * as path from 'path' -import { pathToFileURL } from 'url' import { Result } from 'sarif' const pkg = require('../../../package.json') diff --git a/website/src/content/docs/usage/options.md b/website/src/content/docs/usage/options.md index 0edc86ebf..e2f642288 100644 --- a/website/src/content/docs/usage/options.md +++ b/website/src/content/docs/usage/options.md @@ -21,13 +21,14 @@ Specify the formatter to format your results. Options are: -- `compact` -- `json` (default for Node API) -- `unix` - `checkstyle` +- `compact` - `html` +- `json` (default for Node API) - `junit` - `markdown` +- `sarif` +- `unix` ## `ignore` From f57d3fd6f2097d96607cb984e58bbd73322ab96c Mon Sep 17 00:00:00 2001 From: coliff Date: Thu, 5 Jun 2025 14:31:54 +0900 Subject: [PATCH 3/3] Fix ESLint issue --- dist/core/reporter.js | 4 +- src/cli/formatters/sarif.sarif | 8 +-- src/cli/formatters/sarif.ts | 4 +- src/core/reporter.ts | 2 +- test/cli/formatters/json.json | 40 +++++++------- website/src/content/docs/list-rules.md | 76 +++++++++++++------------- 6 files changed, 67 insertions(+), 67 deletions(-) diff --git a/dist/core/reporter.js b/dist/core/reporter.js index f97b63eb2..8deb4f5dd 100644 --- a/dist/core/reporter.js +++ b/dist/core/reporter.js @@ -47,10 +47,10 @@ class Reporter { rule: { id: rule.id, description: rule.description, - link: `https://htmlhint.com/docs/user-guide/rules/${rule.id}`, + link: `https://htmlhint.com/rules/${rule.id}`, }, }); } } exports.default = Reporter; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwb3J0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29yZS9yZXBvcnRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLE1BQXFCLFFBQVE7SUFPM0IsWUFBbUIsSUFBWSxFQUFFLE9BQWdCO1FBQy9DLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFBO1FBQ2hCLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUNoQyxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO1FBRWhDLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ2pELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO1FBQ3RCLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFBO0lBQ3BCLENBQUM7SUFFTSxJQUFJLENBQ1QsT0FBZSxFQUNmLElBQVksRUFDWixHQUFXLEVBQ1gsSUFBVSxFQUNWLEdBQVc7UUFFWCxJQUFJLENBQUMsTUFBTSxTQUFrQixPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUE7SUFDN0QsQ0FBQztJQUVNLElBQUksQ0FDVCxPQUFlLEVBQ2YsSUFBWSxFQUNaLEdBQVcsRUFDWCxJQUFVLEVBQ1YsR0FBVztRQUVYLElBQUksQ0FBQyxNQUFNLFlBQXFCLE9BQU8sRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQTtJQUNoRSxDQUFDO0lBRU0sS0FBSyxDQUNWLE9BQWUsRUFDZixJQUFZLEVBQ1osR0FBVyxFQUNYLElBQVUsRUFDVixHQUFXO1FBRVgsSUFBSSxDQUFDLE1BQU0sVUFBbUIsT0FBTyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFBO0lBQzlELENBQUM7SUFFTyxNQUFNLENBQ1osSUFBZ0IsRUFDaEIsT0FBZSxFQUNmLElBQVksRUFDWixHQUFXLEVBQ1gsSUFBVSxFQUNWLEdBQVc7UUFFWCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFBO1FBQ3hCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUE7UUFDeEIsSUFBSSxRQUFRLEdBQUcsRUFBRSxDQUFBO1FBQ2pCLElBQUksV0FBVyxHQUFHLENBQUMsQ0FBQTtRQUVuQixLQUFLLElBQUksQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLEVBQUUsU0FBUyxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLFNBQVMsRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNuRSxRQUFRLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFBO1lBQ25CLFdBQVcsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFBO1lBQzdCLElBQUksR0FBRyxHQUFHLFdBQVcsSUFBSSxJQUFJLEdBQUcsU0FBUyxFQUFFO2dCQUN6QyxJQUFJLEVBQUUsQ0FBQTtnQkFDTixHQUFHLElBQUksV0FBVyxDQUFBO2dCQUNsQixJQUFJLEdBQUcsS0FBSyxDQUFDLEVBQUU7b0JBQ2IsR0FBRyxJQUFJLEtBQUssQ0FBQTtpQkFDYjthQUNGO2lCQUFNO2dCQUNMLE1BQUs7YUFDTjtTQUNGO1FBRUQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7WUFDakIsSUFBSSxFQUFFLElBQUk7WUFDVixPQUFPLEVBQUUsT0FBTztZQUNoQixHQUFHLEVBQUUsR0FBRztZQUNSLFFBQVEsRUFBRSxRQUFRO1lBQ2xCLElBQUksRUFBRSxJQUFJO1lBQ1YsR0FBRyxFQUFFLEdBQUc7WUFDUixJQUFJLEVBQUU7Z0JBQ0osRUFBRSxFQUFFLElBQUksQ0FBQyxFQUFFO2dCQUNYLFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBVztnQkFDN0IsSUFBSSxFQUFFLDhDQUE4QyxJQUFJLENBQUMsRUFBRSxFQUFFO2FBQ3REO1NBQ1YsQ0FBQyxDQUFBO0lBQ0osQ0FBQztDQUNGO0FBeEZELDJCQXdGQyJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwb3J0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29yZS9yZXBvcnRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLE1BQXFCLFFBQVE7SUFPM0IsWUFBbUIsSUFBWSxFQUFFLE9BQWdCO1FBQy9DLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFBO1FBQ2hCLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUNoQyxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO1FBRWhDLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ2pELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFBO1FBQ3RCLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFBO0lBQ3BCLENBQUM7SUFFTSxJQUFJLENBQ1QsT0FBZSxFQUNmLElBQVksRUFDWixHQUFXLEVBQ1gsSUFBVSxFQUNWLEdBQVc7UUFFWCxJQUFJLENBQUMsTUFBTSxTQUFrQixPQUFPLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUE7SUFDN0QsQ0FBQztJQUVNLElBQUksQ0FDVCxPQUFlLEVBQ2YsSUFBWSxFQUNaLEdBQVcsRUFDWCxJQUFVLEVBQ1YsR0FBVztRQUVYLElBQUksQ0FBQyxNQUFNLFlBQXFCLE9BQU8sRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQTtJQUNoRSxDQUFDO0lBRU0sS0FBSyxDQUNWLE9BQWUsRUFDZixJQUFZLEVBQ1osR0FBVyxFQUNYLElBQVUsRUFDVixHQUFXO1FBRVgsSUFBSSxDQUFDLE1BQU0sVUFBbUIsT0FBTyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFBO0lBQzlELENBQUM7SUFFTyxNQUFNLENBQ1osSUFBZ0IsRUFDaEIsT0FBZSxFQUNmLElBQVksRUFDWixHQUFXLEVBQ1gsSUFBVSxFQUNWLEdBQVc7UUFFWCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFBO1FBQ3hCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUE7UUFDeEIsSUFBSSxRQUFRLEdBQUcsRUFBRSxDQUFBO1FBQ2pCLElBQUksV0FBVyxHQUFHLENBQUMsQ0FBQTtRQUVuQixLQUFLLElBQUksQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLEVBQUUsU0FBUyxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLFNBQVMsRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNuRSxRQUFRLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFBO1lBQ25CLFdBQVcsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFBO1lBQzdCLElBQUksR0FBRyxHQUFHLFdBQVcsSUFBSSxJQUFJLEdBQUcsU0FBUyxFQUFFO2dCQUN6QyxJQUFJLEVBQUUsQ0FBQTtnQkFDTixHQUFHLElBQUksV0FBVyxDQUFBO2dCQUNsQixJQUFJLEdBQUcsS0FBSyxDQUFDLEVBQUU7b0JBQ2IsR0FBRyxJQUFJLEtBQUssQ0FBQTtpQkFDYjthQUNGO2lCQUFNO2dCQUNMLE1BQUs7YUFDTjtTQUNGO1FBRUQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7WUFDakIsSUFBSSxFQUFFLElBQUk7WUFDVixPQUFPLEVBQUUsT0FBTztZQUNoQixHQUFHLEVBQUUsR0FBRztZQUNSLFFBQVEsRUFBRSxRQUFRO1lBQ2xCLElBQUksRUFBRSxJQUFJO1lBQ1YsR0FBRyxFQUFFLEdBQUc7WUFDUixJQUFJLEVBQUU7Z0JBQ0osRUFBRSxFQUFFLElBQUksQ0FBQyxFQUFFO2dCQUNYLFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBVztnQkFDN0IsSUFBSSxFQUFFLDhCQUE4QixJQUFJLENBQUMsRUFBRSxFQUFFO2FBQ3RDO1NBQ1YsQ0FBQyxDQUFBO0lBQ0osQ0FBQztDQUNGO0FBeEZELDJCQXdGQyJ9 \ No newline at end of file diff --git a/src/cli/formatters/sarif.sarif b/src/cli/formatters/sarif.sarif index 6fb9c6c51..3ea46c0ee 100644 --- a/src/cli/formatters/sarif.sarif +++ b/src/cli/formatters/sarif.sarif @@ -12,28 +12,28 @@ "shortDescription": { "text": "Attribute values must be in double quotes." }, - "helpUri": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "helpUri": "https://htmlhint.com/rules/attr-value-double-quotes" }, { "id": "attr-no-duplication", "shortDescription": { "text": "Elements cannot have duplicate attributes." }, - "helpUri": "https://htmlhint.com/docs/user-guide/rules/attr-no-duplication" + "helpUri": "https://htmlhint.com/rules/attr-no-duplication" }, { "id": "tag-pair", "shortDescription": { "text": "Tag must be paired." }, - "helpUri": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "helpUri": "https://htmlhint.com/rules/tag-pair" }, { "id": "spec-char-escape", "shortDescription": { "text": "Special characters must be escaped." }, - "helpUri": "https://htmlhint.com/docs/user-guide/rules/spec-char-escape" + "helpUri": "https://htmlhint.com/rules/spec-char-escape" } ], "version": "1.1.4", diff --git a/src/cli/formatters/sarif.ts b/src/cli/formatters/sarif.ts index a46bbaf5f..981b8a902 100644 --- a/src/cli/formatters/sarif.ts +++ b/src/cli/formatters/sarif.ts @@ -7,6 +7,7 @@ import { } from 'node-sarif-builder' import * as path from 'path' import { Result } from 'sarif' +import { ReportType } from '../../core/types' const pkg = require('../../../package.json') @@ -49,8 +50,7 @@ const sarifFormatter: FormatterCallback = function (formatter) { const ruleId = message.rule.id const sarifResultInit = { level: - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - message.type === 'info' + message.type === ReportType.info ? 'note' : (message.type.toString() as Result.level), messageText: message.message, diff --git a/src/core/reporter.ts b/src/core/reporter.ts index 8407a0419..024a82caf 100644 --- a/src/core/reporter.ts +++ b/src/core/reporter.ts @@ -84,7 +84,7 @@ export default class Reporter { rule: { id: rule.id, description: rule.description, - link: `https://htmlhint.com/docs/user-guide/rules/${rule.id}`, + link: `https://htmlhint.com/rules/${rule.id}`, } as Rule, }) } diff --git a/test/cli/formatters/json.json b/test/cli/formatters/json.json index 76b61a3ed..7da33f96c 100644 --- a/test/cli/formatters/json.json +++ b/test/cli/formatters/json.json @@ -12,7 +12,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -25,7 +25,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -38,7 +38,7 @@ "rule": { "id": "attr-no-duplication", "description": "Elements cannot have duplicate attributes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-no-duplication" + "link": "https://htmlhint.com/rules/attr-no-duplication" } }, { @@ -51,7 +51,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -64,7 +64,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -77,7 +77,7 @@ "rule": { "id": "attr-no-duplication", "description": "Elements cannot have duplicate attributes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-no-duplication" + "link": "https://htmlhint.com/rules/attr-no-duplication" } }, { @@ -90,7 +90,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -103,7 +103,7 @@ "rule": { "id": "spec-char-escape", "description": "Special characters must be escaped.", - "link": "https://htmlhint.com/docs/user-guide/rules/spec-char-escape" + "link": "https://htmlhint.com/rules/spec-char-escape" } }, { @@ -116,7 +116,7 @@ "rule": { "id": "spec-char-escape", "description": "Special characters must be escaped.", - "link": "https://htmlhint.com/docs/user-guide/rules/spec-char-escape" + "link": "https://htmlhint.com/rules/spec-char-escape" } }, { @@ -129,7 +129,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -142,7 +142,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -155,7 +155,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -168,7 +168,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -181,7 +181,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -194,7 +194,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -207,7 +207,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -220,7 +220,7 @@ "rule": { "id": "attr-value-double-quotes", "description": "Attribute values must be in double quotes.", - "link": "https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes" + "link": "https://htmlhint.com/rules/attr-value-double-quotes" } }, { @@ -233,7 +233,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -246,7 +246,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } }, { @@ -259,7 +259,7 @@ "rule": { "id": "tag-pair", "description": "Tag must be paired.", - "link": "https://htmlhint.com/docs/user-guide/rules/tag-pair" + "link": "https://htmlhint.com/rules/tag-pair" } } ], diff --git a/website/src/content/docs/list-rules.md b/website/src/content/docs/list-rules.md index 499b6050f..b97e1b8cc 100644 --- a/website/src/content/docs/list-rules.md +++ b/website/src/content/docs/list-rules.md @@ -6,56 +6,56 @@ description: A complete list of all the rules for HTMLHint ## Doctype and Head -- [`doctype-first`](/docs/user-guide/rules/doctype-first): Doctype must be declared first. -- [`doctype-html5`](/docs/user-guide/rules/doctype-html5): Invalid doctype. -- [`head-script-disabled`](/docs/user-guide/rules/head-script-disabled): The `