From 189389707c4cd65f25e86c69172309fafd262d13 Mon Sep 17 00:00:00 2001 From: Zachary Keeping Date: Thu, 23 Nov 2023 17:26:25 +1100 Subject: [PATCH 1/4] Initial code --- {ui/src/utils => constants}/countries.js | 0 constants/rules.js | 274 ++++++++++++++++++ docker/customHtmlRules.js | 25 +- .../HtmlErrorsByReason.svelte | 2 +- .../htmlhintcomponents/RuleItem.svelte | 20 +- .../htmlhintcomponents/UpdateHTMLRules.svelte | 3 +- .../summaryitemcomponents/DetailsCard.svelte | 2 +- ui/src/containers/Rules.svelte | 2 +- ui/src/utils/utils.js | 260 +---------------- 9 files changed, 296 insertions(+), 292 deletions(-) rename {ui/src/utils => constants}/countries.js (100%) create mode 100644 constants/rules.js diff --git a/ui/src/utils/countries.js b/constants/countries.js similarity index 100% rename from ui/src/utils/countries.js rename to constants/countries.js diff --git a/constants/rules.js b/constants/rules.js new file mode 100644 index 00000000..3b0f367a --- /dev/null +++ b/constants/rules.js @@ -0,0 +1,274 @@ +import { countryCodes } from './countries.js'; + +export const RuleType = { + Warning: 'Warning', + Error: 'Error', +}; + +export const customOptionInputType = { + dropDown: 'dropDown', + singleTextBox: 'singleTextBox', + multipleTextBoxes: 'multipleTextBoxes', +}; + +export const htmlHintRules = [ + { + rule: 'tagname-lowercase', + displayName: 'Tags - Tag names must be lowercase', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/tagname-lowercase', + type: RuleType.Error, + }, + { + rule: 'tag-pair', + displayName: 'Tags - Tags must be paired', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/tag-pair', + type: RuleType.Error, + }, + { + rule: 'empty-tag-not-self-closed', + displayName: 'Tags - Empty tags should not be closed by itself', + ruleLink: + 'https://htmlhint.com/docs/user-guide/rules/empty-tag-not-self-closed', + type: RuleType.Warning, + }, + { + rule: 'id-class-ad-disabled', + displayName: "Tags - Id and class must not use the 'ad' keyword", + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/id-class-ad-disabled', + type: RuleType.Warning, + }, + { + rule: 'id-unique', + displayName: 'Tags - Id attributes must be unique', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/id-unique', + type: RuleType.Error, + }, + { + rule: 'attr-lowercase', + displayName: 'Attributes - Attribute names must be lowercase', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/attr-lowercase', + type: RuleType.Error, + }, + { + rule: 'attr-value-double-quotes', + displayName: 'Attributes - Attribute values must be in double quotes', + ruleLink: + 'https://htmlhint.com/docs/user-guide/rules/attr-value-double-quotes', + type: RuleType.Error, + }, + { + rule: 'attr-value-not-empty', + displayName: 'Attributes - All attributes must have values', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/attr-value-not-empty', + type: RuleType.Warning, + }, + { + rule: 'attr-no-duplication', + displayName: 'Attributes - Element cannot contain duplicate attributes', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/attr-no-duplication', + type: RuleType.Error, + }, + { + rule: 'attr-unsafe-chars', + displayName: 'Attributes - Attributes cannot contain unsafe characters', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/attr-unsafe-chars', + type: RuleType.Warning, + }, + { + rule: 'doctype-first', + displayName: 'Header - DOCTYPE must be declared first', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/doctype-first', + type: RuleType.Error, + }, + { + rule: 'title-require', + displayName: 'Header - Missing title tag', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/title-require', + type: RuleType.Error, + }, + { + rule: 'doctype-html5', + displayName: 'Header - DOCTYPE must be HTML5', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/doctype-html5', + type: RuleType.Warning, + }, + { + rule: 'head-script-disabled', + displayName: 'Syntax - Script tags cannot be used inside another tag', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/head-script-disabled', + type: RuleType.Warning, + }, + { + rule: 'spec-char-escape', + displayName: 'Syntax - Special characters must be escaped', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/spec-char-escape', + type: RuleType.Error, + }, + { + rule: 'style-disabled', + displayName: 'Syntax - Style tags should not be used outside of header', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/style-disabled', + type: RuleType.Warning, + }, + { + rule: 'inline-style-disabled', + displayName: 'Syntax - Inline styling should not be used', + ruleLink: + 'https://htmlhint.com/docs/user-guide/rules/inline-style-disabled', + type: RuleType.Warning, + }, + { + rule: 'inline-script-disabled', + displayName: 'Syntax - Inline scripts should not be used', + ruleLink: + 'https://htmlhint.com/docs/user-guide/rules/inline-script-disabled', + type: RuleType.Warning, + }, + { + rule: 'alt-require', + displayName: 'Images - Missing alt attribute', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/alt-require', + type: RuleType.Warning, + }, + // Disabled on 20/09/2023 + // { + // rule: "href-abs-or-rel", + // displayName: "Links - Href attribute must be either absolute or relative", + // ruleLink: "https://htmlhint.com/docs/user-guide/rules/href-abs-or-rel", + // type: RuleType.Warning + // }, + { + rule: 'src-not-empty', + displayName: 'Content - The image src attribute must have a value', + ruleLink: 'https://htmlhint.com/docs/user-guide/rules/src-not-empty', + type: RuleType.Error, + }, +]; + +export const customHtmlHintRules = [ + { + rule: 'code-block-missing-language', + displayName: 'Syntax - Code blocks must have a language', + ruleLink: 'https://www.ssw.com.au/rules/set-language-on-code-blocks', + type: RuleType.Warning, + }, + // { + // rule: "youtube-url-must-be-used-correctly", + // displayName: "Syntax - YouTube videos must not be under an embed URL", + // ruleLink: "https://ssw.com.au/rules/optimize-videos-for-youtube/", + // type: RuleType.Warning + // }, + { + rule: 'figure-must-use-the-right-code', + displayName: 'Syntax - Use the right HTML/CSS figure markup', + ruleLink: 'https://www.ssw.com.au/rules/use-the-right-html-figure-caption', + type: RuleType.Warning, + }, + { + rule: 'font-tag-must-not-be-used', + displayName: 'Tags - Font tags must not be used', + ruleLink: + 'https://www.ssw.com.au/rules/do-you-know-font-tags-are-no-longer-used', + type: RuleType.Warning, + }, + { + rule: 'url-must-not-have-click-here', + displayName: 'Content - Do not use the words ‘click here’', + ruleLink: 'https://www.ssw.com.au/rules/relevant-words-on-links', + type: RuleType.Warning, + }, + { + rule: 'grammar-scrum-terms', + displayName: 'Content - Use the correct Scrum terms', + ruleLink: 'https://www.ssw.com.au/rules/scrum-should-be-capitalized', + type: RuleType.Warning, + }, + { + rule: 'common-spelling-mistakes', + displayName: 'Content - Avoid common spelling and syntax mistakes', + ruleLink: 'https://www.ssw.com.au/rules/avoid-common-mistakes', + type: RuleType.Warning, + isEnableCustomOptions: true, + customOptionsMessage: 'Please enter the terms to be reported:', + customOptionInputType: customOptionInputType.multipleTextBoxes, + customOptionDefaultValue: [ + 'a.k.a', + 'A.K.A', + 'AKA', + 'e-mail', + 'EMail', + 'can not', + 'web site', + 'user name', + 'task bar', + ], + }, + { + rule: 'page-must-not-show-email-addresses', + displayName: 'Content - Text must not display any email addresses', + ruleLink: + 'https://www.ssw.com.au/rules/avoid-clear-text-email-addresses-in-web-pages', + type: RuleType.Warning, + }, + { + rule: 'phone-numbers-without-links', + displayName: 'Content - Phone numbers must be in hyperlinks', + ruleLink: + 'https://www.ssw.com.au/rules/do-you-know-to-hyperlink-your-phone-numbers', + type: RuleType.Warning, + isEnableCustomOptions: true, + customOptionsMessage: 'Please choose the country code:', + customOptionInputType: customOptionInputType.dropDown, + customOptionDropdownValues: countryCodes, + customOptionDefaultValue: 'AU', + }, + { + rule: 'use-unicode-hex-code-for-special-html-characters', + displayName: 'Content - Use Unicode Hex code for special HTML characters', + ruleLink: 'https://ssw.com.au/rules/html-unicode-hex-codes/', + type: RuleType.Error, + }, + { + rule: 'anchor-names-must-be-valid', + displayName: 'Links - Anchor links’ names must be valid', + ruleLink: 'https://www.ssw.com.au/rules/chose-efficient-anchor-names', + type: RuleType.Warning, + }, + { + rule: 'url-must-be-formatted-correctly', + displayName: + 'Links - URLs should not include full stop or slash at the end', + ruleLink: + 'https://ssw.com.au/rules/no-full-stop-or-slash-at-the-end-of-urls/', + type: RuleType.Warning, + }, + { + rule: 'detect-absolute-references-url-path-correctly', + displayName: 'Links - Avoid absolute internal URLs', + ruleLink: 'https://ssw.com.au/rules/avoid-absolute-internal-links/', + type: RuleType.Warning, + isEnableCustomOptions: true, + customOptionsMessage: 'Please enter the website internal URL:', + customOptionInputType: customOptionInputType.singleTextBox, + customOptionInputValueType: 'url', + }, + { + rule: 'url-must-not-have-space', + displayName: 'Links - URLs must not have space', + ruleLink: 'https://www.ssw.com.au/rules/use-dashes-in-urls', + type: RuleType.Warning, + }, + { + rule: 'link-must-not-show-unc', + displayName: 'Links - URLs must not have UNC paths', + ruleLink: 'https://www.ssw.com.au/rules/urls-must-not-have-unc-paths/', + type: RuleType.Warning, + }, + { + rule: 'meta-tag-must-not-redirect', + displayName: 'Header - Must not refresh or redirect', + ruleLink: 'https://rules.sonarsource.com/html/RSPEC-1094', + type: RuleType.Warning, + }, + // Add new rule id below +]; diff --git a/docker/customHtmlRules.js b/docker/customHtmlRules.js index 61449a4c..73899226 100644 --- a/docker/customHtmlRules.js +++ b/docker/customHtmlRules.js @@ -1,6 +1,7 @@ const { getCustomHtmlRuleOptions } = require("./api"); const HTMLHint = require("htmlhint").default; const findPhoneNumbersInText = require('libphonenumber-js').findPhoneNumbersInText; +const { customHtmlHintRules } = require("../constants/rules.js"); exports.addCustomHtmlRule = async (apiToken, url) => { const customRuleOptions = await getCustomHtmlRuleOptions(apiToken, url); @@ -446,24 +447,12 @@ exports.addCustomHtmlRule = async (apiToken, url) => { const ruleId = "common-spelling-mistakes"; let optionValue = customRuleOptions?.find(option => option.ruleId === ruleId)?.optionValue; let customOptions = []; + const defaultValue = customHtmlHintRules?.find(rule => rule.rule === ruleId)?.customOptionDefaultValue || []; // Check if custom options exist in this rule if (optionValue?.length) { customOptions = optionValue.split(',').filter(i => i); } - var spellings = - customOptions.length ? - optionValue : - [ - "a.k.a", - "A.K.A", - "AKA", - "e-mail", - "EMail", - "can not", - "web site", - "user name", - "task bar" - ]; + const spellings = customOptions.length ? optionValue : defaultValue; if (event.raw) { const pageContent = event.raw; @@ -505,10 +494,12 @@ exports.addCustomHtmlRule = async (apiToken, url) => { "Checks for phone numbers that aren't in hyperlinks with a \"tel:\" prefix.", init: function (parser, reporter) { const self = this; + const ruleId = "phone-numbers-without-links"; let isInCodeBlock = false; let optionValue = ''; + const defaultValue = customHtmlHintRules?.find(rule => rule.rule === ruleId)?.customOptionDefaultValue || ''; + parser.addListener("tagstart", (event) => { - const ruleId = "phone-numbers-without-links"; // Check if custom options exist in this rule if (customRuleOptions && customRuleOptions.length > 0 && customRuleOptions.filter(option => option.ruleId === ruleId).length > 0) { optionValue = customRuleOptions.find(option => option.ruleId === ruleId).optionValue @@ -518,17 +509,19 @@ exports.addCustomHtmlRule = async (apiToken, url) => { isInCodeBlock = true; } }); + parser.addListener("tagend", (event) => { const tagName = event.tagName.toLowerCase(); if (tagName === "code") { isInCodeBlock = false; } }); + parser.addListener("text", (event) => { // Replace "." and "/" characters to avoid false positives when parsing phone numbers const text = event.raw?.replace(/\.|\//g, "_"); if (text && event.lastEvent) { - const foundPhoneNumbers = findPhoneNumbersInText(text, optionValue.length > 0 ? optionValue : 'AU'); + const foundPhoneNumbers = findPhoneNumbersInText(text, optionValue.length > 0 ? optionValue : defaultValue); foundPhoneNumbers.forEach((phone) => { const pageContent = event.lastEvent.raw; diff --git a/ui/src/components/htmlhintcomponents/HtmlErrorsByReason.svelte b/ui/src/components/htmlhintcomponents/HtmlErrorsByReason.svelte index 9176b4da..d519dbb8 100644 --- a/ui/src/components/htmlhintcomponents/HtmlErrorsByReason.svelte +++ b/ui/src/components/htmlhintcomponents/HtmlErrorsByReason.svelte @@ -14,7 +14,7 @@ import { fade } from "svelte/transition"; import { createEventDispatcher } from "svelte"; import Icon from "../misccomponents/Icon.svelte"; - import { htmlHintRules, customHtmlHintRules } from "../../utils/utils.js"; + import { htmlHintRules, customHtmlHintRules } from "../../../../constants/rules.js"; import LoadingCircle from "../misccomponents/LoadingCircle.svelte"; export let errors = []; diff --git a/ui/src/components/htmlhintcomponents/RuleItem.svelte b/ui/src/components/htmlhintcomponents/RuleItem.svelte index 40a54d90..413a6a1e 100644 --- a/ui/src/components/htmlhintcomponents/RuleItem.svelte +++ b/ui/src/components/htmlhintcomponents/RuleItem.svelte @@ -1,5 +1,6 @@