From 4370602b20795a9dbe9013410be44111605dee72 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 9 Feb 2022 18:30:14 +0800 Subject: [PATCH] Add `text-encoding-identifier-case` rule (#1718) --- configs/recommended.js | 1 + docs/rules/text-encoding-identifier-case.md | 39 ++++++++ readme.md | 1 + rules/text-encoding-identifier-case.js | 69 ++++++++++++++ test/run-rules-on-codebase/lint.mjs | 2 + .../text-encoding-identifier-case.mjs.md | 89 ++++++++++++++++++ .../text-encoding-identifier-case.mjs.snap | Bin 0 -> 437 bytes test/text-encoding-identifier-case.mjs | 24 +++++ 8 files changed, 225 insertions(+) create mode 100644 docs/rules/text-encoding-identifier-case.md create mode 100644 rules/text-encoding-identifier-case.js create mode 100644 test/snapshots/text-encoding-identifier-case.mjs.md create mode 100644 test/snapshots/text-encoding-identifier-case.mjs.snap create mode 100644 test/text-encoding-identifier-case.mjs diff --git a/configs/recommended.js b/configs/recommended.js index 8de9cf04e9..6d7182f2c9 100644 --- a/configs/recommended.js +++ b/configs/recommended.js @@ -114,6 +114,7 @@ module.exports = { 'unicorn/require-post-message-target-origin': 'off', 'unicorn/string-content': 'off', 'unicorn/template-indent': 'warn', + 'unicorn/text-encoding-identifier-case': 'error', 'unicorn/throw-new-error': 'error', }, }; diff --git a/docs/rules/text-encoding-identifier-case.md b/docs/rules/text-encoding-identifier-case.md new file mode 100644 index 0000000000..aa59c2f1df --- /dev/null +++ b/docs/rules/text-encoding-identifier-case.md @@ -0,0 +1,39 @@ +# Enforce consistent case for text encoding identifiers + + + +✅ *This rule is part of the [recommended](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) config.* + +💡 *This rule provides [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).* + + +- Enforce `'utf8'` for [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoding. +- Enforce `'ascii'` for [ASCII](https://en.wikipedia.org/wiki/ASCII) encoding. + +## Fail + +```js +await fs.readFile(file, 'UTF-8'); +``` + +```js +await fs.readFile(file, 'ASCII'); +``` + +```js +const string = buffer.toString('utf-8'); +``` + +## Pass + +```js +await fs.readFile(file, 'utf8'); +``` + +```js +await fs.readFile(file, 'ascii'); +``` + +```js +const string = buffer.toString('utf8'); +``` diff --git a/readme.md b/readme.md index 72b8fbe06a..75ca706550 100644 --- a/readme.md +++ b/readme.md @@ -150,6 +150,7 @@ Each rule has emojis denoting: | [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. | | | 💡 | | [string-content](docs/rules/string-content.md) | Enforce better string content. | | 🔧 | 💡 | | [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | ✅ | 🔧 | | +| [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | ✅ | | 💡 | | [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | ✅ | 🔧 | | diff --git a/rules/text-encoding-identifier-case.js b/rules/text-encoding-identifier-case.js new file mode 100644 index 0000000000..b5dc9d67e0 --- /dev/null +++ b/rules/text-encoding-identifier-case.js @@ -0,0 +1,69 @@ +'use strict'; +const {replaceStringLiteral} = require('./fix/index.js'); + +const MESSAGE_ID_ERROR = 'text-encoding-identifier/error'; +const MESSAGE_ID_SUGGESTION = 'text-encoding-identifier/suggestion'; +const messages = { + [MESSAGE_ID_ERROR]: 'Prefer `{{replacement}}` over `{{value}}`.', + [MESSAGE_ID_SUGGESTION]: 'Replace `{{value}}` with `{{replacement}}`.', +}; + +const getReplacement = encoding => { + switch (encoding.toLowerCase()) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ascii': + return 'ascii'; + // No default + } +}; + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = () => ({ + Literal(node) { + if (typeof node.value !== 'string') { + return; + } + + const {raw} = node; + const value = raw.slice(1, -1); + + const replacement = getReplacement(value); + if (!replacement || replacement === value) { + return; + } + + const messageData = { + value, + replacement, + }; + + return { + node, + messageId: MESSAGE_ID_ERROR, + data: messageData, + suggest: [ + { + messageId: MESSAGE_ID_SUGGESTION, + data: messageData, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix: fixer => replaceStringLiteral(fixer, node, replacement), + }, + ], + }; + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Enforce consistent case for text encoding identifiers.', + }, + hasSuggestions: true, + messages, + }, +}; diff --git a/test/run-rules-on-codebase/lint.mjs b/test/run-rules-on-codebase/lint.mjs index a9f5fba0dd..c3b84d70fa 100644 --- a/test/run-rules-on-codebase/lint.mjs +++ b/test/run-rules-on-codebase/lint.mjs @@ -35,6 +35,8 @@ const eslint = new ESLint({ 'unicorn/prefer-string-replace-all': 'off', 'unicorn/prefer-top-level-await': 'off', 'unicorn/prefer-at': 'off', + // TODO: Turn this on when `xo` updated `eslint-plugin-unicorn` + 'unicorn/text-encoding-identifier-case': 'off', }, overrides: [ { diff --git a/test/snapshots/text-encoding-identifier-case.mjs.md b/test/snapshots/text-encoding-identifier-case.mjs.md new file mode 100644 index 0000000000..15980fd03d --- /dev/null +++ b/test/snapshots/text-encoding-identifier-case.mjs.md @@ -0,0 +1,89 @@ +# Snapshot report for `test/text-encoding-identifier-case.mjs` + +The actual snapshot is saved in `text-encoding-identifier-case.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## Invalid #1 + 1 | "UTF-8" + +> Error 1/1 + + `␊ + > 1 | "UTF-8"␊ + | ^^^^^^^ Prefer \`utf8\` over \`UTF-8\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`UTF-8\` with \`utf8\`.␊ + 1 | "utf8"␊ + ` + +## Invalid #2 + 1 | "utf-8" + +> Error 1/1 + + `␊ + > 1 | "utf-8"␊ + | ^^^^^^^ Prefer \`utf8\` over \`utf-8\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`utf-8\` with \`utf8\`.␊ + 1 | "utf8"␊ + ` + +## Invalid #3 + 1 | 'utf-8' + +> Error 1/1 + + `␊ + > 1 | 'utf-8'␊ + | ^^^^^^^ Prefer \`utf8\` over \`utf-8\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`utf-8\` with \`utf8\`.␊ + 1 | 'utf8'␊ + ` + +## Invalid #4 + 1 | "Utf8" + +> Error 1/1 + + `␊ + > 1 | "Utf8"␊ + | ^^^^^^ Prefer \`utf8\` over \`Utf8\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`Utf8\` with \`utf8\`.␊ + 1 | "utf8"␊ + ` + +## Invalid #5 + 1 | "ASCII" + +> Error 1/1 + + `␊ + > 1 | "ASCII"␊ + | ^^^^^^^ Prefer \`ascii\` over \`ASCII\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`ASCII\` with \`ascii\`.␊ + 1 | "ascii"␊ + ` + +## Invalid #6 + 1 | await fs.readFile(file, "UTF-8",) + +> Error 1/1 + + `␊ + > 1 | await fs.readFile(file, "UTF-8",)␊ + | ^^^^^^^ Prefer \`utf8\` over \`UTF-8\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`UTF-8\` with \`utf8\`.␊ + 1 | await fs.readFile(file, "utf8",)␊ + ` diff --git a/test/snapshots/text-encoding-identifier-case.mjs.snap b/test/snapshots/text-encoding-identifier-case.mjs.snap new file mode 100644 index 0000000000000000000000000000000000000000..4dbb4876666d882105e32c8ee6106c403502aab2 GIT binary patch literal 437 zcmV;m0ZRTsRzVKK85o!u*uk!0WMvR! zI>^9fr(md1qo5R8l4haAr2qsq3UOed5KxqwmRh8cPzsbvP{=O>u|dib^tiZmsSLP+ zOViU+i%T-|^ArsA4XqS{QVVhtlT)EKDwJoIWI$b_2XrOa$)#W?qxk12+&@4`T{I72 z_YqhjeZ2&+l2|{TfcwcY*xAz)m!A@glQT16o&u|+ucshZLOlg>4BS^>F3zY54RIqb zs=x~Aoch345*t;5j8H!%mM3PGD5MqZ6{RMoxMk*~YNP=P9hhHrG@-u51%^y0;Pfas fbb$WE9_Q-dETBHrvM0#dKvx3*bhZN@