diff --git a/packages/eslint-plugin/lib/rules/no-accesskey-attrs.js b/packages/eslint-plugin/lib/rules/no-accesskey-attrs.js index 6c270489..9ecbc5b4 100644 --- a/packages/eslint-plugin/lib/rules/no-accesskey-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-accesskey-attrs.js @@ -7,6 +7,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { UNEXPECTED: "unexpected", @@ -33,19 +34,22 @@ module.exports = { }, create(context) { - return { - /** - * @param {TagNode | ScriptTagNode | StyleTagNode} node - */ - [["Tag", "ScriptTag", "StyleTag"].join(",")](node) { - const accessKeyAttr = findAttr(node, "accesskey"); - if (accessKeyAttr) { - context.report({ - node: accessKeyAttr, - messageId: MESSAGE_IDS.UNEXPECTED, - }); - } - }, - }; + /** + * @param {TagNode | ScriptTagNode | StyleTagNode} node + */ + function check(node) { + const accessKeyAttr = findAttr(node, "accesskey"); + if (accessKeyAttr) { + context.report({ + node: accessKeyAttr, + messageId: MESSAGE_IDS.UNEXPECTED, + }); + } + } + return createVisitors(context, { + Tag: check, + ScriptTag: check, + StyleTag: check, + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/no-aria-hidden-body.js b/packages/eslint-plugin/lib/rules/no-aria-hidden-body.js index 7a23625a..4810d026 100644 --- a/packages/eslint-plugin/lib/rules/no-aria-hidden-body.js +++ b/packages/eslint-plugin/lib/rules/no-aria-hidden-body.js @@ -4,6 +4,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { UNEXPECTED: "unexpected", @@ -31,24 +32,29 @@ module.exports = { }, create(context) { - return { + return createVisitors(context, { Tag(node) { if (node.name !== "body") { return; } const ariaHiddenAttr = findAttr(node, "aria-hidden"); - if (ariaHiddenAttr) { - if ( - (ariaHiddenAttr.value && ariaHiddenAttr.value.value !== "false") || - !ariaHiddenAttr.value - ) { - context.report({ - node: ariaHiddenAttr, - messageId: MESSAGE_IDS.UNEXPECTED, - }); - } + if (!ariaHiddenAttr) { + return; + } + if (ariaHiddenAttr.value && ariaHiddenAttr.value.templates.length) { + return; + } + + if ( + (ariaHiddenAttr.value && ariaHiddenAttr.value.value !== "false") || + !ariaHiddenAttr.value + ) { + context.report({ + node: ariaHiddenAttr, + messageId: MESSAGE_IDS.UNEXPECTED, + }); } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/no-duplicate-id.js b/packages/eslint-plugin/lib/rules/no-duplicate-id.js index 2c8a54ef..10057cb8 100644 --- a/packages/eslint-plugin/lib/rules/no-duplicate-id.js +++ b/packages/eslint-plugin/lib/rules/no-duplicate-id.js @@ -3,10 +3,17 @@ * @typedef { import("../types").TagNode } TagNode * @typedef { import("../types").StyleTagNode } StyleTagNode * @typedef { import("../types").ScriptTagNode } ScriptTagNode + * @typedef { import("es-html-parser").AttributeValueNode } AttributeValueNode */ +const { parse } = require("@html-eslint/template-parser"); const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { + shouldCheckTaggedTemplateExpression, + shouldCheckTemplateLiteral, +} = require("./utils/settings"); +const { getSourceCode } = require("./utils/source-code"); const MESSAGE_IDS = { DUPLICATE_ID: "duplicateId", @@ -33,37 +40,71 @@ module.exports = { }, create(context) { - const IdAttrsMap = new Map(); - return { + const htmlIdAttrsMap = new Map(); + /** + * @param {Map} map + */ + function createTagVisitor(map) { /** - * @param {TagNode | ScriptTagNode | StyleTagNode} node - * @returns + * @param {TagNode} node */ - Tag(node) { + return function (node) { if (!node.attributes || node.attributes.length <= 0) { return; } const idAttr = findAttr(node, "id"); if (idAttr && idAttr.value) { - if (!IdAttrsMap.has(idAttr.value.value)) { - IdAttrsMap.set(idAttr.value.value, []); + if (!map.has(idAttr.value.value)) { + map.set(idAttr.value.value, []); + } + const nodes = map.get(idAttr.value.value); + if (nodes) { + nodes.push(idAttr.value); } - const nodes = IdAttrsMap.get(idAttr.value.value); - nodes.push(idAttr.value); } - }, - "Program:exit"() { - IdAttrsMap.forEach((attrs) => { - if (Array.isArray(attrs) && attrs.length > 1) { - attrs.forEach((attr) => { - context.report({ - node: attr, - data: { id: attr.value }, - messageId: MESSAGE_IDS.DUPLICATE_ID, - }); + }; + } + + /** + * + * @param {Map} map + */ + function report(map) { + map.forEach((attrs) => { + if (Array.isArray(attrs) && attrs.length > 1) { + attrs.forEach((attr) => { + context.report({ + node: attr, + data: { id: attr.value }, + messageId: MESSAGE_IDS.DUPLICATE_ID, }); - } - }); + }); + } + }); + } + + return { + Tag: createTagVisitor(htmlIdAttrsMap), + "Program:exit"() { + report(htmlIdAttrsMap); + }, + TaggedTemplateExpression(node) { + const idAttrsMap = new Map(); + if (shouldCheckTaggedTemplateExpression(node, context)) { + parse(node.quasi, getSourceCode(context), { + Tag: createTagVisitor(idAttrsMap), + }); + } + report(idAttrsMap); + }, + TemplateLiteral(node) { + const idAttrsMap = new Map(); + if (shouldCheckTemplateLiteral(node, context)) { + parse(node, getSourceCode(context), { + Tag: createTagVisitor(idAttrsMap), + }); + } + report(idAttrsMap); }, }; }, diff --git a/packages/eslint-plugin/lib/rules/no-positive-tabindex.js b/packages/eslint-plugin/lib/rules/no-positive-tabindex.js index 2dd6192d..b0405e29 100644 --- a/packages/eslint-plugin/lib/rules/no-positive-tabindex.js +++ b/packages/eslint-plugin/lib/rules/no-positive-tabindex.js @@ -7,6 +7,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { UNEXPECTED: "unexpected", @@ -33,23 +34,28 @@ module.exports = { }, create(context) { - return { - /** - * @param {TagNode | StyleTagNode | ScriptTagNode} node - */ - [["Tag", "StyleTag", "ScriptTag"].join(",")](node) { - const tabIndexAttr = findAttr(node, "tabindex"); - if ( - tabIndexAttr && - tabIndexAttr.value && - parseInt(tabIndexAttr.value.value, 10) > 0 - ) { - context.report({ - node: tabIndexAttr, - messageId: MESSAGE_IDS.UNEXPECTED, - }); - } - }, - }; + /** + * @param {TagNode | StyleTagNode | ScriptTagNode} node + */ + function check(node) { + const tabIndexAttr = findAttr(node, "tabindex"); + if ( + tabIndexAttr && + tabIndexAttr.value && + !tabIndexAttr.value.templates.length && + parseInt(tabIndexAttr.value.value, 10) > 0 + ) { + context.report({ + node: tabIndexAttr, + messageId: MESSAGE_IDS.UNEXPECTED, + }); + } + } + + return createVisitors(context, { + Tag: check, + StyleTag: check, + ScriptTag: check, + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/no-restricted-attr-values.js b/packages/eslint-plugin/lib/rules/no-restricted-attr-values.js index 4b112d77..ae302d40 100644 --- a/packages/eslint-plugin/lib/rules/no-restricted-attr-values.js +++ b/packages/eslint-plugin/lib/rules/no-restricted-attr-values.js @@ -8,6 +8,7 @@ */ const { RULE_CATEGORY } = require("../constants"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { RESTRICTED: "restricted", @@ -64,54 +65,57 @@ module.exports = { */ const options = context.options; const checkers = options.map((option) => new PatternChecker(option)); - - return { - /** - * @param {TagNode | StyleTagNode | ScriptTagNode} node - */ - [["Tag", "StyleTag", "ScriptTag"].join(",")](node) { - node.attributes.forEach((attr) => { - if ( - !attr.key || - !attr.key.value || - !attr.value || - typeof attr.value.value !== "string" - ) { - return; - } - - const matched = checkers.find( - (checker) => - attr.value && checker.test(attr.key.value, attr.value.value) - ); - - if (!matched) { - return; - } - - /** - * @type {{node: AttributeNode, message: string, messageId?: string}} - */ - const result = { - node: attr, - message: "", - }; - - const customMessage = matched.getMessage(); - - if (customMessage) { - result.message = customMessage; - } else { - result.messageId = MESSAGE_IDS.RESTRICTED; - } - - context.report({ - ...result, - data: { attrValuePatterns: attr.value.value }, - }); + /** + * @param {TagNode | StyleTagNode | ScriptTagNode} node + */ + function check(node) { + node.attributes.forEach((attr) => { + if ( + !attr.key || + !attr.key.value || + !attr.value || + typeof attr.value.value !== "string" + ) { + return; + } + + const matched = checkers.find( + (checker) => + attr.value && checker.test(attr.key.value, attr.value.value) + ); + + if (!matched) { + return; + } + + /** + * @type {{node: AttributeNode, message: string, messageId?: string}} + */ + const result = { + node: attr, + message: "", + }; + + const customMessage = matched.getMessage(); + + if (customMessage) { + result.message = customMessage; + } else { + result.messageId = MESSAGE_IDS.RESTRICTED; + } + + context.report({ + ...result, + data: { attrValuePatterns: attr.value.value }, }); - }, - }; + }); + } + + return createVisitors(context, { + Tag: check, + StyleTag: check, + ScriptTag: check, + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/no-restricted-attrs.js b/packages/eslint-plugin/lib/rules/no-restricted-attrs.js index a5f4b298..910d1847 100644 --- a/packages/eslint-plugin/lib/rules/no-restricted-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-restricted-attrs.js @@ -9,6 +9,7 @@ const { NODE_TYPES } = require("@html-eslint/parser"); const { RULE_CATEGORY } = require("../constants"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { RESTRICTED: "restricted", @@ -65,52 +66,56 @@ module.exports = { const options = context.options; const checkers = options.map((option) => new PatternChecker(option)); - return { - /** - * @param {TagNode | StyleTagNode | ScriptTagNode} node - */ - [["Tag", "StyleTag", "ScriptTag"].join(",")](node) { - const tagName = - node.type === NODE_TYPES.Tag - ? node.name - : node.type === NODE_TYPES.ScriptTag - ? "script" - : "style"; - node.attributes.forEach((attr) => { - if (!attr.key || !attr.key.value) { - return; - } - const matched = checkers.find((checker) => - checker.test(tagName, attr.key.value) - ); - - if (!matched) { - return; - } - - /** - * @type {{node: AttributeNode, message: string, messageId?: string}} - */ - const result = { - node: attr, - message: "", - }; - - const customMessage = matched.getMessage(); - - if (customMessage) { - result.message = customMessage; - } else { - result.messageId = MESSAGE_IDS.RESTRICTED; - } - - context.report({ - ...result, - data: { attr: attr.key.value }, - }); + /** + * @param {TagNode | StyleTagNode | ScriptTagNode} node + */ + function check(node) { + const tagName = + node.type === NODE_TYPES.Tag + ? node.name + : node.type === NODE_TYPES.ScriptTag + ? "script" + : "style"; + node.attributes.forEach((attr) => { + if (!attr.key || !attr.key.value) { + return; + } + const matched = checkers.find((checker) => + checker.test(tagName, attr.key.value) + ); + + if (!matched) { + return; + } + + /** + * @type {{node: AttributeNode, message: string, messageId?: string}} + */ + const result = { + node: attr, + message: "", + }; + + const customMessage = matched.getMessage(); + + if (customMessage) { + result.message = customMessage; + } else { + result.messageId = MESSAGE_IDS.RESTRICTED; + } + + context.report({ + ...result, + data: { attr: attr.key.value }, }); - }, - }; + }); + } + + return createVisitors(context, { + Tag: check, + StyleTag: check, + ScriptTag: check, + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/no-script-style-type.js b/packages/eslint-plugin/lib/rules/no-script-style-type.js index 2ad89d1e..9273f9ca 100644 --- a/packages/eslint-plugin/lib/rules/no-script-style-type.js +++ b/packages/eslint-plugin/lib/rules/no-script-style-type.js @@ -7,6 +7,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { UNNECESSARY: "unnecessary", @@ -56,7 +57,7 @@ module.exports = { }); } } - return { + return createVisitors(context, { ScriptTag(node) { check(node, "text/javascript"); }, @@ -71,6 +72,6 @@ module.exports = { } } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/no-target-blank.js b/packages/eslint-plugin/lib/rules/no-target-blank.js index c39d41a7..856e0737 100644 --- a/packages/eslint-plugin/lib/rules/no-target-blank.js +++ b/packages/eslint-plugin/lib/rules/no-target-blank.js @@ -4,6 +4,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { MISSING: "missing", @@ -38,7 +39,7 @@ module.exports = { function isExternalLink(link) { return /^(?:\w+:|\/\/)/.test(link); } - return { + return createVisitors(context, { Tag(node) { if (node.name !== "a") { return; @@ -49,6 +50,10 @@ module.exports = { const href = findAttr(node, "href"); if (href && href.value && isExternalLink(href.value.value)) { const rel = findAttr(node, "rel"); + if (rel && rel.value && rel.value.templates.length) { + return; + } + if (!rel || !rel.value || !rel.value.value.includes("noreferrer")) { context.report({ node: target, @@ -58,6 +63,6 @@ module.exports = { } } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/quotes.js b/packages/eslint-plugin/lib/rules/quotes.js index 810aa8c9..9c684614 100644 --- a/packages/eslint-plugin/lib/rules/quotes.js +++ b/packages/eslint-plugin/lib/rules/quotes.js @@ -8,6 +8,7 @@ */ const { RULE_CATEGORY } = require("../constants"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { UNEXPECTED: "unexpected", @@ -131,14 +132,17 @@ module.exports = { }); } } + /** + * @param {TagNode | ScriptTagNode | StyleTagNode} node + */ + function check(node) { + node.attributes.forEach((attr) => checkQuotes(attr)); + } - return { - /** - * @param {TagNode | ScriptTagNode | StyleTagNode} node - */ - [["Tag", "ScriptTag", "StyleTag"].join(",")](node) { - node.attributes.forEach((attr) => checkQuotes(attr)); - }, - }; + return createVisitors(context, { + Tag: check, + ScriptTag: check, + StyleTag: check, + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/require-attrs.js b/packages/eslint-plugin/lib/rules/require-attrs.js index 5b6e7c2c..771a2d18 100644 --- a/packages/eslint-plugin/lib/rules/require-attrs.js +++ b/packages/eslint-plugin/lib/rules/require-attrs.js @@ -7,6 +7,7 @@ const { NODE_TYPES } = require("@html-eslint/parser"); const { RULE_CATEGORY } = require("../constants"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { MISSING: "missing", @@ -103,25 +104,32 @@ module.exports = { }); } - return { - /** - * @param {StyleTagNode | ScriptTagNode} node - * @returns - */ - [["StyleTag", "ScriptTag"].join(",")](node) { - const tagName = node.type === NODE_TYPES.StyleTag ? "style" : "script"; - if (!tagOptionsMap.has(tagName)) { - return; - } - check(node, tagName); - }, - Tag(node) { - const tagName = node.name.toLowerCase(); - if (!tagOptionsMap.has(tagName)) { - return; - } - check(node, tagName); - }, - }; + /** + * @param {StyleTagNode | ScriptTagNode} node + */ + function checkStyleOrScript(node) { + const tagName = node.type === NODE_TYPES.StyleTag ? "style" : "script"; + if (!tagOptionsMap.has(tagName)) { + return; + } + check(node, tagName); + } + + /** + * @param {TagNode} node + */ + function checkTag(node) { + const tagName = node.name.toLowerCase(); + if (!tagOptionsMap.has(tagName)) { + return; + } + check(node, tagName); + } + + return createVisitors(context, { + StyleTag: checkStyleOrScript, + ScriptTag: checkStyleOrScript, + Tag: checkTag, + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/require-button-type.js b/packages/eslint-plugin/lib/rules/require-button-type.js index fa0ec5f3..67849bb5 100644 --- a/packages/eslint-plugin/lib/rules/require-button-type.js +++ b/packages/eslint-plugin/lib/rules/require-button-type.js @@ -4,6 +4,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { MISSING: "missing", @@ -35,7 +36,7 @@ module.exports = { }, create(context) { - return { + return createVisitors(context, { Tag(node) { if (node.name !== "button") { return; @@ -46,7 +47,10 @@ module.exports = { node: node.openStart, messageId: MESSAGE_IDS.MISSING, }); - } else if (!VALID_BUTTON_TYPES_SET.has(typeAttr.value.value)) { + } else if ( + !VALID_BUTTON_TYPES_SET.has(typeAttr.value.value) && + !typeAttr.value.templates.length + ) { context.report({ node: typeAttr, messageId: MESSAGE_IDS.INVALID, @@ -56,6 +60,6 @@ module.exports = { }); } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/require-closing-tags.js b/packages/eslint-plugin/lib/rules/require-closing-tags.js index 8685d5fb..106c2fcb 100644 --- a/packages/eslint-plugin/lib/rules/require-closing-tags.js +++ b/packages/eslint-plugin/lib/rules/require-closing-tags.js @@ -4,6 +4,7 @@ */ const { RULE_CATEGORY, VOID_ELEMENTS } = require("../constants"); +const { createVisitors } = require("./utils/visitors"); const VOID_ELEMENTS_SET = new Set(VOID_ELEMENTS); @@ -125,7 +126,7 @@ module.exports = { } } - return { + return createVisitors(context, { Tag(node) { const isVoidElement = VOID_ELEMENTS_SET.has(node.name); const isSelfClosingCustomElement = !!selfClosingCustomPatterns.some( @@ -156,6 +157,6 @@ module.exports = { foreignContext.pop(); } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/require-frame-title.js b/packages/eslint-plugin/lib/rules/require-frame-title.js index 7493bac5..cf37e92b 100644 --- a/packages/eslint-plugin/lib/rules/require-frame-title.js +++ b/packages/eslint-plugin/lib/rules/require-frame-title.js @@ -4,6 +4,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { MISSING: "missing", @@ -32,7 +33,7 @@ module.exports = { }, create(context) { - return { + return createVisitors(context, { Tag(node) { if (node.name !== "frame" && node.name !== "iframe") { return; @@ -51,6 +52,6 @@ module.exports = { }); } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/require-img-alt.js b/packages/eslint-plugin/lib/rules/require-img-alt.js index f6fd1b17..ae024f90 100644 --- a/packages/eslint-plugin/lib/rules/require-img-alt.js +++ b/packages/eslint-plugin/lib/rules/require-img-alt.js @@ -4,6 +4,7 @@ */ const { RULE_CATEGORY } = require("../constants"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { MISSING_ALT: "missingAlt", @@ -48,7 +49,7 @@ module.exports = { context.options[0].substitute) || []; - return { + return createVisitors(context, { Tag(node) { if (node.name !== "img") { return; @@ -66,7 +67,7 @@ module.exports = { }); } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/require-lang.js b/packages/eslint-plugin/lib/rules/require-lang.js index 8864b2aa..21c8646b 100644 --- a/packages/eslint-plugin/lib/rules/require-lang.js +++ b/packages/eslint-plugin/lib/rules/require-lang.js @@ -4,6 +4,7 @@ const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { MISSING: "missing", @@ -32,7 +33,7 @@ module.exports = { }, create(context) { - return { + return createVisitors(context, { Tag(node) { if (node.name !== "html") { return; @@ -56,6 +57,6 @@ module.exports = { }); } }, - }; + }); }, }; diff --git a/packages/eslint-plugin/lib/rules/sort-attrs.js b/packages/eslint-plugin/lib/rules/sort-attrs.js index 3f403c61..d8f0764e 100644 --- a/packages/eslint-plugin/lib/rules/sort-attrs.js +++ b/packages/eslint-plugin/lib/rules/sort-attrs.js @@ -6,6 +6,8 @@ */ const { RULE_CATEGORY } = require("../constants"); +const { getSourceCode } = require("./utils/source-code"); +const { createVisitors } = require("./utils/visitors"); const MESSAGE_IDS = { UNSORTED: "unsorted", @@ -43,7 +45,7 @@ module.exports = { }, }, create(context) { - const sourceCode = context.getSourceCode(); + const sourceCode = getSourceCode(context); const option = context.options[0] || { priority: ["id", "type", "class", "style"], }; @@ -150,7 +152,7 @@ module.exports = { }); } - return { + return createVisitors(context, { ScriptTag(node) { checkSorting(node.attributes); }, @@ -160,6 +162,6 @@ module.exports = { StyleTag(node) { checkSorting(node.attributes); }, - }; + }); }, }; diff --git a/packages/eslint-plugin/tests/rules/no-accesskey-attrs.test.js b/packages/eslint-plugin/tests/rules/no-accesskey-attrs.test.js index b537a286..787ed155 100644 --- a/packages/eslint-plugin/tests/rules/no-accesskey-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/no-accesskey-attrs.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-accesskey-attrs"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-accesskey-attrs", rule, { valid: [ @@ -20,3 +21,21 @@ ruleTester.run("no-accesskey-attrs", rule, { }, ], }); + +templateRuleTester.run("[template] no-accesskey-attrs", rule, { + valid: [ + { + code: `html\`
\``, + }, + ], + invalid: [ + { + code: `html\`
\``, + errors: [ + { + message: "Unexpected use of accesskey attribute.", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-aria-hidden-body.test.js b/packages/eslint-plugin/tests/rules/no-aria-hidden-body.test.js index b269bc73..8f9573dd 100644 --- a/packages/eslint-plugin/tests/rules/no-aria-hidden-body.test.js +++ b/packages/eslint-plugin/tests/rules/no-aria-hidden-body.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-aria-hidden-body"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-aria-hidden-body", rule, { valid: [ @@ -34,3 +35,24 @@ ruleTester.run("no-aria-hidden-body", rule, { }, ], }); + +templateRuleTester.run("[template] no-aria-hidden-body", rule, { + valid: [ + { + code: `html\`
\``, + }, + { + code: `html\` \``, + }, + ], + invalid: [ + { + code: `html\` \``, + errors: [ + { + message: "Unexpected aria-hidden on body tag.", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-duplicate-id.test.js b/packages/eslint-plugin/tests/rules/no-duplicate-id.test.js index f871ac0f..c987a553 100644 --- a/packages/eslint-plugin/tests/rules/no-duplicate-id.test.js +++ b/packages/eslint-plugin/tests/rules/no-duplicate-id.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-duplicate-id"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-duplicate-id", rule, { valid: [ @@ -58,3 +59,62 @@ ruleTester.run("no-duplicate-id", rule, { }, ], }); + +templateRuleTester.run("[template] no-duplicate-id", rule, { + valid: [ + { + code: ` +html\` + +
+
+ +\` +`, + }, + ], + invalid: [ + { + code: ` +html\` + +
+ + +\` +`, + + errors: [ + { + messageId: "duplicateId", + line: 4, + }, + { + messageId: "duplicateId", + line: 5, + }, + ], + }, + { + code: ` +html\` + +
+
+ +\` +`, + + errors: [ + { + messageId: "duplicateId", + line: 4, + }, + { + messageId: "duplicateId", + line: 5, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-positive-tabindex.test.js b/packages/eslint-plugin/tests/rules/no-positive-tabindex.test.js index 0f513795..21adba68 100644 --- a/packages/eslint-plugin/tests/rules/no-positive-tabindex.test.js +++ b/packages/eslint-plugin/tests/rules/no-positive-tabindex.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-positive-tabindex"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-positive-tabindex", rule, { valid: [ @@ -31,3 +32,24 @@ ruleTester.run("no-positive-tabindex", rule, { }, ], }); + +templateRuleTester.run("[template] no-positive-tabindex", rule, { + valid: [ + { + code: `html\`foo\``, + }, + { + code: `html\`foo\``, + }, + ], + invalid: [ + { + code: `html\`foo\``, + errors: [ + { + messageId: "unexpected", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-restricted-attr-values.test.js b/packages/eslint-plugin/tests/rules/no-restricted-attr-values.test.js index 97b73d26..2aea1a19 100644 --- a/packages/eslint-plugin/tests/rules/no-restricted-attr-values.test.js +++ b/packages/eslint-plugin/tests/rules/no-restricted-attr-values.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-restricted-attr-values"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-restricted-attr-values", rule, { valid: [ @@ -112,3 +113,37 @@ ruleTester.run("no-restricted-attr-values", rule, { }, ], }); + +templateRuleTester.run("[template] no-restricted-attr-values", rule, { + valid: [ + { + code: `html\`
\``, + options: [ + { + attrPatterns: [".*"], + attrValuePatterns: ["data-.*"], + }, + ], + }, + ], + invalid: [ + { + code: `html\`
\``, + options: [ + { + attrPatterns: ["alt", "class"], + attrValuePatterns: ["^foo$"], + message: "no foo for alt or class", + }, + ], + errors: [ + { + message: "no foo for alt or class", + }, + { + message: "no foo for alt or class", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-restricted-attrs.test.js b/packages/eslint-plugin/tests/rules/no-restricted-attrs.test.js index a636f29f..2e190c03 100644 --- a/packages/eslint-plugin/tests/rules/no-restricted-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/no-restricted-attrs.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-restricted-attrs"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-restricted-attrs", rule, { valid: [ @@ -95,3 +96,34 @@ ruleTester.run("no-restricted-attrs", rule, { }, ], }); + +templateRuleTester.run("[template] no-restricted-attrs", rule, { + valid: [ + { + code: `html\`
\``, + options: [ + { + tagPatterns: [".*"], + attrPatterns: ["data-.*"], + }, + ], + }, + ], + invalid: [ + { + code: `html\`
\``, + options: [ + { + tagPatterns: [".*"], + attrPatterns: ["data-.*"], + message: "please do not use 'data-x'", + }, + ], + errors: [ + { + message: "please do not use 'data-x'", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-script-style-type.test.js b/packages/eslint-plugin/tests/rules/no-script-style-type.test.js index 301f42af..699cb4d4 100644 --- a/packages/eslint-plugin/tests/rules/no-script-style-type.test.js +++ b/packages/eslint-plugin/tests/rules/no-script-style-type.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-script-style-type"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-script-style-type", rule, { valid: [ @@ -45,3 +46,22 @@ ruleTester.run("no-script-style-type", rule, { }, ], }); + +templateRuleTester.run("[template] no-script-style-type", rule, { + valid: [ + { + code: 'html`"`', + }, + ], + invalid: [ + { + code: 'html``', + output: 'html``', + errors: [ + { + messageId: "unnecessary", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-target-blank.test.js b/packages/eslint-plugin/tests/rules/no-target-blank.test.js index 4d7a2ac7..3156d2dd 100644 --- a/packages/eslint-plugin/tests/rules/no-target-blank.test.js +++ b/packages/eslint-plugin/tests/rules/no-target-blank.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/no-target-blank"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-target-blank", rule, { valid: [ @@ -29,3 +30,27 @@ ruleTester.run("no-target-blank", rule, { }, ], }); + +templateRuleTester.run("[template] no-target-blank", rule, { + valid: [ + { + code: "html`
`", + }, + { + code: "html``", + }, + { + code: "html``", + }, + ], + invalid: [ + { + code: "html``", + errors: [ + { + message: 'Missing `rel="noreferrer"` attribute in a tag.', + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/quotes.test.js b/packages/eslint-plugin/tests/rules/quotes.test.js index 68ab1104..4f550b38 100644 --- a/packages/eslint-plugin/tests/rules/quotes.test.js +++ b/packages/eslint-plugin/tests/rules/quotes.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/quotes"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("quotes", rule, { valid: [ @@ -148,3 +149,34 @@ foo>`, }, ], }); + +templateRuleTester.run("[template] quotes", rule, { + valid: [ + { + code: `html\`
\``, + }, + { + code: `html\`
\``, + }, + ], + invalid: [ + { + code: `html\`
\``, + output: `html\`
\``, + errors: [ + { + messageId: "unexpected", + }, + ], + }, + { + code: `html\`
\``, + output: `html\`
\``, + errors: [ + { + messageId: "unexpected", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-attrs.test.js b/packages/eslint-plugin/tests/rules/require-attrs.test.js index 0c6f905a..a9afcda0 100644 --- a/packages/eslint-plugin/tests/rules/require-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/require-attrs.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/require-attrs"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("require-attrs", rule, { valid: [ @@ -250,3 +251,46 @@ ruleTester.run("require-attrs", rule, { }, ], }); + +templateRuleTester.run("[template] require-attrs", rule, { + valid: [ + { + code: "html``", + options: [ + { + tag: "svg", + attr: "viewBox", + }, + ], + }, + ], + invalid: [ + { + code: 'html``', + options: [ + { + tag: "img", + attr: "alt", + }, + { + tag: "img", + attr: "class", + value: "img", + }, + ], + errors: [ + { + line: 1, + column: 6, + message: "Missing 'alt' attributes for 'img' tag", + }, + { + line: 1, + column: 11, + endColumn: 24, + message: "Unexpected 'class' attributes value. 'img' is expected", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-button-type.test.js b/packages/eslint-plugin/tests/rules/require-button-type.test.js index 60467ce6..0fc36c47 100644 --- a/packages/eslint-plugin/tests/rules/require-button-type.test.js +++ b/packages/eslint-plugin/tests/rules/require-button-type.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/require-button-type"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("require-button-type", rule, { valid: [ @@ -38,3 +39,26 @@ ruleTester.run("require-button-type", rule, { }, ], }); + +templateRuleTester.run("[template] require-button-type", rule, { + valid: [ + { + code: 'html``', + }, + { + code: 'html``', + }, + ], + invalid: [ + { + code: "html``", + errors: [ + { + messageId: "missing", + line: 1, + column: 6, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-closing-tags.test.js b/packages/eslint-plugin/tests/rules/require-closing-tags.test.js index 43cc256d..546f211a 100644 --- a/packages/eslint-plugin/tests/rules/require-closing-tags.test.js +++ b/packages/eslint-plugin/tests/rules/require-closing-tags.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/require-closing-tags"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("require-closing-tags", rule, { valid: [ @@ -216,3 +217,21 @@ ruleTester.run("require-closing-tags", rule, { }, ], }); + +templateRuleTester.run("[template] require-closing-tags", rule, { + valid: [ + { + code: "html`
`", + }, + ], + invalid: [ + { + code: "html`
`", + errors: [ + { + messageId: "missing", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-frame-title.test.js b/packages/eslint-plugin/tests/rules/require-frame-title.test.js index 4e62b7dd..36b29eae 100644 --- a/packages/eslint-plugin/tests/rules/require-frame-title.test.js +++ b/packages/eslint-plugin/tests/rules/require-frame-title.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/require-frame-title"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("require-frame-title", rule, { valid: [ @@ -77,3 +78,24 @@ ruleTester.run("require-frame-title", rule, { }, ], }); + +templateRuleTester.run("[template] require-iframe-title", rule, { + valid: [ + { + code: 'html``', + }, + { + code: 'html``', + }, + ], + invalid: [ + { + code: 'html``', + errors: [ + { + messageId: "unexpected", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-img-alt.test.js b/packages/eslint-plugin/tests/rules/require-img-alt.test.js index 15cdf2c1..af356723 100644 --- a/packages/eslint-plugin/tests/rules/require-img-alt.test.js +++ b/packages/eslint-plugin/tests/rules/require-img-alt.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/require-img-alt"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("require-img-alt", rule, { valid: [ @@ -105,3 +106,28 @@ ruleTester.run("require-img-alt", rule, { }, ], }); + +templateRuleTester.run("[template] require-img-alt", rule, { + valid: [ + { + code: `html\`image description\``, + }, + { + code: `html\`\${alt}\``, + }, + ], + invalid: [ + { + code: `html\`\``, + errors: [ + { + messageId: "missingAlt", + line: 1, + column: 6, + endColumn: 30, + endLine: 1, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-lang.test.js b/packages/eslint-plugin/tests/rules/require-lang.test.js index cefc9fc8..8a6a40dd 100644 --- a/packages/eslint-plugin/tests/rules/require-lang.test.js +++ b/packages/eslint-plugin/tests/rules/require-lang.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/require-lang"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("require-lang", rule, { valid: [ @@ -55,3 +56,24 @@ ruleTester.run("require-lang", rule, { }, ], }); + +templateRuleTester.run("[template] require-lang", rule, { + valid: [ + { + code: `html\`\``, + }, + { + code: `html\`\``, + }, + ], + invalid: [ + { + code: `html\`\``, + errors: [ + { + messageId: "missing", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/sort-attrs.test.js b/packages/eslint-plugin/tests/rules/sort-attrs.test.js index f87aa284..ab52f009 100644 --- a/packages/eslint-plugin/tests/rules/sort-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/sort-attrs.test.js @@ -2,6 +2,7 @@ const createRuleTester = require("../rule-tester"); const rule = require("../../lib/rules/sort-attrs"); const ruleTester = createRuleTester(); +const templateRuleTester = createRuleTester("espree"); ruleTester.run("sort-attrs", rule, { valid: [ @@ -202,3 +203,87 @@ ruleTester.run("sort-attrs", rule, { }, ], }); + +templateRuleTester.run("[template] sort-attrs", rule, { + valid: [ + { + code: 'html``', + }, + ], + invalid: [ + { + code: ` + html\`
+
\` + `, + output: ` + html\`
+
\` + `, + options: [ + { + priority: ["id", "style"], + }, + ], + errors: [ + { + messageId: "unsorted", + }, + ], + }, + { + code: ` + html\`
+
\` + `, + output: ` + html\`
+
\` + `, + options: [ + { + priority: ["id", "style"], + }, + ], + errors: [ + { + messageId: "unsorted", + }, + ], + }, + { + code: 'html``', + output: + 'html``', + errors: [ + { + messageId: "unsorted", + }, + ], + }, + ], +});