From 4e1c700378dfdea372c277f3cd4ddef4524cbbe2 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Thu, 7 Aug 2025 22:59:59 +0900 Subject: [PATCH 01/12] implement --- docs/rules/no-ineffective-attrs.md | 13 ++ packages/eslint-plugin/lib/rules/index.js | 2 + .../lib/rules/no-ineffective-attrs.js | 126 ++++++++++++++++++ .../tests/rules/no-ineffective-attrs.test.js | 15 +++ 4 files changed, 156 insertions(+) create mode 100644 docs/rules/no-ineffective-attrs.md create mode 100644 packages/eslint-plugin/lib/rules/no-ineffective-attrs.js create mode 100644 packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js diff --git a/docs/rules/no-ineffective-attrs.md b/docs/rules/no-ineffective-attrs.md new file mode 100644 index 00000000..2a8c2bb6 --- /dev/null +++ b/docs/rules/no-ineffective-attrs.md @@ -0,0 +1,13 @@ +# no-ineffective-attrs + +## How to use + +```js,.eslintrc.js +module.exports = { + rules: { + "@html-eslint/no-ineffective-attrs": "error", + }, +}; +``` + +## Rule Details diff --git a/packages/eslint-plugin/lib/rules/index.js b/packages/eslint-plugin/lib/rules/index.js index 1a3113c0..7712a3c3 100644 --- a/packages/eslint-plugin/lib/rules/index.js +++ b/packages/eslint-plugin/lib/rules/index.js @@ -51,6 +51,7 @@ const noDuplicateClass = require("./no-duplicate-class"); const noEmptyHeadings = require("./no-empty-headings"); const noInvalidEntity = require("./no-invalid-entity"); const noDuplicateInHead = require("./no-duplicate-in-head"); +const noIneffectiveAttrs = require("./no-ineffective-attrs"); // import new rule here ↑ // DO NOT REMOVE THIS COMMENT @@ -108,6 +109,7 @@ const rules = { "no-empty-headings": noEmptyHeadings, "no-invalid-entity": noInvalidEntity, "no-duplicate-in-head": noDuplicateInHead, + "no-ineffective-attrs": noIneffectiveAttrs, // export new rule here ↑ // DO NOT REMOVE THIS COMMENT }; diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js new file mode 100644 index 00000000..a6f81fc3 --- /dev/null +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -0,0 +1,126 @@ +/** + * @import {Tag} from "@html-eslint/types" + * @import {RuleModule} from "../types"; + * @typedef {{ tag: string; }} Checker + */ + +const MESSAGE_IDS = { + UNEXPECTED: "unexpected", +}; + +function getAttrValue(node, attrName) { + const attr = node.attributes.find( + (a) => a.type === "Attribute" && a.key.name === attrName + ); + if (!attr || !attr.value || attr.value.type !== "Text") return undefined; + return attr.value.value; +} + +function hasAttr(node, attrName) { + return node.attributes.some( + (a) => a.type === "Attribute" && a.key.name === attrName + ); +} + +const checkers = [ + { + tag: "input", + attr: "multiple", + when: (node) => { + const type = getAttrValue(node, "type") || "text"; + return [ + "text", + "password", + "radio", + "checkbox", + "image", + "hidden", + "reset", + "button", + ].includes(type); + }, + message: 'The "multiple" attribute has no effect on this input type.', + }, + { + tag: "input", + attr: "accept", + when: (node) => { + const type = getAttrValue(node, "type") || "text"; + return type !== "file"; + }, + message: + 'The "accept" attribute has no effect unless input type is "file".', + }, + { + tag: "script", + attr: "defer", + when: (node) => !hasAttr(node, "src"), + message: 'The "defer" attribute has no effect on inline scripts.', + }, + { + tag: "textarea", + attr: "value", + when: () => true, + message: + 'The "value" attribute has no effect on + + +Download + + +
+ +
+``` + +Examples of **correct** code for this rule: + +```html + + + + + + + + + + + + + +Download + + +
+ +
+``` diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js index a6f81fc3..60447179 100644 --- a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -1,27 +1,36 @@ /** - * @import {Tag} from "@html-eslint/types" * @import {RuleModule} from "../types"; - * @typedef {{ tag: string; }} Checker + * @import {Tag} from "@html-eslint/types" + * @typedef {{ tag: string; attr: string; when: (node: Tag) => boolean; message: string; }} Checker */ -const MESSAGE_IDS = { - UNEXPECTED: "unexpected", -}; - +/** + * @param {Tag} node + * @param {string} attrName + * @returns {string | undefined} + */ function getAttrValue(node, attrName) { const attr = node.attributes.find( - (a) => a.type === "Attribute" && a.key.name === attrName + (a) => a.type === "Attribute" && a.key.value === attrName ); - if (!attr || !attr.value || attr.value.type !== "Text") return undefined; + if (!attr || !attr.value) return undefined; return attr.value.value; } +/** + * @param {Tag} node + * @param {string} attrName + * @returns {boolean} + */ function hasAttr(node, attrName) { return node.attributes.some( - (a) => a.type === "Attribute" && a.key.name === attrName + (a) => a.type === "Attribute" && a.key.value === attrName ); } +/** + * @type {Checker[]} + */ const checkers = [ { tag: "input", @@ -88,7 +97,8 @@ module.exports = { name: "no-ineffective-attrs", meta: { docs: { - description: "Disallow HTML attributes that have no effect in context", + description: + "Disallow HTML attributes that have no effect in their context", category: "Possible Errors", recommended: false, }, @@ -99,17 +109,46 @@ module.exports = { type: "problem", }, defaultOptions: [], + /** + * @param {any} context + */ create(context) { return { + /** + * @param {Tag} node + */ Tag(node) { - for (const check of checks) { - if (node.tagName !== check.tag) continue; + for (const check of checkers) { + if (node.name !== check.tag) continue; + + for (const attr of node.attributes) { + if (attr.type !== "Attribute") continue; + if (attr.key.value !== check.attr) continue; + + if (check.when(node)) { + context.report({ + loc: attr.loc, + messageId: "ineffective", + data: { + message: check.message, + }, + }); + } + } + } + }, + /** + * @param {any} node + */ + ScriptTag(node) { + for (const check of checkers) { + if (check.tag !== "script") continue; for (const attr of node.attributes) { if (attr.type !== "Attribute") continue; - if (attr.key.name !== check.attr) continue; + if (attr.key.value !== check.attr) continue; - if (check.when(node, attr)) { + if (check.when(node)) { context.report({ loc: attr.loc, messageId: "ineffective", diff --git a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js index c0167c64..7c00d11b 100644 --- a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js @@ -5,8 +5,125 @@ const ruleTester = createRuleTester(); const templateRuleTester = createRuleTester("espree"); ruleTester.run("no-ineffective-attrs", rule, { - valid: [], - invalid: [], + valid: [ + // Valid input types for multiple attribute + { + code: '', + }, + { + code: '', + }, + { + code: '', + }, + + // Valid file input with accept + { + code: '', + }, + + // Valid script with src and defer + { + code: '', + }, + + // Valid textarea without value attribute + { + code: '', + }, + + // Valid anchor with href and download + { + code: 'Download', + }, + + // Valid form with POST method and enctype + { + code: '
', + }, + ], + invalid: [ + // Invalid multiple on text input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { message: 'The "multiple" attribute has no effect on this input type.' }, + }, + ], + }, + // Invalid multiple on checkbox + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { message: 'The "multiple" attribute has no effect on this input type.' }, + }, + ], + }, + // Invalid accept on non-file input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { message: 'The "accept" attribute has no effect unless input type is "file".' }, + }, + ], + }, + // Invalid defer on inline script + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { message: 'The "defer" attribute has no effect on inline scripts.' }, + }, + ], + }, + // Invalid value on textarea + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { message: 'The "value" attribute has no effect on ', + code: "", }, - + // Valid anchor with href and download { code: 'Download', }, - + // Valid form with POST method and enctype { code: '
', @@ -49,7 +49,10 @@ ruleTester.run("no-ineffective-attrs", rule, { errors: [ { messageId: "ineffective", - data: { message: 'The "multiple" attribute has no effect on this input type.' }, + data: { + message: + 'The "multiple" attribute has no effect on this input type.', + }, }, ], }, @@ -58,8 +61,11 @@ ruleTester.run("no-ineffective-attrs", rule, { code: '', errors: [ { - messageId: "ineffective", - data: { message: 'The "multiple" attribute has no effect on this input type.' }, + messageId: "ineffective", + data: { + message: + 'The "multiple" attribute has no effect on this input type.', + }, }, ], }, @@ -69,17 +75,22 @@ ruleTester.run("no-ineffective-attrs", rule, { errors: [ { messageId: "ineffective", - data: { message: 'The "accept" attribute has no effect unless input type is "file".' }, + data: { + message: + 'The "accept" attribute has no effect unless input type is "file".', + }, }, ], }, // Invalid defer on inline script { - code: '', + code: "", errors: [ { messageId: "ineffective", - data: { message: 'The "defer" attribute has no effect on inline scripts.' }, + data: { + message: 'The "defer" attribute has no effect on inline scripts.', + }, }, ], }, @@ -89,17 +100,23 @@ ruleTester.run("no-ineffective-attrs", rule, { errors: [ { messageId: "ineffective", - data: { message: 'The "value" attribute has no effect on ', From 3775d704d58e967d05079771d310586eabbe4772 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sat, 9 Aug 2025 00:55:03 +0900 Subject: [PATCH 06/12] refactor --- .../lib/rules/no-ineffective-attrs.js | 132 +++++++++--------- .../tests/rules/no-ineffective-attrs.test.js | 20 +-- 2 files changed, 66 insertions(+), 86 deletions(-) diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js index 92ea1e32..f284b41a 100644 --- a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -1,7 +1,7 @@ /** * @import {RuleModule} from "../types"; * @import {Tag} from "@html-eslint/types" - * @typedef {{ tag: string; attr: string; when: (node: Tag) => boolean; message: string; }} Checker + * @typedef {{ attr: string; when: (node: Tag) => boolean; message: string; }} AttributeChecker */ const { RULE_CATEGORY } = require("../constants"); @@ -21,72 +21,68 @@ function getAttrValue(node, attrName) { } /** - * @type {Checker[]} + * @type {Record} */ -const checkers = [ - { - tag: "input", - attr: "multiple", - when: (node) => { - const type = getAttrValue(node, "type") || "text"; - return [ - "text", - "password", - "radio", - "checkbox", - "image", - "hidden", - "reset", - "button", - ].includes(type); +const checkersByTag = { + input: [ + { + attr: "multiple", + when: (node) => { + const type = getAttrValue(node, "type") || "text"; + return [ + "text", + "password", + "radio", + "checkbox", + "image", + "hidden", + "reset", + "button", + ].includes(type); + }, + message: 'The "multiple" attribute has no effect on this input type.', }, - message: 'The "multiple" attribute has no effect on this input type.', - }, - { - tag: "input", - attr: "accept", - when: (node) => { - const type = getAttrValue(node, "type") || "text"; - return type !== "file"; + { + attr: "accept", + when: (node) => { + const type = getAttrValue(node, "type") || "text"; + return type !== "file"; + }, + message: + 'The "accept" attribute has no effect unless input type is "file".', }, - message: - 'The "accept" attribute has no effect unless input type is "file".', - }, - { - tag: "script", - attr: "defer", - when: (node) => !hasAttr(node, "src"), - message: 'The "defer" attribute has no effect on inline scripts.', - }, - { - tag: "script", - attr: "async", - when: (node) => !hasAttr(node, "src"), - message: 'The "async" attribute has no effect on inline scripts.', - }, - { - tag: "textarea", - attr: "value", - when: () => true, - message: - 'The "value" attribute has no effect on ", - }, - // Valid anchor with href and download { code: 'Download', @@ -111,19 +106,6 @@ ruleTester.run("no-ineffective-attrs", rule, { }, ], }, - // Invalid value on textarea - { - code: '', - errors: [ - { - messageId: "ineffective", - data: { - message: - 'The "value" attribute has no effect on - Download - - -
- -
``` Examples of **correct** code for this rule: @@ -52,14 +44,6 @@ Examples of **correct** code for this rule: - - - Download - - -
- -
``` diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js index fa1870c9..a2277ad8 100644 --- a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -72,6 +72,20 @@ const checkersByTag = { message: 'The "download" attribute has no effect without an "href".', }, ], + audio: [ + { + attr: "controlslist", + when: (node) => !hasAttr(node, "controls"), + message: 'The "controlslist" attribute has no effect without "controls".', + }, + ], + video: [ + { + attr: "controlslist", + when: (node) => !hasAttr(node, "controls"), + message: 'The "controlslist" attribute has no effect without "controls".', + }, + ], }; /** diff --git a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js index 71016b05..9a616f48 100644 --- a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js @@ -36,6 +36,16 @@ ruleTester.run("no-ineffective-attrs", rule, { { code: 'Download', }, + + // Valid audio with controls and controlslist + { + code: '', + }, + + // Valid video with controls and controlslist + { + code: '', + }, ], invalid: [ // Invalid multiple on text input @@ -114,16 +124,80 @@ ruleTester.run("no-ineffective-attrs", rule, { }, ], }, + // Invalid controlslist on audio without controls + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "controlslist" attribute has no effect without "controls".', + }, + }, + ], + }, + // Invalid controlslist on video without controls + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "controlslist" attribute has no effect without "controls".', + }, + }, + ], + }, ], }); templateRuleTester.run("[template] no-ineffective-attrs", rule, { valid: [ + // Valid input types for multiple attribute { code: `html\`\``, }, + { + code: `html\`\``, + }, + { + code: `html\`\``, + }, + + // Valid file input with accept + { + code: `html\`\``, + }, + + // Valid script with src and defer + { + code: `html\`\``, + }, + + // Valid script with src and async + { + code: `html\`\``, + }, + + // Valid anchor with href and download + { + code: `html\`Download\``, + }, + + // Valid audio with controls and controlslist + { + code: `html\`\``, + }, + + // Valid video with controls and controlslist + { + code: `html\`\``, + }, ], invalid: [ + // Invalid multiple on text input { code: 'html``', errors: [ @@ -136,5 +210,94 @@ templateRuleTester.run("[template] no-ineffective-attrs", rule, { }, ], }, + // Invalid multiple on checkbox + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "multiple" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid accept on non-file input + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "accept" attribute has no effect unless input type is "file".', + }, + }, + ], + }, + // Invalid defer on inline script + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: 'The "defer" attribute has no effect on inline scripts.', + }, + }, + ], + }, + // Invalid async on inline script + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: 'The "async" attribute has no effect on inline scripts.', + }, + }, + ], + }, + // Invalid download without href + { + code: `html\`Download\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "download" attribute has no effect without an "href".', + }, + }, + ], + }, + // Invalid controlslist on audio without controls + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "controlslist" attribute has no effect without "controls".', + }, + }, + ], + }, + // Invalid controlslist on video without controls + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "controlslist" attribute has no effect without "controls".', + }, + }, + ], + }, ], }); From 168f17bd95a99f08e61a6c2732b0a56c10f7890c Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sat, 9 Aug 2025 18:46:25 +0900 Subject: [PATCH 09/12] Update .cspell.json --- .cspell.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index c884c750..9c0cf4a5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -59,6 +59,9 @@ "contenteditable", "nbsb", "endl", - "cout" + "cout", + "nodownload", + "nofullscreen", + "controlslist" ] } From b392efce5535af7c659671517f92de751fa5f90f Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sat, 9 Aug 2025 18:51:52 +0900 Subject: [PATCH 10/12] implement --- docs/rules/no-ineffective-attrs.md | 33 +++- .../lib/rules/no-ineffective-attrs.js | 8 + .../tests/rules/no-ineffective-attrs.test.js | 150 ++++++++++++++++++ 3 files changed, 189 insertions(+), 2 deletions(-) diff --git a/docs/rules/no-ineffective-attrs.md b/docs/rules/no-ineffective-attrs.md index ea09eca1..eda2738b 100644 --- a/docs/rules/no-ineffective-attrs.md +++ b/docs/rules/no-ineffective-attrs.md @@ -20,30 +20,59 @@ Examples of **incorrect** code for this rule: + + + + + + + + + + + + Download + + + + ``` Examples of **correct** code for this rule: ```html - + + + - + + + + + + Download + + + + ``` diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js index a2277ad8..60abbc84 100644 --- a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -52,6 +52,14 @@ const checkersByTag = { message: 'The "accept" attribute has no effect unless input type is "file".', }, + { + attr: "readonly", + when: (node) => { + const type = getAttrValue(node, "type") || "text"; + return ["checkbox", "radio", "file", "range", "color"].includes(type); + }, + message: 'The "readonly" attribute has no effect on this input type.', + }, ], script: [ { diff --git a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js index 9a616f48..0b80b2bc 100644 --- a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js @@ -22,6 +22,16 @@ ruleTester.run("no-ineffective-attrs", rule, { code: '', }, + // Valid text input with readonly + { + code: '', + }, + + // Valid password input with readonly + { + code: '', + }, + // Valid script with src and defer { code: '', @@ -150,6 +160,71 @@ ruleTester.run("no-ineffective-attrs", rule, { }, ], }, + // Invalid readonly on checkbox input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on radio input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on file input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on range input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on color input + { + code: '', + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, ], }); @@ -171,6 +246,16 @@ templateRuleTester.run("[template] no-ineffective-attrs", rule, { code: `html\`\``, }, + // Valid text input with readonly + { + code: `html\`\``, + }, + + // Valid password input with readonly + { + code: `html\`\``, + }, + // Valid script with src and defer { code: `html\`\``, @@ -299,5 +384,70 @@ templateRuleTester.run("[template] no-ineffective-attrs", rule, { }, ], }, + // Invalid readonly on checkbox input + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on radio input + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on file input + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on range input + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, + // Invalid readonly on color input + { + code: `html\`\``, + errors: [ + { + messageId: "ineffective", + data: { + message: + 'The "readonly" attribute has no effect on this input type.', + }, + }, + ], + }, ], }); From 547bc1acc564bdf76fcabf8c85691a2d03508dcb Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sat, 9 Aug 2025 18:53:14 +0900 Subject: [PATCH 11/12] Update no-ineffective-attrs.js --- packages/eslint-plugin/lib/rules/no-ineffective-attrs.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js index 60abbc84..352816fe 100644 --- a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -115,9 +115,6 @@ module.exports = { type: "problem", }, defaultOptions: [], - /** - * @param {any} context - */ create(context) { return createVisitors(context, { /** From 286ba84525f393aded7972cfcbd1c01e7315f2f6 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sat, 9 Aug 2025 18:57:37 +0900 Subject: [PATCH 12/12] update --- .../lib/rules/no-ineffective-attrs.js | 16 +++++++++++++++- .../tests/rules/no-ineffective-attrs.test.js | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js index 352816fe..a52f3115 100644 --- a/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js +++ b/packages/eslint-plugin/lib/rules/no-ineffective-attrs.js @@ -5,7 +5,7 @@ */ const { RULE_CATEGORY } = require("../constants"); -const { hasAttr } = require("./utils/node"); +const { hasAttr, hasTemplate, findAttr } = require("./utils/node"); const { createVisitors } = require("./utils/visitors"); /** @@ -21,6 +21,17 @@ function getAttrValue(node, attrName) { return attr.value.value; } +/** + * @param {Tag | ScriptTag} node + * @param {string} attrName + * @returns {boolean} + */ +function isTemplateValueAttr(node, attrName) { + const attr = findAttr(node, attrName); + if (!attr || !attr.value) return false; + return hasTemplate(attr.value); +} + /** * @type {Record} */ @@ -46,6 +57,9 @@ const checkersByTag = { { attr: "accept", when: (node) => { + if (isTemplateValueAttr(node, "type")) { + return false; + } const type = getAttrValue(node, "type") || "text"; return type !== "file"; }, diff --git a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js index 0b80b2bc..ddde93a0 100644 --- a/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js +++ b/packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js @@ -245,11 +245,17 @@ templateRuleTester.run("[template] no-ineffective-attrs", rule, { { code: `html\`\``, }, + { + code: `html\`\``, + }, // Valid text input with readonly { code: `html\`\``, }, + { + code: `html\`\``, + }, // Valid password input with readonly {