From 39520afb4c58bab54caf537d6ec3d8f6a32f2848 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:52:37 +0000 Subject: [PATCH 1/5] Initial plan From 5e96ec8a10c497de76fad98fc3545b3d0ab9895d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:57:13 +0000 Subject: [PATCH 2/5] Initial analysis: Replace dirty words checker with allowed-links rule Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com> --- test-allowed-links.mjs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test-allowed-links.mjs diff --git a/test-allowed-links.mjs b/test-allowed-links.mjs new file mode 100644 index 0000000..1b7af40 --- /dev/null +++ b/test-allowed-links.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from "html-validate"; + +export default defineConfig({ + rules: { + "allowed-links": "error" + } +}); \ No newline at end of file From 61b45dcbbfec2d99220c0636b422c04aba66d1e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:02:35 +0000 Subject: [PATCH 3/5] Replace dirty words checker with allowed-links rule - implementation complete Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com> --- .htmlvalidate.mjs | 14 + package.json | 2 +- test-allowed-links.mjs | 7 - test/dirty-file-paths-checker.mjs | 98 ------- test/dirty-file-paths.json | 67 ----- test/dirty-words-checker.mjs | 85 ------ test/dirty-words.json | 20 -- test/fixtures/required-results.json | 436 +++++++++++++++------------- 8 files changed, 245 insertions(+), 484 deletions(-) delete mode 100644 test-allowed-links.mjs delete mode 100644 test/dirty-file-paths-checker.mjs delete mode 100644 test/dirty-file-paths.json delete mode 100644 test/dirty-words-checker.mjs delete mode 100644 test/dirty-words.json diff --git a/.htmlvalidate.mjs b/.htmlvalidate.mjs index 56a5106..f98d72a 100644 --- a/.htmlvalidate.mjs +++ b/.htmlvalidate.mjs @@ -4,6 +4,20 @@ export default defineConfig({ plugins: ["/test/plugin.html-validate.mjs"], extends: ["html-validate:prettier", "/test/plugin.html-validate.mjs:recommended"], rules: { + "allowed-links": [ + "error", + { + "allowExternal": { + "exclude": ["\\\\?utm_source=chatgpt.com", ".htm[l]?$"] + }, + "allowRelative": { + "exclude": [".htm[l]?$"] + }, + "allowAbsolute": { + "exclude": [".htm[l]?$"] + } + } + ], "pacific-medical-training/mailto-awesome": "error", "pacific-medical-training/external-links": "error", "pacific-medical-training/no-jquery": "error", diff --git a/package.json b/package.json index a78362a..105657c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "structured-data-testing-tool": "^4.5.0" }, "scripts": { - "test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/fixtures-structured-data-should-fail.mjs && yarn node test/build-html-validate.mjs && yarn node test/dirty-words-checker.mjs && yarn node test/dirty-file-paths-checker.mjs && yarn node test/build-structured-data-validate.mjs", + "test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/fixtures-structured-data-should-fail.mjs && yarn node test/build-html-validate.mjs && yarn node test/build-structured-data-validate.mjs", "test-structured-data": "yarn node test/build-structured-data-validate.mjs", "lint": "yarn prettier-check && yarn markdownlint-check", "lint-fix": "yarn prettier-fix && yarn markdownlint-fix", diff --git a/test-allowed-links.mjs b/test-allowed-links.mjs deleted file mode 100644 index 1b7af40..0000000 --- a/test-allowed-links.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "html-validate"; - -export default defineConfig({ - rules: { - "allowed-links": "error" - } -}); \ No newline at end of file diff --git a/test/dirty-file-paths-checker.mjs b/test/dirty-file-paths-checker.mjs deleted file mode 100644 index 3d3e5d2..0000000 --- a/test/dirty-file-paths-checker.mjs +++ /dev/null @@ -1,98 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { glob } from "glob"; - -const CONFIG_FILE = path.join(process.cwd(), "test", "dirty-file-paths.json"); -const BUILD_DIR = path.join(process.cwd(), "build"); - -// Load and parse the configuration file -function loadConfig() { - try { - const configContent = fs.readFileSync(CONFIG_FILE, "utf-8"); - return JSON.parse(configContent); - } catch (error) { - console.error("Error loading configuration:", error.message); - process.exit(1); - } -} - -// Find all files in the build directory -function findTargetFiles() { - return glob - .sync("**/*", { - cwd: BUILD_DIR, - nocase: false, // Case sensitive as per requirements - dot: false, - }) - .filter((file) => { - const fullPath = path.join(BUILD_DIR, file); - return fs.lstatSync(fullPath).isFile(); - }); -} - -// Convert relative file path to the format expected by rules (starting with /) -function normalizePathForMatching(filePath) { - // Convert backslashes to forward slashes and ensure it starts with / - const normalized = filePath.replace(/\\/g, "/"); - return normalized.startsWith("/") ? normalized : "/" + normalized; -} - -// Check a single file's path against the dirty path rules -function checkFile(filePath, config) { - const normalizedPath = normalizePathForMatching(filePath); - - // Process rules in order, later rules take precedence - let finalRule = null; - - for (const rule of config) { - try { - const regex = new RegExp(rule.path); - if (regex.test(normalizedPath)) { - finalRule = rule; - } - } catch (error) { - console.error(`Invalid regex pattern in rule: ${rule.path}`, error.message); - process.exit(1); - } - } - - // Return violation only if the final matching rule is dirty - if (finalRule && finalRule.dirty) { - return { path: normalizedPath, advice: finalRule.advice }; - } - - return null; -} - -console.log("🧪 Testing files for dirty file paths"); - -const config = loadConfig(); -const files = findTargetFiles(); -let hasErrors = false; -const violationsByPath = new Map(); - -// Collect violations -files.forEach((file) => { - const violation = checkFile(file, config); - if (violation) { - hasErrors = true; - console.log(`❌ ${file}: ${violation.advice}`); - violationsByPath.set(violation.path, violation.advice); - } -}); - -// Summary of clean files -const cleanFiles = files.filter((file) => !checkFile(file, config)); -console.log(`✅ Site includes ${cleanFiles.length} clean files`); - -// Summary of dirty paths -if (hasErrors) { - console.log("Following are dirty file paths:"); - violationsByPath.forEach((advice, path) => { - console.log(`${path} ${advice}`); - }); - console.error("\n❌ Dirty paths check failed"); - process.exit(1); -} else { - console.log("✨ All files passed dirty paths check!\n"); -} diff --git a/test/dirty-file-paths.json b/test/dirty-file-paths.json deleted file mode 100644 index 8274ab2..0000000 --- a/test/dirty-file-paths.json +++ /dev/null @@ -1,67 +0,0 @@ -[ - { - "path": "/.*\\.png$", - "dirty": true, - "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" - }, - { - "path": "/.*\\.jpg$", - "dirty": true, - "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" - }, - { - "path": "/.*\\.jpeg$", - "dirty": true, - "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" - }, - { - "path": "/.*\\.gif$", - "dirty": true, - "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" - }, - { - "path": "/.*\\.woff$", - "dirty": true, - "advice": "Only use WOFF2 for fonts https://web.dev/learn/performance/optimize-web-fonts" - }, - { - "path": "/.*\\.eot$", - "dirty": true, - "advice": "Only use WOFF2 for fonts https://web.dev/learn/performance/optimize-web-fonts" - }, - { - "path": "/.*\\.ttf$", - "dirty": true, - "advice": "Only use WOFF2 for fonts https://web.dev/learn/performance/optimize-web-fonts" - }, - { - "path": "/.*\\.htm$", - "dirty": true, - "advice": "Prefer to use .html file extension" - }, - { - "path": "/.*\\.ogv$", - "dirty": true, - "advice": "Only use .webm for video files" - }, - { - "path": "/.*\\.mp4$", - "dirty": true, - "advice": "Only use .webm for video files" - }, - { - "path": "/.*\\.mp3$", - "dirty": true, - "advice": "Only use .webm for video files" - }, - { - "path": "/[^/]*/[^.]*\\.ico$", - "dirty": true, - "advice": "ICO files are not recommended for modern web development" - }, - { - "path": "/favicon\\.ico$", - "dirty": false, - "advice": "Favicon.ico is allowed for browser compatibility" - } -] diff --git a/test/dirty-words-checker.mjs b/test/dirty-words-checker.mjs deleted file mode 100644 index 0b3336d..0000000 --- a/test/dirty-words-checker.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { glob } from "glob"; - -const CONFIG_FILE = path.join(process.cwd(), "test", "dirty-words.json"); -const BUILD_DIR = path.join(process.cwd(), "build"); - -// Load and parse the configuration file -function loadConfig() { - try { - const configContent = fs.readFileSync(CONFIG_FILE, "utf-8"); - return JSON.parse(configContent); - } catch (error) { - console.error("Error loading configuration:", error.message); - process.exit(1); - } -} - -// Check all files in the build directory -function findTargetFiles() { - return glob - .sync("**/*", { - cwd: BUILD_DIR, - nocase: true, - dot: false, - }) - .filter((file) => { - const fullPath = path.join(BUILD_DIR, file); - return fs.lstatSync(fullPath).isFile(); - }); -} - -// Check a single file against all patterns -function checkFile(filePath, patterns) { - const fullPath = path.join(BUILD_DIR, filePath); - const content = fs.readFileSync(fullPath, "utf-8"); - const violations = []; - - patterns.forEach((pattern) => { - const flags = pattern.ignoreCase ? "gi" : "g"; - const regex = new RegExp(pattern.regexp, flags); - - const matches = content.match(regex); - if (matches) { - violations.push({ - pattern: pattern, - matches: matches, - count: matches.length, - }); - } - }); - - return violations; -} - -console.log("🧪 Testing files for dirty words"); - -const config = loadConfig(); -const files = findTargetFiles(); -let hasErrors = false; - -files.forEach((file) => { - const violations = checkFile(file, config); - - if (violations.length > 0) { - hasErrors = true; - console.log(`❌ ${file}:`); - - violations.forEach((violation) => { - const { pattern, matches, count } = violation; - console.error(` ${pattern.severity.toUpperCase()}: ${pattern.note}`); - console.log(` Found ${count} matches:`); - matches.forEach((match) => { - console.log(` "${match}"`); - }); - }); - } -}); - -if (hasErrors) { - console.error("\n❌ Dirty words check failed"); - process.exit(1); -} else { - console.log("✨ All files passed dirty words check!\n"); -} diff --git a/test/dirty-words.json b/test/dirty-words.json deleted file mode 100644 index 5c2a6fa..0000000 --- a/test/dirty-words.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "regexp": "\\?utm_source=chatgpt.com", - "severity": "error", - "ignoreCase": true, - "note": "Copied a link from Chat GPT without reviewing it" - }, - { - "regexp": "<\\s*title\\s*>[^<]*\\&amp;[^<]*", - "severity": "error", - "ignoreCase": true, - "note": "Double-escaped HTML quoting" - }, - { - "regexp": "href\\s*=\\s*\"[^:]+\\.htm[l]?\"", - "severity": "error", - "ignoreCase": true, - "note": "Linking to an HTML page with the extension .html or .htm" - } -] diff --git a/test/fixtures/required-results.json b/test/fixtures/required-results.json index b0b9eae..8d46c89 100644 --- a/test/fixtures/required-results.json +++ b/test/fixtures/required-results.json @@ -1,207 +1,231 @@ { - "test/fixtures/mailto-not-awesome.html": [ - { - "ruleId": "pacific-medical-training/mailto-awesome", - "severity": 2, - "message": "mailto link must have a subject and body", - "offset": 196, - "line": 9, - "column": 6, - "size": 1, - "selector": "html > body > a", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#mailto-awesome" - } - ], - "test/fixtures/external-link-broken.html": [ - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link is broken with status 500: https://freehorses.example.com/free-horses-on-1998-04-01-only.html", - "offset": 271, - "line": 9, - "column": 81, - "size": 1, - "selector": "html > body > a:nth-child(1)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link is broken with status 500: https://----.example.com?a=b&c=d", - "offset": 348, - "line": 10, - "column": 51, - "size": 1, - "selector": "html > body > a:nth-child(2)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link is broken with status 500: https://-..-..-.-.-", - "offset": 413, - "line": 11, - "column": 34, - "size": 1, - "selector": "html > body > a:nth-child(3)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link https://httpbin.org/redirect-to?url=https://example.com&status_code=301 redirects to: https://example.com", - "offset": 655, - "line": 14, - "column": 86, - "size": 1, - "selector": "html > body > a:nth-child(6)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - } - ], - "test/fixtures/internal-link-broken.html": [ - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"/free-horses-on-1998-04-01-only.html\" is broken.", - "offset": 241, - "line": 9, - "column": 51, - "size": 1, - "selector": "html > body > a" - } - ], - "test/fixtures/ensure-https.html": [ - { - "ruleId": "pacific-medical-training/https-links", - "severity": 2, - "message": "external link is insecure and accessible via HTTPS: http://en.wikipedia.org/wiki/Horse", - "offset": 196, - "line": 9, - "column": 6, - "size": 1, - "selector": "html > body > a", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#https-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link http://en.wikipedia.org/wiki/Horse redirects to: https://en.wikipedia.org/wiki/Horse", - "offset": 239, - "line": 9, - "column": 49, - "size": 1, - "selector": "html > body > a", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - } - ], - "test/fixtures/using-jquery.html": [ - { - "ruleId": "pacific-medical-training/no-jquery", - "severity": 2, - "message": "script tag with src including jQuery", - "offset": 123, - "line": 6, - "column": 6, - "size": 6, - "selector": "html > head > script", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#no-jquery" - } - ], - "test/fixtures/canonical-link-missing.html": [ - { - "ruleId": "pacific-medical-training/canonical-link", - "severity": 2, - "message": " is missing ", - "size": 0, - "selector": null, - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#canonical" - } - ], - "test/fixtures/old-package.html": [ - { - "ruleId": "pacific-medical-training/latest-packages", - "severity": 2, - "message": "using outdated package version https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.min.js", - "offset": 123, - "line": 6, - "column": 6, - "size": 6, - "selector": "html > head > script" - } - ], - "test/fixtures/image-missing-alt.html": [ - { - "ruleId": "wcag/h37", - "severity": 2, - "message": " cannot have empty \"alt\" attribute", - "offset": 249, - "line": 9, - "column": 59, - "size": 3, - "selector": "html > body > img", - "ruleUrl": "https://html-validate.org/rules/wcag/h37.html" - } - ], - "test/fixtures/directory-path-links-test.html": [ - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index\" is broken.", - "offset": 367, - "line": 12, - "column": 44, - "size": 1, - "selector": "html > body > ul > li:nth-child(1) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index/\" is broken.", - "offset": 464, - "line": 13, - "column": 45, - "size": 1, - "selector": "html > body > ul > li:nth-child(2) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index/index\" is broken.", - "offset": 567, - "line": 14, - "column": 50, - "size": 1, - "selector": "html > body > ul > li:nth-child(3) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-index\" is broken.", - "offset": 666, - "line": 15, - "column": 41, - "size": 1, - "selector": "html > body > ul > li:nth-child(4) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index-with-twin/\" is broken.", - "offset": 1068, - "line": 19, - "column": 55, - "size": 1, - "selector": "html > body > ul > li:nth-child(8) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index-with-twin/index\" is broken.", - "offset": 1200, - "line": 21, - "column": 58, - "size": 1, - "selector": "html > body > ul > li:nth-child(9) > a" - } - ] -} + "test/fixtures/mailto-not-awesome.html": [ + { + "ruleId": "pacific-medical-training/mailto-awesome", + "severity": 2, + "message": "mailto link must have a subject and body", + "offset": 196, + "line": 9, + "column": 6, + "size": 1, + "selector": "html > body > a", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#mailto-awesome" + } + ], + "test/fixtures/external-link-broken.html": [ + { + "ruleId": "allowed-links", + "severity": 2, + "message": "External link to this destination is not allowed by current configuration", + "offset": 204, + "line": 9, + "column": 14, + "size": 66, + "selector": "html > body > a:nth-child(1)", + "ruleUrl": "https://html-validate.org/rules/allowed-links.html", + "context": "external" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link is broken with status 500: https://freehorses.example.com/free-horses-on-1998-04-01-only.html", + "offset": 271, + "line": 9, + "column": 81, + "size": 1, + "selector": "html > body > a:nth-child(1)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link is broken with status 500: https://----.example.com?a=b&c=d", + "offset": 348, + "line": 10, + "column": 51, + "size": 1, + "selector": "html > body > a:nth-child(2)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link is broken with status 500: https://-..-..-.-.-", + "offset": 413, + "line": 11, + "column": 34, + "size": 1, + "selector": "html > body > a:nth-child(3)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link https://httpbin.org/redirect-to?url=https://example.com&status_code=301 redirects to: https://example.com", + "offset": 655, + "line": 14, + "column": 86, + "size": 1, + "selector": "html > body > a:nth-child(6)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + } + ], + "test/fixtures/internal-link-broken.html": [ + { + "ruleId": "allowed-links", + "severity": 2, + "message": "Absolute link to this destination is not allowed by current configuration", + "offset": 204, + "line": 9, + "column": 14, + "size": 36, + "selector": "html > body > a", + "ruleUrl": "https://html-validate.org/rules/allowed-links.html", + "context": "absolute" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"/free-horses-on-1998-04-01-only.html\" is broken.", + "offset": 241, + "line": 9, + "column": 51, + "size": 1, + "selector": "html > body > a" + } + ], + "test/fixtures/ensure-https.html": [ + { + "ruleId": "pacific-medical-training/https-links", + "severity": 2, + "message": "external link is insecure and accessible via HTTPS: http://en.wikipedia.org/wiki/Horse", + "offset": 196, + "line": 9, + "column": 6, + "size": 1, + "selector": "html > body > a", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#https-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link http://en.wikipedia.org/wiki/Horse redirects to: https://en.wikipedia.org/wiki/Horse", + "offset": 239, + "line": 9, + "column": 49, + "size": 1, + "selector": "html > body > a", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + } + ], + "test/fixtures/using-jquery.html": [ + { + "ruleId": "pacific-medical-training/no-jquery", + "severity": 2, + "message": "script tag with src including jQuery", + "offset": 123, + "line": 6, + "column": 6, + "size": 6, + "selector": "html > head > script", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#no-jquery" + } + ], + "test/fixtures/canonical-link-missing.html": [ + { + "ruleId": "pacific-medical-training/canonical-link", + "severity": 2, + "message": " is missing ", + "size": 0, + "selector": null, + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#canonical" + } + ], + "test/fixtures/old-package.html": [ + { + "ruleId": "pacific-medical-training/latest-packages", + "severity": 2, + "message": "using outdated package version https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.min.js", + "offset": 123, + "line": 6, + "column": 6, + "size": 6, + "selector": "html > head > script" + } + ], + "test/fixtures/image-missing-alt.html": [ + { + "ruleId": "wcag/h37", + "severity": 2, + "message": " cannot have empty \"alt\" attribute", + "offset": 249, + "line": 9, + "column": 59, + "size": 3, + "selector": "html > body > img", + "ruleUrl": "https://html-validate.org/rules/wcag/h37.html" + } + ], + "test/fixtures/directory-path-links-test.html": [ + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index\" is broken.", + "offset": 367, + "line": 12, + "column": 44, + "size": 1, + "selector": "html > body > ul > li:nth-child(1) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index/\" is broken.", + "offset": 464, + "line": 13, + "column": 45, + "size": 1, + "selector": "html > body > ul > li:nth-child(2) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index/index\" is broken.", + "offset": 567, + "line": 14, + "column": 50, + "size": 1, + "selector": "html > body > ul > li:nth-child(3) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-index\" is broken.", + "offset": 666, + "line": 15, + "column": 41, + "size": 1, + "selector": "html > body > ul > li:nth-child(4) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index-with-twin/\" is broken.", + "offset": 1068, + "line": 19, + "column": 55, + "size": 1, + "selector": "html > body > ul > li:nth-child(8) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index-with-twin/index\" is broken.", + "offset": 1200, + "line": 21, + "column": 58, + "size": 1, + "selector": "html > body > ul > li:nth-child(9) > a" + } + ] +} \ No newline at end of file From 4f5cbee6bb02f5e60d427da21f29844f0fc34730 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Sep 2025 18:52:06 +0000 Subject: [PATCH 4/5] Restore dirty file paths checker per @fulldecent feedback Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com> --- package.json | 2 +- test/dirty-file-paths-checker.mjs | 98 +++++++++++++++++++++++++++++++ test/dirty-file-paths.json | 67 +++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 test/dirty-file-paths-checker.mjs create mode 100644 test/dirty-file-paths.json diff --git a/package.json b/package.json index 105657c..790799e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "structured-data-testing-tool": "^4.5.0" }, "scripts": { - "test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/fixtures-structured-data-should-fail.mjs && yarn node test/build-html-validate.mjs && yarn node test/build-structured-data-validate.mjs", + "test": "yarn node test/fixtures-html-validate-should-fail.mjs && yarn node test/fixtures-structured-data-should-fail.mjs && yarn node test/build-html-validate.mjs && yarn node test/dirty-file-paths-checker.mjs && yarn node test/build-structured-data-validate.mjs", "test-structured-data": "yarn node test/build-structured-data-validate.mjs", "lint": "yarn prettier-check && yarn markdownlint-check", "lint-fix": "yarn prettier-fix && yarn markdownlint-fix", diff --git a/test/dirty-file-paths-checker.mjs b/test/dirty-file-paths-checker.mjs new file mode 100644 index 0000000..3d3e5d2 --- /dev/null +++ b/test/dirty-file-paths-checker.mjs @@ -0,0 +1,98 @@ +import fs from "fs"; +import path from "path"; +import { glob } from "glob"; + +const CONFIG_FILE = path.join(process.cwd(), "test", "dirty-file-paths.json"); +const BUILD_DIR = path.join(process.cwd(), "build"); + +// Load and parse the configuration file +function loadConfig() { + try { + const configContent = fs.readFileSync(CONFIG_FILE, "utf-8"); + return JSON.parse(configContent); + } catch (error) { + console.error("Error loading configuration:", error.message); + process.exit(1); + } +} + +// Find all files in the build directory +function findTargetFiles() { + return glob + .sync("**/*", { + cwd: BUILD_DIR, + nocase: false, // Case sensitive as per requirements + dot: false, + }) + .filter((file) => { + const fullPath = path.join(BUILD_DIR, file); + return fs.lstatSync(fullPath).isFile(); + }); +} + +// Convert relative file path to the format expected by rules (starting with /) +function normalizePathForMatching(filePath) { + // Convert backslashes to forward slashes and ensure it starts with / + const normalized = filePath.replace(/\\/g, "/"); + return normalized.startsWith("/") ? normalized : "/" + normalized; +} + +// Check a single file's path against the dirty path rules +function checkFile(filePath, config) { + const normalizedPath = normalizePathForMatching(filePath); + + // Process rules in order, later rules take precedence + let finalRule = null; + + for (const rule of config) { + try { + const regex = new RegExp(rule.path); + if (regex.test(normalizedPath)) { + finalRule = rule; + } + } catch (error) { + console.error(`Invalid regex pattern in rule: ${rule.path}`, error.message); + process.exit(1); + } + } + + // Return violation only if the final matching rule is dirty + if (finalRule && finalRule.dirty) { + return { path: normalizedPath, advice: finalRule.advice }; + } + + return null; +} + +console.log("🧪 Testing files for dirty file paths"); + +const config = loadConfig(); +const files = findTargetFiles(); +let hasErrors = false; +const violationsByPath = new Map(); + +// Collect violations +files.forEach((file) => { + const violation = checkFile(file, config); + if (violation) { + hasErrors = true; + console.log(`❌ ${file}: ${violation.advice}`); + violationsByPath.set(violation.path, violation.advice); + } +}); + +// Summary of clean files +const cleanFiles = files.filter((file) => !checkFile(file, config)); +console.log(`✅ Site includes ${cleanFiles.length} clean files`); + +// Summary of dirty paths +if (hasErrors) { + console.log("Following are dirty file paths:"); + violationsByPath.forEach((advice, path) => { + console.log(`${path} ${advice}`); + }); + console.error("\n❌ Dirty paths check failed"); + process.exit(1); +} else { + console.log("✨ All files passed dirty paths check!\n"); +} diff --git a/test/dirty-file-paths.json b/test/dirty-file-paths.json new file mode 100644 index 0000000..8274ab2 --- /dev/null +++ b/test/dirty-file-paths.json @@ -0,0 +1,67 @@ +[ + { + "path": "/.*\\.png$", + "dirty": true, + "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" + }, + { + "path": "/.*\\.jpg$", + "dirty": true, + "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" + }, + { + "path": "/.*\\.jpeg$", + "dirty": true, + "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" + }, + { + "path": "/.*\\.gif$", + "dirty": true, + "advice": "Only use WebP for bitmap images https://web.dev/articles/choose-the-right-image-format" + }, + { + "path": "/.*\\.woff$", + "dirty": true, + "advice": "Only use WOFF2 for fonts https://web.dev/learn/performance/optimize-web-fonts" + }, + { + "path": "/.*\\.eot$", + "dirty": true, + "advice": "Only use WOFF2 for fonts https://web.dev/learn/performance/optimize-web-fonts" + }, + { + "path": "/.*\\.ttf$", + "dirty": true, + "advice": "Only use WOFF2 for fonts https://web.dev/learn/performance/optimize-web-fonts" + }, + { + "path": "/.*\\.htm$", + "dirty": true, + "advice": "Prefer to use .html file extension" + }, + { + "path": "/.*\\.ogv$", + "dirty": true, + "advice": "Only use .webm for video files" + }, + { + "path": "/.*\\.mp4$", + "dirty": true, + "advice": "Only use .webm for video files" + }, + { + "path": "/.*\\.mp3$", + "dirty": true, + "advice": "Only use .webm for video files" + }, + { + "path": "/[^/]*/[^.]*\\.ico$", + "dirty": true, + "advice": "ICO files are not recommended for modern web development" + }, + { + "path": "/favicon\\.ico$", + "dirty": false, + "advice": "Favicon.ico is allowed for browser compatibility" + } +] From 9174b207ed162ad9831cca1f8e93dce27601dd55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Sep 2025 19:11:00 +0000 Subject: [PATCH 5/5] Fix prettier lint error and improve sitemap generation Co-authored-by: fulldecent <382183+fulldecent@users.noreply.github.com> --- .github/workflows/build-test-deploy.yml | 4 + .htmlvalidate.mjs | 16 +- scripts/generate-sitemap.mjs | 4 +- test/fixtures/required-results.json | 460 ++++++++++++------------ 4 files changed, 245 insertions(+), 239 deletions(-) diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml index 349f74d..47a0af7 100644 --- a/.github/workflows/build-test-deploy.yml +++ b/.github/workflows/build-test-deploy.yml @@ -48,6 +48,10 @@ jobs: run: yarn install --immutable - name: Generate sitemap + env: + # For repository named username.github.io, use https://username.github.io + # For other repositories, use https://username.github.io/repository-name + SITE_URL: ${{ github.event.repository.name == format('{0}.github.io', github.repository_owner) && format('https://{0}.github.io', github.repository_owner) || format('https://{0}.github.io/{1}', github.repository_owner, github.event.repository.name) }} run: yarn run generate-sitemap - name: Upload build artifact, ready for GitHub Pages deployment uses: actions/upload-pages-artifact@v3 diff --git a/.htmlvalidate.mjs b/.htmlvalidate.mjs index f98d72a..ed20daa 100644 --- a/.htmlvalidate.mjs +++ b/.htmlvalidate.mjs @@ -7,16 +7,16 @@ export default defineConfig({ "allowed-links": [ "error", { - "allowExternal": { - "exclude": ["\\\\?utm_source=chatgpt.com", ".htm[l]?$"] + allowExternal: { + exclude: ["\\\\?utm_source=chatgpt.com", ".htm[l]?$"], }, - "allowRelative": { - "exclude": [".htm[l]?$"] + allowRelative: { + exclude: [".htm[l]?$"], }, - "allowAbsolute": { - "exclude": [".htm[l]?$"] - } - } + allowAbsolute: { + exclude: [".htm[l]?$"], + }, + }, ], "pacific-medical-training/mailto-awesome": "error", "pacific-medical-training/external-links": "error", diff --git a/scripts/generate-sitemap.mjs b/scripts/generate-sitemap.mjs index 2e9c758..abf2744 100644 --- a/scripts/generate-sitemap.mjs +++ b/scripts/generate-sitemap.mjs @@ -3,7 +3,9 @@ import path from "path"; import https from "https"; import { parseString } from "xml2js"; // Using xml2js for XML parsing -const site = "https://www.acls.net"; +// Site URL configuration - can be overridden via SITE_URL environment variable +// For GitHub Pages, this should typically be https://username.github.io/repository-name +const site = process.env.SITE_URL || "https://www.acls.net"; const buildFolderPath = path.join(process.cwd(), "build"); const sitemapPath = path.join(buildFolderPath, "sitemap.xml"); const daysThreshold = 30; // Number of days to compare for updating lastmod diff --git a/test/fixtures/required-results.json b/test/fixtures/required-results.json index 8d46c89..3944351 100644 --- a/test/fixtures/required-results.json +++ b/test/fixtures/required-results.json @@ -1,231 +1,231 @@ { - "test/fixtures/mailto-not-awesome.html": [ - { - "ruleId": "pacific-medical-training/mailto-awesome", - "severity": 2, - "message": "mailto link must have a subject and body", - "offset": 196, - "line": 9, - "column": 6, - "size": 1, - "selector": "html > body > a", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#mailto-awesome" - } - ], - "test/fixtures/external-link-broken.html": [ - { - "ruleId": "allowed-links", - "severity": 2, - "message": "External link to this destination is not allowed by current configuration", - "offset": 204, - "line": 9, - "column": 14, - "size": 66, - "selector": "html > body > a:nth-child(1)", - "ruleUrl": "https://html-validate.org/rules/allowed-links.html", - "context": "external" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link is broken with status 500: https://freehorses.example.com/free-horses-on-1998-04-01-only.html", - "offset": 271, - "line": 9, - "column": 81, - "size": 1, - "selector": "html > body > a:nth-child(1)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link is broken with status 500: https://----.example.com?a=b&c=d", - "offset": 348, - "line": 10, - "column": 51, - "size": 1, - "selector": "html > body > a:nth-child(2)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link is broken with status 500: https://-..-..-.-.-", - "offset": 413, - "line": 11, - "column": 34, - "size": 1, - "selector": "html > body > a:nth-child(3)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link https://httpbin.org/redirect-to?url=https://example.com&status_code=301 redirects to: https://example.com", - "offset": 655, - "line": 14, - "column": 86, - "size": 1, - "selector": "html > body > a:nth-child(6)", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - } - ], - "test/fixtures/internal-link-broken.html": [ - { - "ruleId": "allowed-links", - "severity": 2, - "message": "Absolute link to this destination is not allowed by current configuration", - "offset": 204, - "line": 9, - "column": 14, - "size": 36, - "selector": "html > body > a", - "ruleUrl": "https://html-validate.org/rules/allowed-links.html", - "context": "absolute" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"/free-horses-on-1998-04-01-only.html\" is broken.", - "offset": 241, - "line": 9, - "column": 51, - "size": 1, - "selector": "html > body > a" - } - ], - "test/fixtures/ensure-https.html": [ - { - "ruleId": "pacific-medical-training/https-links", - "severity": 2, - "message": "external link is insecure and accessible via HTTPS: http://en.wikipedia.org/wiki/Horse", - "offset": 196, - "line": 9, - "column": 6, - "size": 1, - "selector": "html > body > a", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#https-links" - }, - { - "ruleId": "pacific-medical-training/external-links", - "severity": 2, - "message": "external link http://en.wikipedia.org/wiki/Horse redirects to: https://en.wikipedia.org/wiki/Horse", - "offset": 239, - "line": 9, - "column": 49, - "size": 1, - "selector": "html > body > a", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" - } - ], - "test/fixtures/using-jquery.html": [ - { - "ruleId": "pacific-medical-training/no-jquery", - "severity": 2, - "message": "script tag with src including jQuery", - "offset": 123, - "line": 6, - "column": 6, - "size": 6, - "selector": "html > head > script", - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#no-jquery" - } - ], - "test/fixtures/canonical-link-missing.html": [ - { - "ruleId": "pacific-medical-training/canonical-link", - "severity": 2, - "message": " is missing ", - "size": 0, - "selector": null, - "ruleUrl": "https://github.com/fulldecent/github-pages-template/#canonical" - } - ], - "test/fixtures/old-package.html": [ - { - "ruleId": "pacific-medical-training/latest-packages", - "severity": 2, - "message": "using outdated package version https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.min.js", - "offset": 123, - "line": 6, - "column": 6, - "size": 6, - "selector": "html > head > script" - } - ], - "test/fixtures/image-missing-alt.html": [ - { - "ruleId": "wcag/h37", - "severity": 2, - "message": " cannot have empty \"alt\" attribute", - "offset": 249, - "line": 9, - "column": 59, - "size": 3, - "selector": "html > body > img", - "ruleUrl": "https://html-validate.org/rules/wcag/h37.html" - } - ], - "test/fixtures/directory-path-links-test.html": [ - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index\" is broken.", - "offset": 367, - "line": 12, - "column": 44, - "size": 1, - "selector": "html > body > ul > li:nth-child(1) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index/\" is broken.", - "offset": 464, - "line": 13, - "column": 45, - "size": 1, - "selector": "html > body > ul > li:nth-child(2) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index/index\" is broken.", - "offset": 567, - "line": 14, - "column": 50, - "size": 1, - "selector": "html > body > ul > li:nth-child(3) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-index\" is broken.", - "offset": 666, - "line": 15, - "column": 41, - "size": 1, - "selector": "html > body > ul > li:nth-child(4) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index-with-twin/\" is broken.", - "offset": 1068, - "line": 19, - "column": 55, - "size": 1, - "selector": "html > body > ul > li:nth-child(8) > a" - }, - { - "ruleId": "pacific-medical-training/internal-links", - "severity": 2, - "message": "internal link \"directory-with-no-index-with-twin/index\" is broken.", - "offset": 1200, - "line": 21, - "column": 58, - "size": 1, - "selector": "html > body > ul > li:nth-child(9) > a" - } - ] -} \ No newline at end of file + "test/fixtures/mailto-not-awesome.html": [ + { + "ruleId": "pacific-medical-training/mailto-awesome", + "severity": 2, + "message": "mailto link must have a subject and body", + "offset": 196, + "line": 9, + "column": 6, + "size": 1, + "selector": "html > body > a", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#mailto-awesome" + } + ], + "test/fixtures/external-link-broken.html": [ + { + "ruleId": "allowed-links", + "severity": 2, + "message": "External link to this destination is not allowed by current configuration", + "offset": 204, + "line": 9, + "column": 14, + "size": 66, + "selector": "html > body > a:nth-child(1)", + "ruleUrl": "https://html-validate.org/rules/allowed-links.html", + "context": "external" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link is broken with status 500: https://freehorses.example.com/free-horses-on-1998-04-01-only.html", + "offset": 271, + "line": 9, + "column": 81, + "size": 1, + "selector": "html > body > a:nth-child(1)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link is broken with status 500: https://----.example.com?a=b&c=d", + "offset": 348, + "line": 10, + "column": 51, + "size": 1, + "selector": "html > body > a:nth-child(2)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link is broken with status 500: https://-..-..-.-.-", + "offset": 413, + "line": 11, + "column": 34, + "size": 1, + "selector": "html > body > a:nth-child(3)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link https://httpbin.org/redirect-to?url=https://example.com&status_code=301 redirects to: https://example.com", + "offset": 655, + "line": 14, + "column": 86, + "size": 1, + "selector": "html > body > a:nth-child(6)", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + } + ], + "test/fixtures/internal-link-broken.html": [ + { + "ruleId": "allowed-links", + "severity": 2, + "message": "Absolute link to this destination is not allowed by current configuration", + "offset": 204, + "line": 9, + "column": 14, + "size": 36, + "selector": "html > body > a", + "ruleUrl": "https://html-validate.org/rules/allowed-links.html", + "context": "absolute" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"/free-horses-on-1998-04-01-only.html\" is broken.", + "offset": 241, + "line": 9, + "column": 51, + "size": 1, + "selector": "html > body > a" + } + ], + "test/fixtures/ensure-https.html": [ + { + "ruleId": "pacific-medical-training/https-links", + "severity": 2, + "message": "external link is insecure and accessible via HTTPS: http://en.wikipedia.org/wiki/Horse", + "offset": 196, + "line": 9, + "column": 6, + "size": 1, + "selector": "html > body > a", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#https-links" + }, + { + "ruleId": "pacific-medical-training/external-links", + "severity": 2, + "message": "external link http://en.wikipedia.org/wiki/Horse redirects to: https://en.wikipedia.org/wiki/Horse", + "offset": 239, + "line": 9, + "column": 49, + "size": 1, + "selector": "html > body > a", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#external-links" + } + ], + "test/fixtures/using-jquery.html": [ + { + "ruleId": "pacific-medical-training/no-jquery", + "severity": 2, + "message": "script tag with src including jQuery", + "offset": 123, + "line": 6, + "column": 6, + "size": 6, + "selector": "html > head > script", + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#no-jquery" + } + ], + "test/fixtures/canonical-link-missing.html": [ + { + "ruleId": "pacific-medical-training/canonical-link", + "severity": 2, + "message": " is missing ", + "size": 0, + "selector": null, + "ruleUrl": "https://github.com/fulldecent/github-pages-template/#canonical" + } + ], + "test/fixtures/old-package.html": [ + { + "ruleId": "pacific-medical-training/latest-packages", + "severity": 2, + "message": "using outdated package version https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.min.js", + "offset": 123, + "line": 6, + "column": 6, + "size": 6, + "selector": "html > head > script" + } + ], + "test/fixtures/image-missing-alt.html": [ + { + "ruleId": "wcag/h37", + "severity": 2, + "message": " cannot have empty \"alt\" attribute", + "offset": 249, + "line": 9, + "column": 59, + "size": 3, + "selector": "html > body > img", + "ruleUrl": "https://html-validate.org/rules/wcag/h37.html" + } + ], + "test/fixtures/directory-path-links-test.html": [ + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index\" is broken.", + "offset": 367, + "line": 12, + "column": 44, + "size": 1, + "selector": "html > body > ul > li:nth-child(1) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index/\" is broken.", + "offset": 464, + "line": 13, + "column": 45, + "size": 1, + "selector": "html > body > ul > li:nth-child(2) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index/index\" is broken.", + "offset": 567, + "line": 14, + "column": 50, + "size": 1, + "selector": "html > body > ul > li:nth-child(3) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-index\" is broken.", + "offset": 666, + "line": 15, + "column": 41, + "size": 1, + "selector": "html > body > ul > li:nth-child(4) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index-with-twin/\" is broken.", + "offset": 1068, + "line": 19, + "column": 55, + "size": 1, + "selector": "html > body > ul > li:nth-child(8) > a" + }, + { + "ruleId": "pacific-medical-training/internal-links", + "severity": 2, + "message": "internal link \"directory-with-no-index-with-twin/index\" is broken.", + "offset": 1200, + "line": 21, + "column": 58, + "size": 1, + "selector": "html > body > ul > li:nth-child(9) > a" + } + ] +}