From 8c907692711d4db768fabe9d4413501f450da4d4 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 10 Jun 2020 00:25:28 +0900 Subject: [PATCH 1/8] feat(eslint-plugin): add extension rule `no-loss-of-precision` --- packages/eslint-plugin/README.md | 1 + .../docs/rules/no-loss-of-precision.md | 18 ++++++++ packages/eslint-plugin/src/configs/all.ts | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-loss-of-precision.ts | 41 +++++++++++++++++++ .../tests/rules/no-loss-of-precision.test.ts | 36 ++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 13 ++++++ 7 files changed, 113 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-loss-of-precision.md create mode 100644 packages/eslint-plugin/src/rules/no-loss-of-precision.ts create mode 100644 packages/eslint-plugin/tests/rules/no-loss-of-precision.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 53b8cbb9412..cc2bbc07899 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -191,6 +191,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | | [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | disallow `this` keywords outside of classes or class-like objects | | | | +| [`@typescript-eslint/no-loss-of-precision`](./docs/rules/no-loss-of-precision.md) | Disallow literal numbers that lose precision | | | | | [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | | | [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | | | [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | | diff --git a/packages/eslint-plugin/docs/rules/no-loss-of-precision.md b/packages/eslint-plugin/docs/rules/no-loss-of-precision.md new file mode 100644 index 00000000000..dc1cb466f39 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-loss-of-precision.md @@ -0,0 +1,18 @@ +# Disallow literal numbers that lose precision (`no-loss-of-precision`) + +## Rule Details + +This rule extends the base [`eslint/no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision) rule. +It adds support for numeric separators. + +## How to use + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "no-loss-of-precision": "off", + "@typescript-eslint/no-loss-of-precision": ["error"] +} +``` + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-loss-of-precision.md) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 25001233883..642d6f736d0 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -59,6 +59,8 @@ export = { 'no-invalid-this': 'off', '@typescript-eslint/no-invalid-this': 'error', '@typescript-eslint/no-invalid-void-type': 'error', + 'no-loss-of-precision': 'off', + '@typescript-eslint/no-loss-of-precision': 'error', 'no-magic-numbers': 'off', '@typescript-eslint/no-magic-numbers': 'error', '@typescript-eslint/no-misused-new': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 05016d5705f..37ad1da4806 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -96,6 +96,7 @@ import typedef from './typedef'; import unboundMethod from './unbound-method'; import unifiedSignatures from './unified-signatures'; import linesBetweenClassMembers from './lines-between-class-members'; +import noLossOfPrecision from './no-loss-of-precision'; export default { 'adjacent-overload-signatures': adjacentOverloadSignatures, @@ -196,4 +197,5 @@ export default { 'unbound-method': unboundMethod, 'unified-signatures': unifiedSignatures, 'lines-between-class-members': linesBetweenClassMembers, + 'no-loss-of-precision': noLossOfPrecision, }; diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts new file mode 100644 index 00000000000..ec38d8c8ef6 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -0,0 +1,41 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/no-loss-of-precision'; +import * as util from '../util'; + +type Options = util.InferOptionsTypeFromRule; +type MessageIds = util.InferMessageIdsTypeFromRule; + +export default util.createRule({ + name: 'no-loss-of-precision', + meta: { + type: 'problem', + docs: { + description: 'Disallow literal numbers that lose precision', + category: 'Possible Errors', + recommended: false, + extendsBaseRule: true, + }, + schema: [], + messages: baseRule.meta.messages, + }, + defaultOptions: [], + create(context) { + const rules = baseRule.create(context); + + function isSeperatedNumeric(node: TSESTree.Literal): boolean { + return typeof node.value === 'number' && node.raw.includes('_'); + } + return { + Literal(node: TSESTree.Literal): void { + if (isSeperatedNumeric(node)) { + rules.Literal({ + ...node, + raw: node.raw.replace(/_/g, ''), + }); + return; + } + rules.Literal(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-loss-of-precision.test.ts b/packages/eslint-plugin/tests/rules/no-loss-of-precision.test.ts new file mode 100644 index 00000000000..f2405bacdb6 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-loss-of-precision.test.ts @@ -0,0 +1,36 @@ +import rule from '../../src/rules/no-loss-of-precision'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-loss-of-precision', rule, { + valid: [ + 'const x = 12345;', + 'const x = 123.456;', + 'const x = -123.456;', + 'const x = 123_456;', + 'const x = 123_00_000_000_000_000_000_000_000;', + 'const x = 123.000_000_000_000_000_000_000_0;', + ], + invalid: [ + { + code: 'const x = 9007199254740993;', + errors: [{ messageId: 'noLossOfPrecision' }], + }, + { + code: 'const x = 9_007_199_254_740_993;', + errors: [{ messageId: 'noLossOfPrecision' }], + }, + { + code: 'const x = 9_007_199_254_740.993e3;', + errors: [{ messageId: 'noLossOfPrecision' }], + }, + { + code: + 'const x = 0b100_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_001;', + errors: [{ messageId: 'noLossOfPrecision' }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index c27e620399e..cc49e8f178c 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -704,3 +704,16 @@ declare module 'eslint/lib/rules/dot-notation' { >; export = rule; } + +declare module 'eslint/lib/rules/no-loss-of-precision' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + 'noLossOfPrecision', + [], + { + Literal(node: TSESTree.Literal): void; + } + >; + export = rule; +} From d5c31e195c252a461f26bb00d55fa8e9a54a1db6 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 10 Jun 2020 03:11:45 +0900 Subject: [PATCH 2/8] bump eslint (7.0.0 -> 7.2.0) --- package.json | 2 +- yarn.lock | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index a3ffa851516..e01a0ed0c89 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "cspell": "^4.0.61", "cz-conventional-changelog": "^3.2.0", "downlevel-dts": "^0.4.0", - "eslint": "^7.0.0", + "eslint": "^7.2.0", "eslint-plugin-eslint-comments": "^3.1.2", "eslint-plugin-eslint-plugin": "^2.2.1", "eslint-plugin-import": "^2.20.2", diff --git a/yarn.lock b/yarn.lock index 9b6fedc38d8..337037083ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1750,7 +1750,7 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.1.0, acorn@^7.1.1: +acorn@^7.1.0, acorn@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== @@ -3614,6 +3614,14 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" @@ -3626,10 +3634,15 @@ eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.0.0.tgz#c35dfd04a4372110bd78c69a8d79864273919a08" - integrity sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg== +eslint-visitor-keys@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" + integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== + +eslint@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.2.0.tgz#d41b2e47804b30dbabb093a967fb283d560082e6" + integrity sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -3637,10 +3650,10 @@ eslint@^7.0.0: cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^5.0.0" + eslint-scope "^5.1.0" eslint-utils "^2.0.0" - eslint-visitor-keys "^1.1.0" - espree "^7.0.0" + eslint-visitor-keys "^1.2.0" + espree "^7.1.0" esquery "^1.2.0" esutils "^2.0.2" file-entry-cache "^5.0.1" @@ -3668,14 +3681,14 @@ eslint@^7.0.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.0.0.tgz#8a7a60f218e69f120a842dc24c5a88aa7748a74e" - integrity sha512-/r2XEx5Mw4pgKdyb7GNLQNsu++asx/dltf/CI8RFi9oGHxmQFgvLbc5Op4U6i8Oaj+kdslhJtVlEZeAqH5qOTw== +espree@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" + integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== dependencies: - acorn "^7.1.1" + acorn "^7.2.0" acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + eslint-visitor-keys "^1.2.0" esprima@^2.7.0: version "2.7.3" From 7647d52d0e7747a065453f78d8afa0571a831a54 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 10 Jun 2020 17:38:01 +0900 Subject: [PATCH 3/8] fix: handling eslint version < 7 --- .../src/rules/no-loss-of-precision.ts | 28 ++++++++++++++----- .../fixtures/eslint-v6/test.js.snap | 5 ++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index ec38d8c8ef6..388e32f86ec 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -1,9 +1,17 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import baseRule from 'eslint/lib/rules/no-loss-of-precision'; +import BaseRule from 'eslint/lib/rules/no-loss-of-precision'; import * as util from '../util'; -type Options = util.InferOptionsTypeFromRule; -type MessageIds = util.InferMessageIdsTypeFromRule; +const baseRule = ((): typeof BaseRule | null => { + try { + return require('eslint/lib/rules/no-loss-of-precision'); + } catch { + return null; + } +})(); + +type Options = util.InferOptionsTypeFromRule; +type MessageIds = util.InferMessageIdsTypeFromRule; export default util.createRule({ name: 'no-loss-of-precision', @@ -16,11 +24,17 @@ export default util.createRule({ extendsBaseRule: true, }, schema: [], - messages: baseRule.meta.messages, + messages: baseRule?.meta?.messages ?? { noLossOfPrecision: '' }, }, defaultOptions: [], create(context) { - const rules = baseRule.create(context); + if (baseRule === null) { + throw new Error( + '@typescript-eslint/no-loss-of-precision requires at least ESLint v7.1.0', + ); + } + + const rules = baseRule?.create(context); function isSeperatedNumeric(node: TSESTree.Literal): boolean { return typeof node.value === 'number' && node.raw.includes('_'); @@ -28,13 +42,13 @@ export default util.createRule({ return { Literal(node: TSESTree.Literal): void { if (isSeperatedNumeric(node)) { - rules.Literal({ + rules?.Literal({ ...node, raw: node.raw.replace(/_/g, ''), }); return; } - rules.Literal(node); + rules?.Literal(node); }, }; }, diff --git a/tests/integration/fixtures/eslint-v6/test.js.snap b/tests/integration/fixtures/eslint-v6/test.js.snap index 350194ef759..b4fccf25c07 100644 --- a/tests/integration/fixtures/eslint-v6/test.js.snap +++ b/tests/integration/fixtures/eslint-v6/test.js.snap @@ -1,5 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`it should fail 1`] = ` +"Error while loading rule '@typescript-eslint/no-loss-of-precision': dd +Occurred while linting /usr/linked/index.ts" +`; + exports[`it should produce the expected lint ouput 1`] = ` Array [ Object { From ab555f16a30cd6a3c279c3add95fd0f9ad0e4951 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 10 Jun 2020 19:26:59 +0900 Subject: [PATCH 4/8] delete snapshot --- tests/integration/fixtures/eslint-v6/test.js.snap | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/integration/fixtures/eslint-v6/test.js.snap b/tests/integration/fixtures/eslint-v6/test.js.snap index b4fccf25c07..350194ef759 100644 --- a/tests/integration/fixtures/eslint-v6/test.js.snap +++ b/tests/integration/fixtures/eslint-v6/test.js.snap @@ -1,10 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`it should fail 1`] = ` -"Error while loading rule '@typescript-eslint/no-loss-of-precision': dd -Occurred while linting /usr/linked/index.ts" -`; - exports[`it should produce the expected lint ouput 1`] = ` Array [ Object { From 30ac14f0eef7d8587efda8092bcc2a8d2d75ac13 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Wed, 10 Jun 2020 19:39:34 +0900 Subject: [PATCH 5/8] Update packages/eslint-plugin/docs/rules/no-loss-of-precision.md Co-authored-by: Brad Zacher --- packages/eslint-plugin/docs/rules/no-loss-of-precision.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-loss-of-precision.md b/packages/eslint-plugin/docs/rules/no-loss-of-precision.md index dc1cb466f39..d6cf7b71c14 100644 --- a/packages/eslint-plugin/docs/rules/no-loss-of-precision.md +++ b/packages/eslint-plugin/docs/rules/no-loss-of-precision.md @@ -3,7 +3,8 @@ ## Rule Details This rule extends the base [`eslint/no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision) rule. -It adds support for numeric separators. +It adds support for [numeric separators](https://github.com/tc39/proposal-numeric-separator). +Note that this rule requires ESLint v7. ## How to use From e4526b24d8e7be961e8cc4fac2ffd7464311817c Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 10 Jun 2020 20:05:55 +0900 Subject: [PATCH 6/8] chore: istanbul comment --- packages/eslint-plugin/src/rules/no-loss-of-precision.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 388e32f86ec..788f45583b9 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -6,6 +6,7 @@ const baseRule = ((): typeof BaseRule | null => { try { return require('eslint/lib/rules/no-loss-of-precision'); } catch { + /* istanbul ignore next */ return null; } })(); @@ -28,7 +29,7 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - if (baseRule === null) { + /* istanbul ignore if */ if (baseRule === null) { throw new Error( '@typescript-eslint/no-loss-of-precision requires at least ESLint v7.1.0', ); From 22103bb86aab7d3463e81e8598b3bb91dfb60487 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Fri, 12 Jun 2020 12:54:03 +0900 Subject: [PATCH 7/8] refactor --- .../src/rules/no-loss-of-precision.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 788f45583b9..431434a4df7 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -35,21 +35,18 @@ export default util.createRule({ ); } - const rules = baseRule?.create(context); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const rules = baseRule!.create(context); function isSeperatedNumeric(node: TSESTree.Literal): boolean { return typeof node.value === 'number' && node.raw.includes('_'); } return { Literal(node: TSESTree.Literal): void { - if (isSeperatedNumeric(node)) { - rules?.Literal({ - ...node, - raw: node.raw.replace(/_/g, ''), - }); - return; - } - rules?.Literal(node); + rules.Literal({ + ...node, + raw: isSeperatedNumeric(node) ? node.raw.replace(/_/g, '') : node.raw, + }); }, }; }, From c6f81e4f4e1e253ded523b1b7f219f864ed827ed Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Fri, 12 Jun 2020 12:57:25 +0900 Subject: [PATCH 8/8] refactor --- packages/eslint-plugin/src/rules/no-loss-of-precision.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 431434a4df7..47ab5b1a74f 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -25,7 +25,7 @@ export default util.createRule({ extendsBaseRule: true, }, schema: [], - messages: baseRule?.meta?.messages ?? { noLossOfPrecision: '' }, + messages: baseRule?.meta.messages ?? { noLossOfPrecision: '' }, }, defaultOptions: [], create(context) {