Skip to content

Commit

Permalink
Add prefer-code-point rule (#1584)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Nov 9, 2021
1 parent a32d8f8 commit 31c83cd
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 0 deletions.
1 change: 1 addition & 0 deletions configs/recommended.js
Expand Up @@ -58,6 +58,7 @@ module.exports = {
'unicorn/prefer-array-some': 'error',
// TODO: Enable this by default when targeting a Node.js version that supports `Array#at`.
'unicorn/prefer-at': 'off',
'unicorn/prefer-code-point': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-dom-node-append': 'error',
Expand Down
25 changes: 25 additions & 0 deletions docs/rules/prefer-code-point.md
@@ -0,0 +1,25 @@
# Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`

Unicode is better supported in [`String#codePointAt()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) and [`String.fromCodePoint()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint).

- [Different between `String.fromCodePoint()` and `String.fromCharCode()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint#compared_to_fromcharcode)

## Fail

```js
const unicorn = '🦄'.charCodeAt(0).toString(16);
```

```js
const unicorn = String.fromCharCode(0x1f984);
```

## Pass

```js
const unicorn = '🦄'.codePointAt(0).toString(16);
```

```js
const unicorn = String.fromCodePoint(0x1f984);
```
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -90,6 +90,7 @@ Configure it in `package.json`.
"unicorn/prefer-array-index-of": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-at": "off",
"unicorn/prefer-code-point": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-default-parameters": "error",
"unicorn/prefer-dom-node-append": "error",
Expand Down Expand Up @@ -202,6 +203,7 @@ Each rule has emojis denoting:
| [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#indexOf()` over `Array#findIndex()` when looking for the index of an item. || 🔧 | 💡 |
| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`. || 🔧 | 💡 |
| [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | | 🔧 | 💡 |
| [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. || | 💡 |
| [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()`. || 🔧 | |
Expand Down
55 changes: 55 additions & 0 deletions rules/prefer-code-point.js
@@ -0,0 +1,55 @@
'use strict';
const {methodCallSelector} = require('./selectors/index.js');

const messages = {
'error/charCodeAt': 'Prefer `String#codePointAt()` over `String#charCodeAt()`.',
'error/fromCharCode': 'Prefer `String.fromCodePoint()` over `String.fromCharCode()`.',
'suggestion/charCodeAt': 'Use `String#codePointAt()`.',
'suggestion/fromCharCode': 'Use `String.fromCodePoint()`.',
};

const cases = [
{
selector: methodCallSelector('charCodeAt'),
replacement: 'codePointAt',
},
{
selector: methodCallSelector({object: 'String', method: 'fromCharCode'}),
replacement: 'fromCodePoint',
},
];

const create = () => Object.fromEntries(
cases.map(({selector, replacement}) => [
selector,
node => {
const method = node.callee.property;
const methodName = method.name;
const fix = fixer => fixer.replaceText(method, replacement);

return {
node: method,
messageId: `error/${methodName}`,
suggest: [
{
messageId: `suggestion/${methodName}`,
fix,
},
],
};
},
]),
);

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`.',
},
hasSuggestions: true,
schema: [],
messages,
},
};
35 changes: 35 additions & 0 deletions test/prefer-code-point.mjs
@@ -0,0 +1,35 @@
import {getTester} from './utils/test.mjs';

const {test} = getTester(import.meta);

test.snapshot({
valid: [
'"🦄".codePointAt(0)',
'foo.charCodeAt',
'new foo.charCodeAt',
'charCodeAt(0)',
'foo.charCodeAt?.(0)',
'foo?.charCodeAt(0)',
'foo[charCodeAt](0)',
'foo["charCodeAt"](0)',
'foo.notCharCodeAt(0)',

'String.fromCodePoint(0x1f984)',
'String.fromCodePoint',
'new String.fromCodePoint',
'fromCodePoint(foo)',
'String.fromCodePoint?.(foo)',
'String?.fromCodePoint(foo)',
'window.String.fromCodePoint(foo)',
'String[fromCodePoint](foo)',
'String["fromCodePoint"](foo)',
'String.notFromCodePoint(foo)',
'NotString.fromCodePoint(foo)',
],
invalid: [
'string.charCodeAt(index)',
'(( (( string )).charCodeAt( ((index)), )))',
'String.fromCharCode( code )',
'(( (( String )).fromCharCode( ((code)), ) ))',
],
});
61 changes: 61 additions & 0 deletions test/snapshots/prefer-code-point.mjs.md
@@ -0,0 +1,61 @@
# Snapshot report for `test/prefer-code-point.mjs`

The actual snapshot is saved in `prefer-code-point.mjs.snap`.

Generated by [AVA](https://avajs.dev).

## Invalid #1
1 | string.charCodeAt(index)

> Error 1/1
`␊
> 1 | string.charCodeAt(index)␊
| ^^^^^^^^^^ Prefer \`String#codePointAt()\` over \`String#charCodeAt()\`.␊
--------------------------------------------------------------------------------␊
Suggestion 1/1: Use \`String#codePointAt()\`.␊
1 | string.codePointAt(index)␊
`

## Invalid #2
1 | (( (( string )).charCodeAt( ((index)), )))

> Error 1/1
`␊
> 1 | (( (( string )).charCodeAt( ((index)), )))␊
| ^^^^^^^^^^ Prefer \`String#codePointAt()\` over \`String#charCodeAt()\`.␊
--------------------------------------------------------------------------------␊
Suggestion 1/1: Use \`String#codePointAt()\`.␊
1 | (( (( string )).codePointAt( ((index)), )))␊
`

## Invalid #3
1 | String.fromCharCode( code )

> Error 1/1
`␊
> 1 | String.fromCharCode( code )␊
| ^^^^^^^^^^^^ Prefer \`String.fromCodePoint()\` over \`String.fromCharCode()\`.␊
--------------------------------------------------------------------------------␊
Suggestion 1/1: Use \`String.fromCodePoint()\`.␊
1 | String.fromCodePoint( code )␊
`

## Invalid #4
1 | (( (( String )).fromCharCode( ((code)), ) ))

> Error 1/1
`␊
> 1 | (( (( String )).fromCharCode( ((code)), ) ))␊
| ^^^^^^^^^^^^ Prefer \`String.fromCodePoint()\` over \`String.fromCharCode()\`.␊
--------------------------------------------------------------------------------␊
Suggestion 1/1: Use \`String.fromCodePoint()\`.␊
1 | (( (( String )).fromCodePoint( ((code)), ) ))␊
`
Binary file added test/snapshots/prefer-code-point.mjs.snap
Binary file not shown.

0 comments on commit 31c83cd

Please sign in to comment.