Skip to content

Commit

Permalink
prefer-query-selector: Add support for getElementsByName (#2398)
Browse files Browse the repository at this point in the history
Co-authored-by: fisker Cheung <lionkay@gmail.com>
  • Loading branch information
axetroy and fisker authored Jul 25, 2024
1 parent 4db75c4 commit e511ffd
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/rules/prefer-query-selector.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`
# Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()`

💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).

Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
| [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. || 🔧 | |
| [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. || 🔧 | |
| [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. || 🔧 | |
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. || 🔧 | |
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()`. || 🔧 | |
| [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) | Prefer `Reflect.apply()` over `Function#apply()`. || 🔧 | |
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. || 🔧 | 💡 |
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. || 🔧 | 💡 |
Expand Down
37 changes: 35 additions & 2 deletions rules/prefer-query-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,37 @@ const disallowedIdentifierNames = new Map([
['getElementById', 'querySelector'],
['getElementsByClassName', 'querySelectorAll'],
['getElementsByTagName', 'querySelectorAll'],
['getElementsByName', 'querySelectorAll'],
]);

const getReplacementForId = value => `#${value}`;
const getReplacementForClass = value => value.match(/\S+/g).map(className => `.${className}`).join('');
const getReplacementForName = (value, originQuote) => `[name=${wrapQuoted(value, originQuote)}]`;

const getQuotedReplacement = (node, value) => {
const leftQuote = node.raw.charAt(0);
const rightQuote = node.raw.at(-1);
return `${leftQuote}${value}${rightQuote}`;
};

const wrapQuoted = (value, originalQuote) => {
switch (originalQuote) {
case '\'': {
return `"${value}"`;
}

case '"': {
return `'${value}'`;
}

case '`': {
return `'${value}'`;
}

// No default
}
};

function * getLiteralFix(fixer, node, identifierName) {
let replacement = node.raw;
if (identifierName === 'getElementById') {
Expand All @@ -32,6 +52,11 @@ function * getLiteralFix(fixer, node, identifierName) {
replacement = getQuotedReplacement(node, getReplacementForClass(node.value));
}

if (identifierName === 'getElementsByName') {
const quoted = node.raw.charAt(0);
replacement = getQuotedReplacement(node, getReplacementForName(node.value, quoted));
}

yield fixer.replaceText(node, replacement);
}

Expand All @@ -53,6 +78,14 @@ function * getTemplateLiteralFix(fixer, node, identifierName) {
getReplacementForClass(templateElement.value.cooked),
);
}

if (identifierName === 'getElementsByName') {
const quoted = node.raw ? node.raw.charAt(0) : '"';
yield fixer.replaceText(
templateElement,
getReplacementForName(templateElement.value.cooked, quoted),
);
}
}
}

Expand Down Expand Up @@ -91,7 +124,7 @@ const create = () => ({
CallExpression(node) {
if (
!isMethodCall(node, {
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName'],
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName', 'getElementsByName'],
argumentsLength: 1,
optionalCall: false,
optionalMember: false,
Expand Down Expand Up @@ -127,7 +160,7 @@ module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`.',
description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()`.',
recommended: true,
},
fixable: 'code',
Expand Down
9 changes: 9 additions & 0 deletions test/prefer-query-selector.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ test.snapshot({
'document.querySelectorAll(".foo .bar");',
'document.querySelectorAll("li a");',
'document.querySelector("li").querySelectorAll("a");',
'document.getElementsByName();',
],
invalid: [
'document.getElementById("foo");',
Expand Down Expand Up @@ -61,5 +62,13 @@ test.snapshot({
`,
// #1030
'e.getElementById(3)',
'document.getElementsByName("foo");',
'document.getElementsByName(\'foo\');',
'document.getElementsByName(`foo`);',
'document.getElementsByName(`${\'foo\'}`);', // eslint-disable-line no-template-curly-in-string
'document.getElementsByName(null);',
'document.getElementsByName("");',
'document.getElementsByName(foo + "bar");',
'document.getElementsByName("multiple name should be fixable");',
],
});
150 changes: 150 additions & 0 deletions test/snapshots/prefer-query-selector.mjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,153 @@ Generated by [AVA](https://avajs.dev).
> 1 | e.getElementById(3)␊
| ^^^^^^^^^^^^^^ Prefer \`.querySelector()\` over \`.getElementById()\`.␊
`

## invalid(25): document.getElementsByName("foo");

> Input
`␊
1 | document.getElementsByName("foo");␊
`

> Output
`␊
1 | document.querySelectorAll("[name='foo']");␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName("foo");␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(26): document.getElementsByName('foo');

> Input
`␊
1 | document.getElementsByName('foo');␊
`

> Output
`␊
1 | document.querySelectorAll('[name="foo"]');␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName('foo');␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(27): document.getElementsByName(`foo`);

> Input
`␊
1 | document.getElementsByName(\`foo\`);␊
`

> Output
`␊
1 | document.querySelectorAll(\`[name='foo']\`);␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName(\`foo\`);␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(28): document.getElementsByName(`${'foo'}`);

> Input
`␊
1 | document.getElementsByName(\`${'foo'}\`);␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName(\`${'foo'}\`);␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(29): document.getElementsByName(null);

> Input
`␊
1 | document.getElementsByName(null);␊
`

> Output
`␊
1 | document.querySelectorAll(null);␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName(null);␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(30): document.getElementsByName("");

> Input
`␊
1 | document.getElementsByName("");␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName("");␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(31): document.getElementsByName(foo + "bar");

> Input
`␊
1 | document.getElementsByName(foo + "bar");␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName(foo + "bar");␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`

## invalid(32): document.getElementsByName("multiple name should be fixable");

> Input
`␊
1 | document.getElementsByName("multiple name should be fixable");␊
`

> Output
`␊
1 | document.querySelectorAll("[name='multiple name should be fixable']");␊
`

> Error 1/1
`␊
> 1 | document.getElementsByName("multiple name should be fixable");␊
| ^^^^^^^^^^^^^^^^^ Prefer \`.querySelectorAll()\` over \`.getElementsByName()\`.␊
`
Binary file modified test/snapshots/prefer-query-selector.mjs.snap
Binary file not shown.

0 comments on commit e511ffd

Please sign in to comment.