-
-
Notifications
You must be signed in to change notification settings - Fork 51
feat: add new rule no-ineffective-attrs #399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new ESLint rule called no-ineffective-attrs that detects HTML attributes that have no effect in their current context. The rule helps identify potential errors or unnecessary code by flagging attributes like multiple on text inputs, defer on inline scripts, or download without href.
- Adds a new ESLint rule implementation with comprehensive validation logic
- Includes extensive test coverage for both regular HTML and template literals
- Provides documentation with clear examples of correct and incorrect usage
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/eslint-plugin/lib/rules/no-ineffective-attrs.js | Core rule implementation that checks for ineffective HTML attributes across different element types |
| packages/eslint-plugin/tests/rules/no-ineffective-attrs.test.js | Comprehensive test suite covering valid and invalid attribute combinations |
| packages/eslint-plugin/lib/rules/utils/node.js | Adds utility function hasAttr for checking attribute presence |
| packages/eslint-plugin/lib/rules/index.js | Registers the new rule in the rules index |
| docs/rules/no-ineffective-attrs.md | User documentation with usage examples and rule explanation |
| .cspell.json | Adds spell check exceptions for HTML attribute values |
| { | ||
| attr: "multiple", | ||
| when: (node) => { | ||
| const type = getAttrValue(node, "type") || "text"; |
Copilot
AI
Aug 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic string "text" is used as a default input type. Consider extracting this as a named constant to improve maintainability and reduce duplication (it appears on lines 32, 49, and 58).
| const type = getAttrValue(node, "type") || "text"; | |
| const type = getAttrValue(node, "type") || DEFAULT_INPUT_TYPE; |
| const tagCheckers = checkersByTag[node.name]; | ||
| if (!tagCheckers) return; | ||
|
|
||
| for (const check of tagCheckers) { | ||
| 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 {ScriptTag} node | ||
| */ | ||
| ScriptTag(node) { | ||
| const scriptCheckers = checkersByTag.script; | ||
| if (!scriptCheckers) return; | ||
|
|
||
| for (const check of scriptCheckers) { | ||
| 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, | ||
| }, | ||
| }); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Aug 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic in the ScriptTag visitor is nearly identical to the Tag visitor (lines 126-146). Consider extracting the common attribute checking logic into a shared helper function to reduce code duplication.
| const tagCheckers = checkersByTag[node.name]; | |
| if (!tagCheckers) return; | |
| for (const check of tagCheckers) { | |
| 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 {ScriptTag} node | |
| */ | |
| ScriptTag(node) { | |
| const scriptCheckers = checkersByTag.script; | |
| if (!scriptCheckers) return; | |
| for (const check of scriptCheckers) { | |
| 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, | |
| }, | |
| }); | |
| } | |
| } | |
| } | |
| checkIneffectiveAttrs(node, checkersByTag[node.name], context); | |
| }, | |
| /** | |
| * @param {ScriptTag} node | |
| */ | |
| ScriptTag(node) { | |
| checkIneffectiveAttrs(node, checkersByTag.script, context); |
| return [ | ||
| "text", | ||
| "password", | ||
| "radio", | ||
| "checkbox", | ||
| "image", | ||
| "hidden", | ||
| "reset", | ||
| "button", | ||
| ].includes(type); |
Copilot
AI
Aug 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The array of input types that don't support 'multiple' should be extracted as a constant. This improves maintainability and makes it easier to update the list if HTML specifications change.
| return [ | |
| "text", | |
| "password", | |
| "radio", | |
| "checkbox", | |
| "image", | |
| "hidden", | |
| "reset", | |
| "button", | |
| ].includes(type); | |
| return INPUT_TYPES_WITHOUT_MULTIPLE.includes(type); |
| attr: "readonly", | ||
| when: (node) => { | ||
| const type = getAttrValue(node, "type") || "text"; | ||
| return ["checkbox", "radio", "file", "range", "color"].includes(type); |
Copilot
AI
Aug 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The array of input types that don't support 'readonly' should be extracted as a constant for better maintainability and consistency with other type checking logic.
| return ["checkbox", "radio", "file", "range", "color"].includes(type); | |
| return INPUT_TYPES_WITHOUT_READONLY.includes(type); |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #399 +/- ##
==========================================
+ Coverage 98.53% 98.56% +0.02%
==========================================
Files 82 83 +1
Lines 2658 2712 +54
Branches 736 752 +16
==========================================
+ Hits 2619 2673 +54
Misses 39 39
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Checklist
no-ineffective-attrsrule #346Description