From cad9394134c1198bdc50608edf1b71197bd7a8c0 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 9 Nov 2021 14:46:03 +0800 Subject: [PATCH] Add `prefer-code-point` rule --- configs/recommended.js | 1 + docs/rules/prefer-code-point.md | 25 +++++++++ readme.md | 2 + rules/prefer-code-point.js | 55 +++++++++++++++++++ test/prefer-code-point.mjs | 36 +++++++++++++ test/snapshots/prefer-code-point.mjs.md | 61 ++++++++++++++++++++++ test/snapshots/prefer-code-point.mjs.snap | Bin 0 -> 397 bytes 7 files changed, 180 insertions(+) create mode 100644 docs/rules/prefer-code-point.md create mode 100644 rules/prefer-code-point.js create mode 100644 test/prefer-code-point.mjs create mode 100644 test/snapshots/prefer-code-point.mjs.md create mode 100644 test/snapshots/prefer-code-point.mjs.snap diff --git a/configs/recommended.js b/configs/recommended.js index 6ed245407f..16034c6370 100644 --- a/configs/recommended.js +++ b/configs/recommended.js @@ -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', diff --git a/docs/rules/prefer-code-point.md b/docs/rules/prefer-code-point.md new file mode 100644 index 0000000000..5b54cd0210 --- /dev/null +++ b/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) . + +*Note: they behave differently in some case.* + +## 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); +``` diff --git a/readme.md b/readme.md index 92a33ac537..1a03ef847b 100644 --- a/readme.md +++ b/readme.md @@ -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", @@ -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()`. | ✅ | 🔧 | | diff --git a/rules/prefer-code-point.js b/rules/prefer-code-point.js new file mode 100644 index 0000000000..0586b003ad --- /dev/null +++ b/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, + }, +}; diff --git a/test/prefer-code-point.mjs b/test/prefer-code-point.mjs new file mode 100644 index 0000000000..23f391539b --- /dev/null +++ b/test/prefer-code-point.mjs @@ -0,0 +1,36 @@ +import outdent from 'outdent'; +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)), ) ))', + ], +}); diff --git a/test/snapshots/prefer-code-point.mjs.md b/test/snapshots/prefer-code-point.mjs.md new file mode 100644 index 0000000000..f6b342a7bf --- /dev/null +++ b/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)), ) ))␊ + ` diff --git a/test/snapshots/prefer-code-point.mjs.snap b/test/snapshots/prefer-code-point.mjs.snap new file mode 100644 index 0000000000000000000000000000000000000000..5268ae32df78b964a4df771c154834ee14614b49 GIT binary patch literal 397 zcmV;80doF9RzVxEk00000000Ba zV_;xlVaSwNHEFr+oMo-v2OQbfmDw^ffPg%ZDtllqr7miivbIyY#WGFvC?i-jn~8y; z-sSOaVfC}MV%|F?x=afvFoH#AFf%YL_;h`#qKmR|^aK`>4_pe-j9}3ZEDQ|H4D4W& z8Ce+wnFJWQ>=XS(jfS^VJ3gX~EA)qKV zEwxA?AsDPtIT@%aAU`v&1gKLpK_R~kSq@>SW`Z6UmoAk7S8!>1dTMb=W`3T6p}wJ& zLTGU+4u|Oh{e$dXBuS`$QG&-77CagnAOz8+ps9%*PCyBWR!tqC3|fFG47-rS2ni%r z=s|%JmU6JLgcz@vR+OLX4E3Le0>~HzoT&{X)F8THR-=a-s@Zf8ISj|a!Var6%