diff --git a/.changeset/fluffy-grapes-raise.md b/.changeset/fluffy-grapes-raise.md new file mode 100644 index 00000000..4da577a2 --- /dev/null +++ b/.changeset/fluffy-grapes-raise.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-yml": minor +--- + +feat: add `yml/file-extension` rule diff --git a/README.md b/README.md index 12bc6a0e..ce7a2e0e 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ The rules with the following star :star: are included in the config. | [yml/block-mapping](https://ota-meshi.github.io/eslint-plugin-yml/rules/block-mapping.html) | require or disallow block style mappings. | :wrench: | | :star: | | [yml/block-sequence-hyphen-indicator-newline](https://ota-meshi.github.io/eslint-plugin-yml/rules/block-sequence-hyphen-indicator-newline.html) | enforce consistent line breaks after `-` indicator | :wrench: | | :star: | | [yml/block-sequence](https://ota-meshi.github.io/eslint-plugin-yml/rules/block-sequence.html) | require or disallow block style sequences. | :wrench: | | :star: | +| [yml/file-extension](https://ota-meshi.github.io/eslint-plugin-yml/rules/file-extension.html) | enforce YAML file extension | | | | | [yml/indent](https://ota-meshi.github.io/eslint-plugin-yml/rules/indent.html) | enforce consistent indentation | :wrench: | | :star: | | [yml/key-name-casing](https://ota-meshi.github.io/eslint-plugin-yml/rules/key-name-casing.html) | enforce naming convention to key names | | | | | [yml/no-empty-document](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-empty-document.html) | disallow empty document | | :star: | :star: | diff --git a/docs/rules/README.md b/docs/rules/README.md index 44150882..22a48bee 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -18,6 +18,7 @@ The rules with the following star :star: are included in the `plugin:yml/recomme | [yml/block-mapping](./block-mapping.md) | require or disallow block style mappings. | :wrench: | | :star: | | [yml/block-sequence-hyphen-indicator-newline](./block-sequence-hyphen-indicator-newline.md) | enforce consistent line breaks after `-` indicator | :wrench: | | :star: | | [yml/block-sequence](./block-sequence.md) | require or disallow block style sequences. | :wrench: | | :star: | +| [yml/file-extension](./file-extension.md) | enforce YAML file extension | | | | | [yml/indent](./indent.md) | enforce consistent indentation | :wrench: | | :star: | | [yml/key-name-casing](./key-name-casing.md) | enforce naming convention to key names | | | | | [yml/no-empty-document](./no-empty-document.md) | disallow empty document | | :star: | :star: | diff --git a/docs/rules/block-mapping-colon-indicator-newline.md b/docs/rules/block-mapping-colon-indicator-newline.md index 33b79ac8..51a8083d 100644 --- a/docs/rules/block-mapping-colon-indicator-newline.md +++ b/docs/rules/block-mapping-colon-indicator-newline.md @@ -10,7 +10,6 @@ description: "enforce consistent line breaks after `:` indicator" > enforce consistent line breaks after `:` indicator - :exclamation: **_This rule has not been released yet._** -- :gear: This rule is included in . - :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 diff --git a/docs/rules/file-extension.md b/docs/rules/file-extension.md new file mode 100644 index 00000000..549e050f --- /dev/null +++ b/docs/rules/file-extension.md @@ -0,0 +1,60 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "yml/file-extension" +description: "enforce YAML file extension" +--- + +# yml/file-extension + +> enforce YAML file extension + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule aims to enforce YAML file extension. + + + + + +```yaml +# ✓ GOOD +# filename is `example.yaml` + +# eslint yml/file-extension: 'error' +``` + + + + + + + +```yaml +# ✗ BAD +# filename is `example.yml` + +# eslint yml/file-extension: 'error' +``` + + + +## :wrench: Options + +```yaml +yml/file-extension: + - error + - extension: yaml # or 'yml' + caseSensitive: true +``` + +- `extension` ... The extension you want to enforce. Default is `"yaml"`. +- `caseSensitive` ... Specify `true` to enforce lowercase extension. Default is `true`. + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-yml/blob/master/src/rules/file-extension.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-yml/blob/master/tests/src/rules/file-extension.ts) +- [Test fixture sources](https://github.com/ota-meshi/eslint-plugin-yml/tree/master/tests/fixtures/rules/file-extension) diff --git a/src/rules/file-extension.ts b/src/rules/file-extension.ts new file mode 100644 index 00000000..e7ff1f2c --- /dev/null +++ b/src/rules/file-extension.ts @@ -0,0 +1,60 @@ +import path from "path"; +import { createRule } from "../utils"; + +export default createRule("file-extension", { + meta: { + docs: { + description: "enforce YAML file extension", + categories: [], + extensionRule: false, + layout: false, + }, + schema: [ + { + type: "object", + properties: { + extension: { + enum: ["yaml", "yml"], + }, + caseSensitive: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + unexpected: `Expected extension '{{expected}}' but used extension '{{actual}}'.`, + }, + type: "suggestion", + }, + create(context) { + if (!context.parserServices.isYAML) { + return {}; + } + const expected: string = context.options[0]?.extension || "yaml"; + const caseSensitive: string = context.options[0]?.caseSensitive ?? true; + + return { + Program(node) { + const filename = context.getFilename(); + const actual = path.extname(filename); + if ( + (caseSensitive ? actual : actual.toLocaleLowerCase()) === + `.${expected}` + ) { + return; + } + context.report({ + node, + loc: node.loc.start, + messageId: "unexpected", + data: { + expected: `.${expected}`, + actual, + }, + }); + }, + }; + }, +}); diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 1017b631..a5ef2728 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -4,6 +4,7 @@ import blockMappingQuestionIndicatorNewline from "../rules/block-mapping-questio import blockMapping from "../rules/block-mapping"; import blockSequenceHyphenIndicatorNewline from "../rules/block-sequence-hyphen-indicator-newline"; import blockSequence from "../rules/block-sequence"; +import fileExtension from "../rules/file-extension"; import flowMappingCurlyNewline from "../rules/flow-mapping-curly-newline"; import flowMappingCurlySpacing from "../rules/flow-mapping-curly-spacing"; import flowSequenceBracketNewline from "../rules/flow-sequence-bracket-newline"; @@ -32,6 +33,7 @@ export const rules = [ blockMapping, blockSequenceHyphenIndicatorNewline, blockSequence, + fileExtension, flowMappingCurlyNewline, flowMappingCurlySpacing, flowSequenceBracketNewline, diff --git a/tests/src/rules/file-extension.ts b/tests/src/rules/file-extension.ts new file mode 100644 index 00000000..4ecb67a6 --- /dev/null +++ b/tests/src/rules/file-extension.ts @@ -0,0 +1,58 @@ +import { RuleTester } from "eslint"; +import rule from "../../../src/rules/file-extension"; + +const tester = new RuleTester({ + parser: require.resolve("yaml-eslint-parser"), + parserOptions: { + ecmaVersion: 2020, + }, +}); + +tester.run("file-extension", rule as any, { + valid: [ + { + filename: "test.yaml", + code: "a: b", + }, + { + filename: "test.yml", + code: "a: b", + options: [{ extension: "yml" }], + }, + { + filename: "test.yaml", + code: "a: b", + options: [{ extension: "yaml" }], + }, + { + filename: "test.YAML", + code: "a: b", + options: [{ extension: "yaml", caseSensitive: false }], + }, + ], + invalid: [ + { + filename: "test.yml", + code: "a: b", + errors: ["Expected extension '.yaml' but used extension '.yml'."], + }, + { + filename: "test.yaml", + code: "a: b", + options: [{ extension: "yml" }], + errors: ["Expected extension '.yml' but used extension '.yaml'."], + }, + { + filename: "test.yml", + code: "a: b", + options: [{ extension: "yaml" }], + errors: ["Expected extension '.yaml' but used extension '.yml'."], + }, + { + filename: "test.YAML", + code: "a: b", + options: [{ extension: "yaml" }], + errors: ["Expected extension '.yaml' but used extension '.YAML'."], + }, + ], +}); diff --git a/tools/update-docs.ts b/tools/update-docs.ts index 11f603d8..20212b8f 100644 --- a/tools/update-docs.ts +++ b/tools/update-docs.ts @@ -92,7 +92,7 @@ class DocFile { notes.push("- :warning: This rule was **deprecated**."); } } else { - if (categories) { + if (categories && categories.length) { const presets = []; // eslint-disable-next-line @typescript-eslint/require-array-sort-compare -- ignore for (const cat of categories.sort()) {