diff --git a/.changeset/sixty-cougars-ring.md b/.changeset/sixty-cougars-ring.md
new file mode 100644
index 00000000..a45bbedb
--- /dev/null
+++ b/.changeset/sixty-cougars-ring.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-yml": minor
+---
+
+feat: add `yml/no-trailing-zeros` rule
diff --git a/README.md b/README.md
index ce7a2e0e..0212b8ac 100644
--- a/README.md
+++ b/README.md
@@ -207,6 +207,7 @@ The rules with the following star :star: are included in the config.
| [yml/no-empty-mapping-value](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-empty-mapping-value.html) | disallow empty mapping values | | :star: | :star: |
| [yml/no-empty-sequence-entry](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-empty-sequence-entry.html) | disallow empty sequence entries | | :star: | :star: |
| [yml/no-tab-indent](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-tab-indent.html) | disallow tabs for indentation. | | :star: | :star: |
+| [yml/no-trailing-zeros](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-trailing-zeros.html) | disallow trailing zeros for floats | :wrench: | | |
| [yml/plain-scalar](https://ota-meshi.github.io/eslint-plugin-yml/rules/plain-scalar.html) | require or disallow plain style scalar. | :wrench: | | :star: |
| [yml/quotes](https://ota-meshi.github.io/eslint-plugin-yml/rules/quotes.html) | enforce the consistent use of either double, or single quotes | :wrench: | | :star: |
| [yml/require-string-key](https://ota-meshi.github.io/eslint-plugin-yml/rules/require-string-key.html) | disallow mapping keys other than strings | | | |
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 22a48bee..800eabfc 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -26,6 +26,7 @@ The rules with the following star :star: are included in the `plugin:yml/recomme
| [yml/no-empty-mapping-value](./no-empty-mapping-value.md) | disallow empty mapping values | | :star: | :star: |
| [yml/no-empty-sequence-entry](./no-empty-sequence-entry.md) | disallow empty sequence entries | | :star: | :star: |
| [yml/no-tab-indent](./no-tab-indent.md) | disallow tabs for indentation. | | :star: | :star: |
+| [yml/no-trailing-zeros](./no-trailing-zeros.md) | disallow trailing zeros for floats | :wrench: | | |
| [yml/plain-scalar](./plain-scalar.md) | require or disallow plain style scalar. | :wrench: | | :star: |
| [yml/quotes](./quotes.md) | enforce the consistent use of either double, or single quotes | :wrench: | | :star: |
| [yml/require-string-key](./require-string-key.md) | disallow mapping keys other than strings | | | |
diff --git a/docs/rules/no-trailing-zeros.md b/docs/rules/no-trailing-zeros.md
new file mode 100644
index 00000000..3159823f
--- /dev/null
+++ b/docs/rules/no-trailing-zeros.md
@@ -0,0 +1,43 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "yml/no-trailing-zeros"
+description: "disallow trailing zeros for floats"
+---
+
+# yml/no-trailing-zeros
+
+> disallow trailing zeros for floats
+
+- :exclamation: **_This rule has not been released yet._**
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule enforces the removal of unnecessary trailing zeros from floats.
+
+
+
+
+
+```yaml
+# eslint yml/no-trailing-zeros: 'error'
+
+# ✓ GOOD
+"GOOD": 1.2
+
+# ✗ BAD
+'BAD': 1.20
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-yml/blob/master/src/rules/no-trailing-zeros.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-yml/blob/master/tests/src/rules/no-trailing-zeros.ts)
+- [Test fixture sources](https://github.com/ota-meshi/eslint-plugin-yml/tree/master/tests/fixtures/rules/no-trailing-zeros)
diff --git a/src/configs/prettier.ts b/src/configs/prettier.ts
index 2267e337..349f7948 100644
--- a/src/configs/prettier.ts
+++ b/src/configs/prettier.ts
@@ -15,6 +15,7 @@ export = {
"yml/indent": "off",
"yml/key-spacing": "off",
"yml/no-multiple-empty-lines": "off",
+ "yml/no-trailing-zeros": "off",
"yml/quotes": "off",
},
};
diff --git a/src/rules/no-trailing-zeros.ts b/src/rules/no-trailing-zeros.ts
new file mode 100644
index 00000000..6e2d86b9
--- /dev/null
+++ b/src/rules/no-trailing-zeros.ts
@@ -0,0 +1,57 @@
+import type { AST } from "yaml-eslint-parser";
+import { createRule } from "../utils";
+
+export default createRule("no-trailing-zeros", {
+ meta: {
+ docs: {
+ description: "disallow trailing zeros for floats",
+ categories: null,
+ extensionRule: false,
+ layout: true,
+ },
+ fixable: "code",
+ schema: [],
+ messages: {
+ wrongZeros: "Trailing zeros are not allowed, fix to `{{fixed}}`.",
+ },
+ type: "layout",
+ },
+ create(context) {
+ if (!context.parserServices.isYAML) {
+ return {};
+ }
+
+ return {
+ YAMLScalar(node: AST.YAMLScalar) {
+ if (node.style !== "plain") {
+ return;
+ } else if (typeof node.value !== "number") {
+ return;
+ } else if (!node.strValue.endsWith("0")) {
+ return;
+ }
+
+ const parts = node.strValue.split(".");
+ if (parts.length !== 2) {
+ return;
+ }
+
+ while (parts[1].endsWith("0")) {
+ parts[1] = parts[1].slice(0, -1);
+ }
+ const fixed = parts[1] ? parts.join(".") : parts[0] || "0";
+
+ context.report({
+ node,
+ messageId: "wrongZeros",
+ data: {
+ fixed,
+ },
+ fix(fixer) {
+ return fixer.replaceText(node, fixed);
+ },
+ });
+ },
+ };
+ },
+});
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index a5ef2728..167a4c10 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -19,6 +19,7 @@ import noEmptySequenceEntry from "../rules/no-empty-sequence-entry";
import noIrregularWhitespace from "../rules/no-irregular-whitespace";
import noMultipleEmptyLines from "../rules/no-multiple-empty-lines";
import noTabIndent from "../rules/no-tab-indent";
+import noTrailingZeros from "../rules/no-trailing-zeros";
import plainScalar from "../rules/plain-scalar";
import quotes from "../rules/quotes";
import requireStringKey from "../rules/require-string-key";
@@ -48,6 +49,7 @@ export const rules = [
noIrregularWhitespace,
noMultipleEmptyLines,
noTabIndent,
+ noTrailingZeros,
plainScalar,
quotes,
requireStringKey,
diff --git a/tests/fixtures/rules/no-trailing-zeros/invalid/errors.json b/tests/fixtures/rules/no-trailing-zeros/invalid/errors.json
new file mode 100644
index 00000000..962796ff
--- /dev/null
+++ b/tests/fixtures/rules/no-trailing-zeros/invalid/errors.json
@@ -0,0 +1,62 @@
+[
+ {
+ "message": "Trailing zeros are not allowed, fix to `1.2`.",
+ "line": 4,
+ "column": 10
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `.2`.",
+ "line": 6,
+ "column": 10
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `1`.",
+ "line": 7,
+ "column": 10
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `0`.",
+ "line": 8,
+ "column": 10
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `+1`.",
+ "line": 9,
+ "column": 10
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `-1`.",
+ "line": 10,
+ "column": 10
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `1.2`.",
+ "line": 14,
+ "column": 12
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `.2`.",
+ "line": 16,
+ "column": 12
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `1`.",
+ "line": 17,
+ "column": 12
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `0`.",
+ "line": 18,
+ "column": 12
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `+1`.",
+ "line": 19,
+ "column": 12
+ },
+ {
+ "message": "Trailing zeros are not allowed, fix to `-1`.",
+ "line": 20,
+ "column": 12
+ }
+]
diff --git a/tests/fixtures/rules/no-trailing-zeros/invalid/input.yml b/tests/fixtures/rules/no-trailing-zeros/invalid/input.yml
new file mode 100644
index 00000000..79b7b6f6
--- /dev/null
+++ b/tests/fixtures/rules/no-trailing-zeros/invalid/input.yml
@@ -0,0 +1,20 @@
+# {"options": []}
+- {
+ a: 1.23,
+ b: 1.20,
+ c: .23,
+ d: .20,
+ e: 1.0,
+ f: .0,
+ g: +1.0,
+ h: -1.0
+ }
+-
+ - "a": 1.23
+ - "b": 1.20
+ - "c": .23
+ - "d": .20
+ - "e": 1.0
+ - "f": .0
+ - "g": +1.0
+ - "h": -1.0
diff --git a/tests/fixtures/rules/no-trailing-zeros/invalid/output.yml b/tests/fixtures/rules/no-trailing-zeros/invalid/output.yml
new file mode 100644
index 00000000..2d6a8be0
--- /dev/null
+++ b/tests/fixtures/rules/no-trailing-zeros/invalid/output.yml
@@ -0,0 +1,20 @@
+# no-trailing-zeros/invalid/input.yml
+- {
+ a: 1.23,
+ b: 1.2,
+ c: .23,
+ d: .2,
+ e: 1,
+ f: 0,
+ g: +1,
+ h: -1
+ }
+-
+ - "a": 1.23
+ - "b": 1.2
+ - "c": .23
+ - "d": .2
+ - "e": 1
+ - "f": 0
+ - "g": +1
+ - "h": -1
diff --git a/tests/fixtures/rules/no-trailing-zeros/valid/input.yml b/tests/fixtures/rules/no-trailing-zeros/valid/input.yml
new file mode 100644
index 00000000..539feec0
--- /dev/null
+++ b/tests/fixtures/rules/no-trailing-zeros/valid/input.yml
@@ -0,0 +1,20 @@
+# {"options": []}
+- {
+ a: 1.23,
+ b: "1.20",
+ c: .23,
+ d: .2,
+ e: "1.0",
+ f: ".0",
+ g: "+1.0",
+ h: "-1.0"
+ }
+-
+ - "a": 1.23
+ - "b": "1.20"
+ - "c": .23
+ - "d": .2
+ - "e": "1.0"
+ - "f": ".0"
+ - "g": "+1.0"
+ - "h": "-1.0"
diff --git a/tests/src/rules/no-trailing-zeros.ts b/tests/src/rules/no-trailing-zeros.ts
new file mode 100644
index 00000000..31789409
--- /dev/null
+++ b/tests/src/rules/no-trailing-zeros.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint";
+import rule from "../../../src/rules/no-trailing-zeros";
+import { loadTestCases } from "../../utils/utils";
+
+const tester = new RuleTester({
+ parser: require.resolve("yaml-eslint-parser"),
+ parserOptions: {
+ ecmaVersion: 2020,
+ },
+});
+
+tester.run(
+ "no-trailing-zeros",
+ rule as any,
+ loadTestCases("no-trailing-zeros")
+);