Repository: sindresorhus/eslint-plugin-unicorn (JavaScript)

Issue:
Rule proposal: No zero fractions
<!-- Issuehunt Badges -->

[<img alt="Issuehunt badges" src="https://issuehunt.io/r/sindresorhus/eslint-plugin-unicorn/issues/207/badge.svg" />](https://issuehunt.io/r/sindresorhus/eslint-plugin-unicorn/issues/207)

<!-- /Issuehunt Badges -->

There are no difference between `1` and `1.0` in JS. For consistency, the former should be preferred.

## Fail

```js
console.log(1.0 + 5.0);
console.log(3.00000000);
```

## Pass

```js
console.log(1 + 5);
console.log(3);
console.log(7.0000001);
```
<!-- Issuehunt content -->



> [stroncium](https://issuehunt.io/u/stroncium) earned $60.00 by resolving this issue!
> - Checkout the [Issuehunt explorer](https://issuehunt.io/r/sindresorhus/eslint-plugin-unicorn/) to discover more funded issues.
> - Need some help from other developers? [Add your repositories](https://issuehunt.io/r/new) on Issuehunt to raise funds.


<!-- /Issuehunt content-->


Discussion:
Accepted. PR welcome :)
@issuehuntfest has funded $60.00 to this issue. [See it on IssueHunt](https://issuehunt.io/repos/55832243/issues/207)
@sindresorhus I would like to take this. 
@arshadkazmi42  I have already started the work :)
@gurungrahul2 ok. thanks for information 👍 
Sorry about triple reference, didn't know github added cross-repo referencing, consider only the last one.
@sindresorhus has rewarded $54.00 to @stroncium. [See it on IssueHunt](https://issuehunt.io/repos/55832243/issues/207)

- :moneybag: Total deposit: $60.00
- :tada: Repository reward(0%): $0.00
- :wrench: Service fee(10%): $6.00



Patch:
diff --git a/docs/rules/no-zero-fractions.md b/docs/rules/no-zero-fractions.md
new file mode 100644
index 0000000000..73f9b8aacd
--- /dev/null
+++ b/docs/rules/no-zero-fractions.md
@@ -0,0 +1,29 @@
+# Disallow number literals with zero fractions or dangling dots
+
+There is no difference in JavaScript between, for example, `1`, `1.0` and `1.`, so prefer the former for consistency.
+
+This rule is fixable.
+
+## Fail
+
+```js
+const foo = 1.0;
+const foo = -1.0;
+const foo = 123123123.0;
+const foo = 1.;
+const foo = 123.111000000;
+const foo = 123.00e20;
+```
+
+
+## Pass
+
+```js
+const foo = 1;
+const foo = -1;
+const foo = 123123123;
+const foo = 1.1;
+const foo = -1.1;
+const foo = 123123123.4;
+const foo = 1e3;
+```
diff --git a/index.js b/index.js
index 822316ab22..ef66bf7210 100644
--- a/index.js
+++ b/index.js
@@ -49,7 +49,8 @@ module.exports = {
 				'unicorn/prefer-text-content': 'error',
 				'unicorn/no-for-loop': 'error',
 				'unicorn/prevent-abbreviations': 'error',
-				'unicorn/prefer-includes': 'error'
+				'unicorn/prefer-includes': 'error',
+				'unicorn/no-zero-fractions': 'error'
 			}
 		}
 	}
diff --git a/readme.md b/readme.md
index e0a8bc1319..97e9374abe 100644
--- a/readme.md
+++ b/readme.md
@@ -66,7 +66,8 @@ Configure it in `package.json`.
 			"unicorn/prefer-text-content": "error",
 			"unicorn/no-for-loop": "error",
 			"unicorn/prevent-abbreviations": "error",
-			"unicorn/prefer-includes": "error"
+			"unicorn/prefer-includes": "error",
+			"unicorn/no-zero-fractions": "error"
 		}
 	}
 }
@@ -108,6 +109,7 @@ Configure it in `package.json`.
 - [no-for-loop](docs/rules/no-for-loop.md) - Do not use a `for` loop that can be replaced with a `for-of` loop. *(fixable)*
 - [prevent-abbreviations](docs/rules/prevent-abbreviations.md) - Prevent abbreviations *(partly fixable)*
 - [prefer-includes](docs/rules/prefer-includes.md) - Prefer `.includes()` over `.indexOf()` when checking for existence or non-existence. *(fixable)*
+- [no-zero-fractions](docs/rules/no-zero-fractions.md) - Disallow number literals with zero fractions or dangling dots. *(fixable)*
 
 
 ## Recommended config
diff --git a/rules/no-zero-fractions.js b/rules/no-zero-fractions.js
new file mode 100644
index 0000000000..497cfb520e
--- /dev/null
+++ b/rules/no-zero-fractions.js
@@ -0,0 +1,49 @@
+'use strict';
+const getDocsUrl = require('./utils/get-docs-url');
+
+const MESSAGE_ZERO_FRACTION = 'Don\'t use a zero fraction in the number.';
+const MESSAGE_DANGLING_DOT = 'Don\'t use a dangling dot in the number.';
+
+// Groups:
+// 1. Integer part
+// 2. Dangling dot or dot with zeroes
+// 3. Dot with digits except last zeroes
+// 4. Scientific notation
+const RE_DANGLINGDOT_OR_ZERO_FRACTIONS = /^([+-]?\d*)(?:(\.0*)|(\.\d*[1-9])0+)(e[+-]?\d+)?$/; // TODO: Possibly use named capture groups when targeting Node.js 10
+
+const create = context => {
+	return {
+		Literal: node => {
+			if (typeof node.value === 'number') {
+				const match = RE_DANGLINGDOT_OR_ZERO_FRACTIONS.exec(node.raw);
+				if (match !== null) {
+					const [, integerPart, dotAndZeroes, dotAndDigits, scientificNotationSuffix] = match;
+					const isDanglingDot = dotAndZeroes === '.';
+					context.report({
+						node,
+						message: isDanglingDot ? MESSAGE_DANGLING_DOT : MESSAGE_ZERO_FRACTION,
+						fix: fixer => {
+							let wantedString = (dotAndZeroes === undefined) ? integerPart + dotAndDigits : integerPart;
+							if (scientificNotationSuffix !== undefined) {
+								wantedString += scientificNotationSuffix;
+							}
+
+							return fixer.replaceText(node, wantedString);
+						}
+					});
+				}
+			}
+		}
+	};
+};
+
+module.exports = {
+	create,
+	meta: {
+		type: 'suggestion',
+		docs: {
+			url: getDocsUrl(__filename)
+		},
+		fixable: 'code'
+	}
+};


Test patch:
diff --git a/test/no-zero-fractions.js b/test/no-zero-fractions.js
new file mode 100644
index 0000000000..2cc3b1724c
--- /dev/null
+++ b/test/no-zero-fractions.js
@@ -0,0 +1,108 @@
+import test from 'ava';
+import avaRuleTester from 'eslint-ava-rule-tester';
+import rule from '../rules/no-zero-fractions';
+
+const ruleTester = avaRuleTester(test, {
+	env: {
+		es6: true
+	},
+	parserOptions: {
+		sourceType: 'module'
+	}
+});
+
+const errorZeroFraction = {
+	ruleId: 'no-zero-fractions',
+	message: 'Don\'t use a zero fraction in the number.'
+};
+const errorDanglingDot = {
+	ruleId: 'no-zero-fractions',
+	message: 'Don\'t use a dangling dot in the number.'
+};
+
+ruleTester.run('no-zero-fractions', rule, {
+	valid: [
+		'const foo = "123.1000"',
+		'foo("123.1000")',
+		'const foo = 1',
+		'const foo = 1 + 2',
+		'const foo = -1',
+		'const foo = 123123123',
+		'const foo = 1.1',
+		'const foo = -1.1',
+		'const foo = 123123123.4',
+		'const foo = 1e3'
+	],
+	invalid: [
+		{
+			code: 'const foo = 1.0',
+			output: 'const foo = 1',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = 1.0 + 1',
+			output: 'const foo = 1 + 1',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'foo(1.0 + 1)',
+			output: 'foo(1 + 1)',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = 1.00',
+			output: 'const foo = 1',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = 1.00000',
+			output: 'const foo = 1',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = -1.0',
+			output: 'const foo = -1',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = 123123123.0',
+			output: 'const foo = 123123123',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = 123.11100000000',
+			output: 'const foo = 123.111',
+			errors: [errorZeroFraction]
+		},
+		{
+			code: 'const foo = 1.',
+			output: 'const foo = 1',
+			errors: [errorDanglingDot]
+		},
+		{
+			code: 'const foo = +1.',
+			output: 'const foo = +1',
+			errors: [errorDanglingDot]
+		},
+		{
+			code: 'const foo = -1.',
+			output: 'const foo = -1',
+			errors: [errorDanglingDot]
+		},
+		{
+			code: 'const foo = 1.e10',
+			output: 'const foo = 1e10',
+			errors: [errorDanglingDot]
+		},
+		{
+			code: 'const foo = +1.e-10',
+			output: 'const foo = +1e-10',
+			errors: [errorDanglingDot]
+		},
+		{
+			code: 'const foo = -1.e+10',
+			output: 'const foo = -1e+10',
+			errors: [errorDanglingDot]
+		}
+	]
+});