Skip to content

Commit

Permalink
prefer-dom-node-dataset: Check .hasAttribute() and `.getAttribute…
Browse files Browse the repository at this point in the history
…()` (#1673)
  • Loading branch information
fisker committed Dec 30, 2021
1 parent 021aa9b commit 152f153
Show file tree
Hide file tree
Showing 6 changed files with 548 additions and 10 deletions.
28 changes: 26 additions & 2 deletions docs/rules/prefer-dom-node-dataset.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Prefer using `.dataset` on DOM elements over `.setAttribute(…)` and `.removeAttribute(…)`
# Prefer using `.dataset` on DOM elements over calling attribute methods

*This rule is part of the [recommended](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) config.*

🔧 *This rule is [auto-fixable](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems).*

Use [`.dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) on DOM elements over `.setAttribute(…)` and `.removeAttribute(…)`.
Use [`.dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) on DOM elements over `getAttribute(…)`, `.setAttribute(…)`, `.removeAttribute(…)` and `.hasAttribute(…)`.

## Fail

```js
const unicorn = element.getAttribute('data-unicorn');
```

```js
element.setAttribute('data-unicorn', '🦄');
```
Expand All @@ -16,8 +20,16 @@ element.setAttribute('data-unicorn', '🦄');
element.removeAttribute('data-unicorn');
```

```js
const hasUnicorn = element.hasAttribute('data-unicorn');
```

## Pass

```js
const {unicorn} = element.dataset;
```

```js
element.dataset.unicorn = '🦄';
```
Expand All @@ -26,10 +38,22 @@ element.dataset.unicorn = '🦄';
delete element.dataset.unicorn;
```

```js
const hasUnicorn = Object.hasOwn(element.dataset, 'unicorn');
```

```js
const foo = element.getAttribute('foo');
```

```js
element.setAttribute('not-dataset', '🦄');
```

```js
element.removeAttribute('not-dataset');
```

```js
const hasFoo = element.hasAttribute('foo');
```
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ Each rule has emojis denoting:
| [prefer-date-now](docs/rules/prefer-date-now.md) | Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. || 🔧 | |
| [prefer-default-parameters](docs/rules/prefer-default-parameters.md) | Prefer default parameters over reassignment. || 🔧 | 💡 |
| [prefer-dom-node-append](docs/rules/prefer-dom-node-append.md) | Prefer `Node#append()` over `Node#appendChild()`. || 🔧 | |
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over `.setAttribute(…)` and `.removeAttribute(…)`. || 🔧 | |
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over calling attribute methods. || 🔧 | |
| [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. || 🔧 | 💡 |
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. || | 💡 |
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. || 🔧 | 💡 |
Expand Down
34 changes: 27 additions & 7 deletions rules/prefer-dom-node-dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const messages = {
const selector = [
matches([
methodCallSelector({method: 'setAttribute', argumentsLength: 2}),
methodCallSelector({method: 'removeAttribute', argumentsLength: 1}),
methodCallSelector({methods: ['getAttribute', 'removeAttribute', 'hasAttribute'], argumentsLength: 1}),
]),
'[arguments.0.type="Literal"]',
].join('');
Expand All @@ -30,14 +30,34 @@ const create = context => ({

const method = node.callee.property.name;
const name = dashToCamelCase(attributeName.slice(5));
let text = isValidVariableName(name) ? `.${name}` : `[${quoteString(name, nameNode.raw.charAt(0))}]`;

const sourceCode = context.getSourceCode();
text = `${sourceCode.getText(node.callee.object)}.dataset${text}`;
let text = '';
const datasetText = `${sourceCode.getText(node.callee.object)}.dataset`;
switch (method) {
case 'setAttribute':
case 'getAttribute':
case 'removeAttribute': {
text = isValidVariableName(name) ? `.${name}` : `[${quoteString(name, nameNode.raw.charAt(0))}]`;
text = `${datasetText}${text}`;
if (method === 'setAttribute') {
text += ` = ${sourceCode.getText(node.arguments[1])}`;
} else if (method === 'removeAttribute') {
text = `delete ${text}`;
}

text = method === 'setAttribute'
? `${text} = ${sourceCode.getText(node.arguments[1])}`
: `delete ${text}`;
/*
For non-exists attribute, `element.getAttribute('data-foo')` returns `null`,
but `element.dataset.foo` returns `undefined`, switch to suggestions if necessary
*/
break;
}

case 'hasAttribute':
text = `Object.hasOwn(${datasetText}, ${quoteString(name, nameNode.raw.charAt(0))})`;
break;
// No default
}

return {
node,
Expand All @@ -54,7 +74,7 @@ module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Prefer using `.dataset` on DOM elements over `.setAttribute(…)` and `.removeAttribute(…)`.',
description: 'Prefer using `.dataset` on DOM elements over calling attribute methods.',
},
fixable: 'code',
messages,
Expand Down
98 changes: 98 additions & 0 deletions test/prefer-dom-node-dataset.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,101 @@ test.snapshot({
'element.querySelector("#selector").removeAttribute("data-AllowAccess");',
],
});

// `hasAttribute``
test.snapshot({
valid: [
'"unicorn" in element.dataset',
'element.dataset.hasOwnProperty("unicorn")',
'Object.prototype.hasOwnProperty.call(element.dataset, "unicorn")',
'Object.hasOwn(element.dataset, "unicorn")',
'Reflect.has(element.dataset, "unicorn")',
// Not `CallExpression`
'new element.hasAttribute("data-unicorn");',
// Not `MemberExpression`
'hasAttribute("data-unicorn");',
// `callee.property` is not a `Identifier`
'element["hasAttribute"]("data-unicorn");',
// Computed
'element[hasAttribute]("data-unicorn");',
// Not `removeAttribute`
'element.foo("data-unicorn");',
// More or less argument(s)
'element.hasAttribute("data-unicorn", "extra");',
'element.hasAttribute();',
'element.hasAttribute(...argumentsArray, ...argumentsArray2)',
// First Argument is not `Literal`
'element.hasAttribute(`data-unicorn`);',
// First Argument is not `string`
'element.hasAttribute(0);',
// First Argument is not startsWith `data-`
'element.hasAttribute("foo-unicorn");',
// First Argument is `data-`
'element.hasAttribute("data-");',
],
invalid: [
outdent`
element.hasAttribute(
"data-foo", // comment
);
`,
'element.hasAttribute(\'data-unicorn\');',
'element.hasAttribute("data-unicorn");',
'element.hasAttribute("data-unicorn",);',
'element.hasAttribute("data-🦄");',
'element.hasAttribute("data-foo2");',
'element.hasAttribute("data-foo:bar");',
'element.hasAttribute("data-foo:bar");',
'element.hasAttribute("data-foo.bar");',
'element.hasAttribute("data-foo-bar");',
'element.hasAttribute("data-foo");',
'element.querySelector("#selector").hasAttribute("data-AllowAccess");',
],
});

// `getAttribute``
test.snapshot({
valid: [
'element.dataset.unicorn',
// Not `CallExpression`
'new element.getAttribute("data-unicorn");',
// Not `MemberExpression`
'getAttribute("data-unicorn");',
// `callee.property` is not a `Identifier`
'element["getAttribute"]("data-unicorn");',
// Computed
'element[getAttribute]("data-unicorn");',
// Not `getAttribute`
'element.foo("data-unicorn");',
// More or less argument(s)
'element.getAttribute("data-unicorn", "extra");',
'element.getAttribute();',
'element.getAttribute(...argumentsArray, ...argumentsArray2)',
// First Argument is not `Literal`
'element.getAttribute(`data-unicorn`);',
// First Argument is not `string`
'element.getAttribute(0);',
// First Argument is not startsWith `data-`
'element.getAttribute("foo-unicorn");',
// First Argument is `data-`
'element.getAttribute("data-");',
],
invalid: [
outdent`
element.getAttribute(
"data-foo", // comment
);
`,
'element.getAttribute(\'data-unicorn\');',
'element.getAttribute("data-unicorn");',
'element.getAttribute("data-unicorn",);',
'element.getAttribute("data-🦄");',
'element.getAttribute("data-foo2");',
'element.getAttribute("data-foo:bar");',
'element.getAttribute("data-foo:bar");',
'element.getAttribute("data-foo.bar");',
'element.getAttribute("data-foo-bar");',
'element.getAttribute("data-foo");',
'element.querySelector("#selector").getAttribute("data-AllowAccess");',
],
});

0 comments on commit 152f153

Please sign in to comment.