diff --git a/jest.config.cjs b/jest.config.cjs index 684200a0..ccdb782a 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -1,7 +1,8 @@ +/* eslint filenames/match-exported:"off" -- trouble matching given period in filename */ // https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = { +const jestConfig = { preset: 'ts-jest/presets/default-esm', testEnvironment: 'node', testMatch: ['/test/**/*-test.ts'], @@ -22,3 +23,13 @@ module.exports = { }, }, }; + +if (process.version.startsWith('v14.')) { + // TODO: remove this workaround after dropping support for Node 14. + // Use number greater than number of test suites to avoid: "You are trying to `import` a file after the Jest environment has been torn down." + // https://github.com/facebook/jest/issues/11438#issuecomment-954155180 + jestConfig.maxConcurrency = 30; + jestConfig.maxWorkers = 30; +} + +module.exports = jestConfig; diff --git a/test/lib/__snapshots__/generator-test.ts.snap b/test/lib/__snapshots__/generator-test.ts.snap deleted file mode 100644 index 128e6d72..00000000 --- a/test/lib/__snapshots__/generator-test.ts.snap +++ /dev/null @@ -1,1488 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`generator #generate Missing plugin package.json \`name\` field throws an error 1`] = `"Could not find \`name\` field in ESLint plugin's package.json."`; - -exports[`generator #generate Missing plugin package.json throws an error 1`] = `"Could not find package.json of ESLint plugin."`; - -exports[`generator #generate No configs found omits the config column 1`] = ` -" - -| Name | Description | -| :----------------------------- | :------------ | -| [no-foo](docs/rules/no-foo.md) | disallow foo. | - -" -`; - -exports[`generator #generate No configs found omits the config column 2`] = ` -"# Disallow foo (\`test/no-foo\`) - - -" -`; - -exports[`generator #generate No exported rules object found throws an error 1`] = `"Could not find exported \`rules\` object in ESLint plugin."`; - -exports[`generator #generate README missing rule list markers and no rules section throws an error 1`] = `"README.md is missing rules list markers: "`; - -exports[`generator #generate README missing rule list markers but with rules section adds rule list markers to rule section 1`] = ` -"# eslint-plugin-test - -Foo. - -## Rules - - -| Name | Description | -| :----------------------------- | :--------------------- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | - - - -Old rules list. - -## Other - -Bar." -`; - -exports[`generator #generate Rule description needs to be formatted capitalizes the first letter and removes the trailing period from the description 1`] = ` -"# Disallow foo (\`test/no-foo\`) - - -" -`; - -exports[`generator #generate Scoped plugin name determines the correct plugin prefix 1`] = ` -"# Disallow foo (\`@my-scope/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate config with overrides generates the documentation 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | - - -" -`; - -exports[`generator #generate config with overrides generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate custom path to rule docs and rules list generates the documentation 1`] = ` -" - -| Name | -| :------------------------------- | -| [no-foo](rules/no-foo/no-foo.md) | - -" -`; - -exports[`generator #generate custom path to rule docs and rules list generates the documentation 2`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate deprecated function-style rule generates the documentation 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | πŸ’Ό | -| :----------------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | βœ… | - - -" -`; - -exports[`generator #generate deprecated function-style rule generates the documentation 2`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate deprecated rule - using prefix ahead of replacement rule name uses correct replacement rule link 1`] = ` -" - -❌ Deprecated. - -| Name | Description | ❌ | -| :----------------------------- | :----------- | :- | -| [no-bar](docs/rules/no-bar.md) | Description. | | -| [no-foo](docs/rules/no-foo.md) | Description. | ❌ | - -" -`; - -exports[`generator #generate deprecated rule - using prefix ahead of replacement rule name uses correct replacement rule link 2`] = ` -"# Description (\`test/no-foo\`) - -❌ This rule is deprecated. It was replaced by [\`no-bar\`](no-bar.md). - - -" -`; - -exports[`generator #generate deprecated rule - using prefix ahead of replacement rule name uses correct replacement rule link 3`] = ` -"# Description (\`test/no-bar\`) - - -" -`; - -exports[`generator #generate deprecated rule with no rule doc but --ignore-deprecated-rules omits the rule from the README and does not try to update its non-existent rule doc 1`] = ` -" - -| Name | -| :--- | - -" -`; - -exports[`generator #generate deprecated rules - with nested rule names has the correct links, especially replacement rule link 1`] = ` -" - -❌ Deprecated. - -| Name | Description | ❌ | -| :----------------------------------------------- | :----------- | :- | -| [category/no-bar](docs/rules/category/no-bar.md) | Description. | ❌ | -| [category/no-foo](docs/rules/category/no-foo.md) | Description. | ❌ | - -" -`; - -exports[`generator #generate deprecated rules - with nested rule names has the correct links, especially replacement rule link 2`] = ` -"# Description (\`test/category/no-foo\`) - -❌ This rule is deprecated. It was replaced by [\`category/no-bar\`](../category/no-bar.md). - - -" -`; - -exports[`generator #generate deprecated rules - with nested rule names has the correct links, especially replacement rule link 3`] = ` -"# Description (\`test/category/no-bar\`) - -❌ This rule is deprecated. It was replaced by [\`category/no-foo\`](../category/no-foo.md). - - -" -`; - -exports[`generator #generate deprecated rules updates the documentation 1`] = ` -" - -❌ Deprecated. - -| Name | Description | ❌ | -| :----------------------------- | :----------- | :- | -| [no-bar](docs/rules/no-bar.md) | Description. | ❌ | -| [no-baz](docs/rules/no-baz.md) | Description. | ❌ | -| [no-biz](docs/rules/no-biz.md) | Description. | | -| [no-foo](docs/rules/no-foo.md) | Description. | ❌ | - -" -`; - -exports[`generator #generate deprecated rules updates the documentation 2`] = ` -"# Description (\`test/no-foo\`) - -❌ This rule is deprecated. It was replaced by [\`no-bar\`](no-bar.md). - - -" -`; - -exports[`generator #generate deprecated rules updates the documentation 3`] = ` -"# Description (\`test/no-bar\`) - -❌ This rule is deprecated. - - -" -`; - -exports[`generator #generate deprecated rules updates the documentation 4`] = ` -"# Description (\`test/no-baz\`) - -❌ This rule is deprecated. - - -" -`; - -exports[`generator #generate deprecated rules updates the documentation 5`] = ` -"# Description (\`test/no-biz\`) - - -" -`; - -exports[`generator #generate lowercase README file generates the documentation 1`] = ` -" - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - -" -`; - -exports[`generator #generate no existing comment markers - minimal doc content generates the documentation 1`] = ` -"## Rules - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). - -| Name | Description | πŸ”§ | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | - - -" -`; - -exports[`generator #generate no existing comment markers - minimal doc content generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - - -" -`; - -exports[`generator #generate no existing comment markers - with no blank lines in existing content generates the documentation 1`] = ` -"## Rules - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). - -| Name | Description | πŸ”§ | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | - - -Existing rules section content." -`; - -exports[`generator #generate no existing comment markers - with no blank lines in existing content generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - - -Existing rule doc content." -`; - -exports[`generator #generate no existing comment markers - with one blank line around existing content generates the documentation 1`] = ` -"## Rules - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). - -| Name | Description | πŸ”§ | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | - - - -Existing rules section content." -`; - -exports[`generator #generate no existing comment markers - with one blank line around existing content generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - - - -Existing rule doc content." -`; - -exports[`generator #generate no rules with description generates the documentation 1`] = ` -"## Rules - - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - - -" -`; - -exports[`generator #generate no rules with description generates the documentation 2`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate one rule missing description generates the documentation 1`] = ` -"## Rules - - -| Name | Description | -| :----------------------------- | :---------------------- | -| [no-bar](docs/rules/no-bar.md) | | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | - - -" -`; - -exports[`generator #generate one rule missing description generates the documentation 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - - -" -`; - -exports[`generator #generate one rule missing description generates the documentation 3`] = ` -"# test/no-bar - - -" -`; - -exports[`generator #generate only a \`recommended\` config updates the documentation 1`] = ` -" - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :----------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description. | βœ… | - -" -`; - -exports[`generator #generate only a \`recommended\` config updates the documentation 2`] = ` -"# Description (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate plugin entry point in JSON format generates the documentation 1`] = ` -" - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo | βœ… | - -" -`; - -exports[`generator #generate plugin entry point in JSON format generates the documentation 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rule config with options generates the documentation 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | - - -" -`; - -exports[`generator #generate rule config with options generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rule doc without header marker but pre-existing header updates the documentation 1`] = ` -"# Description (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -Pre-existing notice about the rule being recommended. -## Rule details -Details." -`; - -exports[`generator #generate rule with long-enough description to require name column wrapping avoidance adds spaces to the name column 1`] = ` -"## Rules - - -| NameΒ Β  | Description | -| :----------------------------- | :---------------------------------------------------------------------------------- | -| [no-foo](docs/rules/no-foo.md) | over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars | - - -" -`; - -exports[`generator #generate rule with long-enough description to require name column wrapping avoidance adds spaces to the name column 2`] = ` -"# Over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars (\`test/no-foo\`) - - -" -`; - -exports[`generator #generate rule with long-enough description to require name column wrapping avoidance but rule name too short does not add spaces to name column 1`] = ` -"## Rules - - -| Name | Description | -| :----------------------- | :---------------------------------------------------------------------------------- | -| [foo](docs/rules/foo.md) | over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars | - - -" -`; - -exports[`generator #generate rule with long-enough description to require name column wrapping avoidance but rule name too short does not add spaces to name column 2`] = ` -"# Over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars (\`test/foo\`) - - -" -`; - -exports[`generator #generate rule with no meta object generates the documentation 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | πŸ’Ό | -| :----------------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | βœ… | - - -" -`; - -exports[`generator #generate rule with no meta object generates the documentation 2`] = ` -"# test/no-foo - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rule with options, options column/notice enabled displays the column and notice 1`] = ` -"## Rules - - -βš™οΈ Has configuration options. - -| Name | βš™οΈ | -| :----------------------------- | :- | -| [no-bar](docs/rules/no-bar.md) | βš™οΈ | -| [no-baz](docs/rules/no-baz.md) | | -| [no-biz](docs/rules/no-biz.md) | | -| [no-foo](docs/rules/no-foo.md) | βš™οΈ | - - -" -`; - -exports[`generator #generate rule with options, options column/notice enabled displays the column and notice 2`] = ` -"# test/no-foo - -βš™οΈ This rule is configurable. - - -## Options -" -`; - -exports[`generator #generate rule with options, options column/notice enabled displays the column and notice 3`] = ` -"# test/no-bar - -βš™οΈ This rule is configurable. - - -## Options -" -`; - -exports[`generator #generate rule with options, options column/notice enabled displays the column and notice 4`] = ` -"# test/no-biz - - -" -`; - -exports[`generator #generate rule with options, options column/notice enabled displays the column and notice 5`] = ` -"# test/no-baz - - -" -`; - -exports[`generator #generate rule with type, type column enabled displays the type 1`] = ` -"## Rules - - -πŸ—‚οΈ The type of rule.\\ -❗ Identifies problems that could cause errors or unexpected behavior.\\ -πŸ“– Identifies potential improvements.\\ -πŸ“ Focuses on code formatting. - -| Name | πŸ—‚οΈ | -| :----------------------------- | :-- | -| [no-bar](docs/rules/no-bar.md) | πŸ“– | -| [no-biz](docs/rules/no-biz.md) | πŸ“ | -| [no-boz](docs/rules/no-boz.md) | | -| [no-buz](docs/rules/no-buz.md) | | -| [no-foo](docs/rules/no-foo.md) | ❗ | - - -" -`; - -exports[`generator #generate rule with type, type column enabled displays the type 2`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate rule with type, type column enabled displays the type 3`] = ` -"# test/no-bar - - -" -`; - -exports[`generator #generate rule with type, type column enabled displays the type 4`] = ` -"# test/no-biz - - -" -`; - -exports[`generator #generate rule with type, type column enabled displays the type 5`] = ` -"# test/no-boz - - -" -`; - -exports[`generator #generate rule with type, type column enabled displays the type 6`] = ` -"# test/no-buz - - -" -`; - -exports[`generator #generate rule with type, type column enabled, but only an unknown type hides the type column and notice 1`] = ` -"## Rules - - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - - -" -`; - -exports[`generator #generate rule with type, type column enabled, but only an unknown type hides the type column and notice 2`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate rule with type, type column not enabled hides the type column 1`] = ` -"## Rules - - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - - -" -`; - -exports[`generator #generate rule with type, type column not enabled hides the type column 2`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -⚠️ Configurations set to warn in.\\ -🚫 Configurations disabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | ⚠️ | 🚫 | -| :----------------------------- | :--------------------- | :- | :----------- | :----------- | -| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | | | ![other][] βœ… | -| [no-baz](docs/rules/no-baz.md) | Description of no-baz. | βœ… | | ![other][] | -| [no-bez](docs/rules/no-bez.md) | Description of no-bez. | | ![other][] | | -| [no-biz](docs/rules/no-biz.md) | Description of no-biz. | | | ![other][] | -| [no-boz](docs/rules/no-boz.md) | Description of no-boz. | | βœ… | | -| [no-buz](docs/rules/no-buz.md) | Description of no-buz. | | ![other][] βœ… | | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | | | βœ… | - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -🚫 This rule is _disabled_ in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 3`] = ` -"# Description of no-bar (\`test/no-bar\`) - -🚫 This rule is _disabled_ in the following configs: \`other\`, βœ… \`recommended\`. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 4`] = ` -"# Description of no-baz (\`test/no-baz\`) - -πŸ’ΌπŸš« This rule is enabled in the βœ… \`recommended\` config. This rule is _disabled_ in the \`other\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 5`] = ` -"# Description of no-biz (\`test/no-biz\`) - -🚫 This rule is _disabled_ in the \`other\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 6`] = ` -"# Description of no-boz (\`test/no-boz\`) - -⚠️ This rule _warns_ in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 7`] = ` -"# Description of no-buz (\`test/no-buz\`) - -⚠️ This rule _warns_ in the following configs: \`other\`, βœ… \`recommended\`. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn generates the documentation 8`] = ` -"# Description of no-bez (\`test/no-bez\`) - -⚠️ This rule _warns_ in the \`other\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn, only one config present generates the documentation 1`] = ` -"## Rules - - -⚠️ Configurations set to warn in.\\ -🚫 Configurations disabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | ⚠️ | 🚫 | -| :----------------------------- | :--------------------- | :- | :- | -| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | | βœ… | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | | - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn, only one config present generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -⚠️ This rule _warns_ in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn, only one config present generates the documentation 3`] = ` -"# Description of no-bar (\`test/no-bar\`) - -🚫 This rule is _disabled_ in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn, two configs present generates the documentation 1`] = ` -"## Rules - - -⚠️ Configurations set to warn in.\\ -🚫 Configurations disabled in.\\ -βœ… Set in the \`recommended\` configuration.\\ -⌨️ Set in the \`typescript\` configuration. - -| Name | Description | ⚠️ | 🚫 | -| :----------------------------- | :--------------------- | :- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | ⌨️ | - - -" -`; - -exports[`generator #generate rules that are disabled or set to warn, two configs present generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -⚠️🚫 This rule _warns_ in the βœ… \`recommended\` config. This rule is _disabled_ in the ⌨️ \`typescript\` config. - - -" -`; - -exports[`generator #generate shows column and notice for requiresTypeChecking updates the documentation 1`] = ` -" - -πŸ’Ό Configurations enabled in.\\ -🌐 Set in the \`all\` configuration.\\ -πŸ’­ Requires type information. - -| Name | Description | πŸ’Ό | πŸ’­ | -| :----------------------------- | :--------------------- | :- | :- | -| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | | πŸ’­ | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | 🌐 | | - -" -`; - -exports[`generator #generate shows column and notice for requiresTypeChecking updates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the 🌐 \`all\` config. - - -" -`; - -exports[`generator #generate shows column and notice for requiresTypeChecking updates the documentation 3`] = ` -"# Description of no-bar (\`test/no-bar\`) - -πŸ’­ This rule requires type information. - - -" -`; - -exports[`generator #generate sorting rules and configs case-insensitive sorts correctly 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in. - -| Name | πŸ’Ό | -| :------------------- | :------------------- | -| [a](docs/rules/a.md) | ![a][] ![B][] ![c][] | -| [B](docs/rules/B.md) | | -| [c](docs/rules/c.md) | | - - -" -`; - -exports[`generator #generate sorting rules and configs case-insensitive sorts correctly 2`] = ` -"# test/a - -πŸ’Ό This rule is enabled in the following configs: \`a\`, \`B\`, \`c\`. - - -" -`; - -exports[`generator #generate sorting rules and configs case-insensitive sorts correctly 3`] = ` -"# test/B - - -" -`; - -exports[`generator #generate sorting rules and configs case-insensitive sorts correctly 4`] = ` -"# test/c - - -" -`; - -exports[`generator #generate splitting list by nested property meta.docs.category splits the list 1`] = ` -"## Rules - - -| Name | -| :----------------------------- | -| [no-baz](docs/rules/no-baz.md) | - -### candy - -| Name | -| :----------------------------- | -| [no-bar](docs/rules/no-bar.md) | - -### fruits - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - - -" -`; - -exports[`generator #generate splitting list by type splits the list 1`] = ` -"## Rules - - -| Name | -| :----------------------------- | -| [no-biz](docs/rules/no-biz.md) | - -### problem - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - -### suggestion - -| Name | -| :----------------------------- | -| [no-bar](docs/rules/no-bar.md) | -| [no-baz](docs/rules/no-baz.md) | - - -" -`; - -exports[`generator #generate splitting list, ignores case splits the list 1`] = ` -"## Rules - - -### a - -| Name | -| :----------------------------- | -| [no-bar](docs/rules/no-bar.md) | - -### B - -| Name | -| :----------------------------- | -| [no-baz](docs/rules/no-baz.md) | - -### c - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - - -" -`; - -exports[`generator #generate splitting list, with boolean splits the list 1`] = ` -"## Rules - - -πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). - -| Name | πŸ’‘ | -| :----------------------------- | :- | -| [no-bar](docs/rules/no-bar.md) | | -| [no-baz](docs/rules/no-baz.md) | | - -### Has Suggestions - -| Name | πŸ’‘ | -| :----------------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | πŸ’‘ | - - -" -`; - -exports[`generator #generate splitting list, with one sub-list having no rules enabled by the config splits the list and still uses recommended config emoji in both lists 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -### bar - -| Name | πŸ’Ό | -| :----------------------------- | :- | -| [no-bar](docs/rules/no-bar.md) | | - -### foo - -| Name | πŸ’Ό | -| :----------------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | βœ… | - - -" -`; - -exports[`generator #generate splitting list, with unknown variable type splits the list but does not attempt to convert variable name to title 1`] = ` -"## Rules - - -| Name | -| :----------------------------- | -| [no-foo](docs/rules/no-foo.md) | - -### foo_barBIZ-baz3bOz - -| Name | -| :----------------------------- | -| [no-bar](docs/rules/no-bar.md) | - - -" -`; - -exports[`generator #generate successful updates the documentation 1`] = ` -"# eslint-plugin-test -Description. -## Rules - - -πŸ’Ό Configurations enabled in.\\ -🌐 Set in the \`all\` configuration.\\ -βœ… Set in the \`recommended\` configuration.\\ -🎨 Set in the \`style\` configuration.\\ -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\ -πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). - -| Name | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | -| :----------------------------- | :--------------------- | :---- | :- | :- | -| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | 🌐 🎨 | πŸ”§ | | -| [no-baz](docs/rules/no-baz.md) | Description of no-boz. | | | | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | 🌐 βœ… | πŸ”§ | πŸ’‘ | - - -more content." -`; - -exports[`generator #generate successful updates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the following configs: 🌐 \`all\`, βœ… \`recommended\`. - -πŸ”§πŸ’‘ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). - - -## Rule details -details -## Options -optionToDoSomething1 - explanation -optionToDoSomething2 - explanation" -`; - -exports[`generator #generate successful updates the documentation 3`] = ` -"# Description of no-bar (\`test/no-bar\`) - -πŸ’Ό This rule is enabled in the following configs: 🌐 \`all\`, 🎨 \`style\`. - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - - -## Rule details -details" -`; - -exports[`generator #generate successful updates the documentation 4`] = ` -"# Description of no-boz (\`test/no-baz\`) - - -## Rule details -details" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = desc uses the right rule doc title format 1`] = ` -"# Description for no-foo - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = desc uses the right rule doc title format 2`] = ` -"# test/no-bar - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = desc-parens-name uses the right rule doc title format 1`] = ` -"# Description for no-foo (\`no-foo\`) - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = desc-parens-name uses the right rule doc title format 2`] = ` -"# no-bar - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = desc-parens-prefix-name uses the right rule doc title format, with fallback when missing description 1`] = ` -"# Description for no-foo (\`test/no-foo\`) - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = desc-parens-prefix-name uses the right rule doc title format, with fallback when missing description 2`] = ` -"# test/no-bar - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = name uses the right rule doc title format 1`] = ` -"# no-foo - - -" -`; - -exports[`generator #generate with \`--rule-doc-title-format\` option = prefix-name uses the right rule doc title format 1`] = ` -"# test/no-foo - - -" -`; - -exports[`generator #generate with \`--url-configs\` option includes the config link 1`] = ` -"## Rules - - -πŸ’Ό [Configurations](http://example.com/configs) enabled in.\\ -βœ… Set in the \`recommended\` [configuration](http://example.com/configs). - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :---------------- | -| [no-bar](docs/rules/no-bar.md) | Description for no-bar. | ![customConfig][] | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | - - -" -`; - -exports[`generator #generate with \`--url-configs\` option includes the config link 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` [config](http://example.com/configs). - - -" -`; - -exports[`generator #generate with \`--url-configs\` option includes the config link 3`] = ` -"# Description for no-bar (\`test/no-bar\`) - -πŸ’Ό This rule is enabled in the \`customConfig\` [config](http://example.com/configs). - - -" -`; - -exports[`generator #generate with \`--url-configs\` option with only recommended config includes the config link 1`] = ` -"## Rules - - -πŸ’Ό [Configurations](http://example.com/configs) enabled in.\\ -βœ… Set in the \`recommended\` [configuration](http://example.com/configs). - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | - - -" -`; - -exports[`generator #generate with \`--url-configs\` option with only recommended config includes the config link 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` [config](http://example.com/configs). - - -" -`; - -exports[`generator #generate with --check prints the issues, exits with failure, and does not write changes 1`] = ` -[ - "- Expected -+ Received - -- # Description for no-foo (\`test/no-foo\`) -- --  -- -+ # test/no-foo", -] -`; - -exports[`generator #generate with --check prints the issues, exits with failure, and does not write changes 2`] = ` -[ - "- Expected -+ Received - - ## Rules --  -- -- | Name | Description | -- | :----------------------------- | :---------------------- | -- | [no-foo](docs/rules/no-foo.md) | Description for no-foo. | -- --  -", -] -`; - -exports[`generator #generate with --check prints the issues, exits with failure, and does not write changes 3`] = ` -"## Rules -" -`; - -exports[`generator #generate with --check prints the issues, exits with failure, and does not write changes 4`] = `"# test/no-foo"`; - -exports[`generator #generate with --config-emoji and removing default emoji for a config reverts to using a badge for the config 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :--------------- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | ![recommended][] | - - -" -`; - -exports[`generator #generate with --config-emoji and removing default emoji for a config reverts to using a badge for the config 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the \`recommended\` config. - - -" -`; - -exports[`generator #generate with --config-emoji shows the correct emojis 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -πŸ”₯ Set in the \`recommended\` configuration.\\ -🎨 Set in the \`stylistic\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :------------------------- | -| [no-bar](docs/rules/no-bar.md) | Description for no-bar. | πŸ”₯ 🎨 | -| [no-baz](docs/rules/no-baz.md) | Description for no-boz. | ![configWithoutEmoji][] 🎨 | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | πŸ”₯ | - - -" -`; - -exports[`generator #generate with --config-emoji shows the correct emojis 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the πŸ”₯ \`recommended\` config. - - -" -`; - -exports[`generator #generate with --config-emoji shows the correct emojis 3`] = ` -"# Description for no-bar (\`test/no-bar\`) - -πŸ’Ό This rule is enabled in the following configs: πŸ”₯ \`recommended\`, 🎨 \`stylistic\`. - - -" -`; - -exports[`generator #generate with --config-emoji shows the correct emojis 4`] = ` -"# Description for no-boz (\`test/no-baz\`) - -πŸ’Ό This rule is enabled in the following configs: \`configWithoutEmoji\`, 🎨 \`stylistic\`. - - -" -`; - -exports[`generator #generate with --ignore-config hides the ignored config 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | - - -" -`; - -exports[`generator #generate with --ignore-config hides the ignored config 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate with --rule-doc-notices shows the right rule doc notices 1`] = ` -"## Rules - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\ -πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\\ -❌ Deprecated. - -| Name | Description | πŸ”§ | πŸ’‘ | ❌ | -| :----------------------------- | :---------------------- | :- | :- | :- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | πŸ”§ | πŸ’‘ | ❌ | - - -" -`; - -exports[`generator #generate with --rule-doc-notices shows the right rule doc notices 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - -❌ This rule is deprecated. - -❗ This rule identifies problems that could cause errors or unexpected behavior. - - -" -`; - -exports[`generator #generate with --rule-list-columns and consolidated fixableAndHasSuggestions column shows the right columns and legend 1`] = ` -"## Rules - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\ -πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). - -| Name | πŸ”§πŸ’‘ | -| :----------------------------- | :--- | -| [no-bar](docs/rules/no-bar.md) | πŸ”§ | -| [no-baz](docs/rules/no-baz.md) | | -| [no-foo](docs/rules/no-foo.md) | πŸ”§πŸ’‘ | - - -" -`; - -exports[`generator #generate with --rule-list-columns shows the right columns and legend 1`] = ` -"## Rules - - -πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\\ -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). - -| πŸ’‘ | πŸ”§ | Name | -| :- | :- | :----------------------------- | -| πŸ’‘ | πŸ”§ | [no-foo](docs/rules/no-foo.md) | - - -" -`; - -exports[`generator #generate with --rule-list-columns shows the right columns and legend 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -❌ This rule is deprecated. - -πŸ”§πŸ’‘ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). - - -" -`; - -exports[`generator #generate with config that does not have any rules uses recommended config emoji since it is the only relevant config 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in.\\ -βœ… Set in the \`recommended\` configuration. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | - - -" -`; - -exports[`generator #generate with config that does not have any rules uses recommended config emoji since it is the only relevant config 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. - - -" -`; - -exports[`generator #generate with no blank lines around comment markers generates the documentation 1`] = ` -"# Rules - -No blank line after this. - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). - -| Name | Description | πŸ”§ | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | - - -No blank line before this." -`; - -exports[`generator #generate with no blank lines around comment markers generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - - -No blank line before this." -`; - -exports[`generator #generate with one blank line around comment markers generates the documentation 1`] = ` -"# Rules - -One blank line after this. - - - -πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). - -| Name | Description | πŸ”§ | -| :----------------------------- | :--------------------- | :- | -| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | - - - -One blank line before this." -`; - -exports[`generator #generate with one blank line around comment markers generates the documentation 2`] = ` -"# Description of no-foo (\`test/no-foo\`) - -πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). - - - -One blank line before this." -`; - -exports[`generator #generate with one config that does not have emoji shows the default config emoji 1`] = ` -"## Rules - - -πŸ’Ό Configurations enabled in. - -| Name | Description | πŸ’Ό | -| :----------------------------- | :---------------------- | :---------------------- | -| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | ![configWithoutEmoji][] | - - -" -`; - -exports[`generator #generate with one config that does not have emoji shows the default config emoji 2`] = ` -"# Description for no-foo (\`test/no-foo\`) - -πŸ’Ό This rule is enabled in the \`configWithoutEmoji\` config. - - -" -`; diff --git a/test/lib/generate/__snapshots__/check-test.ts.snap b/test/lib/generate/__snapshots__/check-test.ts.snap new file mode 100644 index 00000000..2587d390 --- /dev/null +++ b/test/lib/generate/__snapshots__/check-test.ts.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--check) basic prints the issues, exits with failure, and does not write changes 1`] = ` +[ + "- Expected ++ Received + +- # Description for no-foo (\`test/no-foo\`) +- +-  +- ++ # test/no-foo", +] +`; + +exports[`generate (--check) basic prints the issues, exits with failure, and does not write changes 2`] = ` +[ + "- Expected ++ Received + + ## Rules +-  +- +- | Name | Description | +- | :----------------------------- | :---------------------- | +- | [no-foo](docs/rules/no-foo.md) | Description for no-foo. | +- +-  +", +] +`; + +exports[`generate (--check) basic prints the issues, exits with failure, and does not write changes 3`] = ` +"## Rules +" +`; + +exports[`generate (--check) basic prints the issues, exits with failure, and does not write changes 4`] = `"# test/no-foo"`; diff --git a/test/lib/__snapshots__/generate-cjs-test.ts.snap b/test/lib/generate/__snapshots__/cjs-test.ts.snap similarity index 100% rename from test/lib/__snapshots__/generate-cjs-test.ts.snap rename to test/lib/generate/__snapshots__/cjs-test.ts.snap diff --git a/test/lib/generate/__snapshots__/comment-markers-test.ts.snap b/test/lib/generate/__snapshots__/comment-markers-test.ts.snap new file mode 100644 index 00000000..f634deaf --- /dev/null +++ b/test/lib/generate/__snapshots__/comment-markers-test.ts.snap @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (comment markers) README missing rule list markers and no rules section throws an error 1`] = `"README.md is missing rules list markers: "`; + +exports[`generate (comment markers) README missing rule list markers but with rules section adds rule list markers to rule section 1`] = ` +"# eslint-plugin-test + +Foo. + +## Rules + + +| Name | Description | +| :----------------------------- | :--------------------- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | + + + +Old rules list. + +## Other + +Bar." +`; + +exports[`generate (comment markers) no existing comment markers - minimal doc content generates the documentation 1`] = ` +"## Rules + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). + +| Name | Description | πŸ”§ | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | + + +" +`; + +exports[`generate (comment markers) no existing comment markers - minimal doc content generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + +" +`; + +exports[`generate (comment markers) no existing comment markers - with no blank lines in existing content generates the documentation 1`] = ` +"## Rules + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). + +| Name | Description | πŸ”§ | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | + + +Existing rules section content." +`; + +exports[`generate (comment markers) no existing comment markers - with no blank lines in existing content generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + +Existing rule doc content." +`; + +exports[`generate (comment markers) no existing comment markers - with one blank line around existing content generates the documentation 1`] = ` +"## Rules + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). + +| Name | Description | πŸ”§ | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | + + + +Existing rules section content." +`; + +exports[`generate (comment markers) no existing comment markers - with one blank line around existing content generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + +Existing rule doc content." +`; + +exports[`generate (comment markers) rule doc without header marker but pre-existing header updates the documentation 1`] = ` +"# Description (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +Pre-existing notice about the rule being recommended. +## Rule details +Details." +`; + +exports[`generate (comment markers) with no blank lines around comment markers generates the documentation 1`] = ` +"# Rules + +No blank line after this. + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). + +| Name | Description | πŸ”§ | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | + + +No blank line before this." +`; + +exports[`generate (comment markers) with no blank lines around comment markers generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + +No blank line before this." +`; + +exports[`generate (comment markers) with one blank line around comment markers generates the documentation 1`] = ` +"# Rules + +One blank line after this. + + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). + +| Name | Description | πŸ”§ | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | πŸ”§ | + + + +One blank line before this." +`; + +exports[`generate (comment markers) with one blank line around comment markers generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + +One blank line before this." +`; diff --git a/test/lib/generate/__snapshots__/config-emoji-test.ts.snap b/test/lib/generate/__snapshots__/config-emoji-test.ts.snap new file mode 100644 index 00000000..f124433c --- /dev/null +++ b/test/lib/generate/__snapshots__/config-emoji-test.ts.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--config-emoji) basic shows the correct emojis 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +πŸ”₯ Set in the \`recommended\` configuration.\\ +🎨 Set in the \`stylistic\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :------------------------- | +| [no-bar](docs/rules/no-bar.md) | Description for no-bar. | πŸ”₯ 🎨 | +| [no-baz](docs/rules/no-baz.md) | Description for no-boz. | ![configWithoutEmoji][] 🎨 | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | πŸ”₯ | + + +" +`; + +exports[`generate (--config-emoji) basic shows the correct emojis 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the πŸ”₯ \`recommended\` config. + + +" +`; + +exports[`generate (--config-emoji) basic shows the correct emojis 3`] = ` +"# Description for no-bar (\`test/no-bar\`) + +πŸ’Ό This rule is enabled in the following configs: πŸ”₯ \`recommended\`, 🎨 \`stylistic\`. + + +" +`; + +exports[`generate (--config-emoji) basic shows the correct emojis 4`] = ` +"# Description for no-boz (\`test/no-baz\`) + +πŸ’Ό This rule is enabled in the following configs: \`configWithoutEmoji\`, 🎨 \`stylistic\`. + + +" +`; + +exports[`generate (--config-emoji) removing default emoji for a config reverts to using a badge for the config 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :--------------- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | ![recommended][] | + + +" +`; + +exports[`generate (--config-emoji) removing default emoji for a config reverts to using a badge for the config 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the \`recommended\` config. + + +" +`; + +exports[`generate (--config-emoji) with one config that does not have emoji shows the default config emoji 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :---------------------- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | ![configWithoutEmoji][] | + + +" +`; + +exports[`generate (--config-emoji) with one config that does not have emoji shows the default config emoji 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the \`configWithoutEmoji\` config. + + +" +`; diff --git a/test/lib/generate/__snapshots__/configs-test.ts.snap b/test/lib/generate/__snapshots__/configs-test.ts.snap new file mode 100644 index 00000000..95881821 --- /dev/null +++ b/test/lib/generate/__snapshots__/configs-test.ts.snap @@ -0,0 +1,266 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (configs) config with overrides generates the documentation 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | + + +" +`; + +exports[`generate (configs) config with overrides generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) only a \`recommended\` config updates the documentation 1`] = ` +" + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :----------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description. | βœ… | + +" +`; + +exports[`generate (configs) only a \`recommended\` config updates the documentation 2`] = ` +"# Description (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) rule config with options generates the documentation 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | + + +" +`; + +exports[`generate (configs) rule config with options generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +⚠️ Configurations set to warn in.\\ +🚫 Configurations disabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | ⚠️ | 🚫 | +| :----------------------------- | :--------------------- | :- | :----------- | :----------- | +| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | | | ![other][] βœ… | +| [no-baz](docs/rules/no-baz.md) | Description of no-baz. | βœ… | | ![other][] | +| [no-bez](docs/rules/no-bez.md) | Description of no-bez. | | ![other][] | | +| [no-biz](docs/rules/no-biz.md) | Description of no-biz. | | | ![other][] | +| [no-boz](docs/rules/no-boz.md) | Description of no-boz. | | βœ… | | +| [no-buz](docs/rules/no-buz.md) | Description of no-buz. | | ![other][] βœ… | | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | | | βœ… | + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +🚫 This rule is _disabled_ in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 3`] = ` +"# Description of no-bar (\`test/no-bar\`) + +🚫 This rule is _disabled_ in the following configs: \`other\`, βœ… \`recommended\`. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 4`] = ` +"# Description of no-baz (\`test/no-baz\`) + +πŸ’ΌπŸš« This rule is enabled in the βœ… \`recommended\` config. This rule is _disabled_ in the \`other\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 5`] = ` +"# Description of no-biz (\`test/no-biz\`) + +🚫 This rule is _disabled_ in the \`other\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 6`] = ` +"# Description of no-boz (\`test/no-boz\`) + +⚠️ This rule _warns_ in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 7`] = ` +"# Description of no-buz (\`test/no-buz\`) + +⚠️ This rule _warns_ in the following configs: \`other\`, βœ… \`recommended\`. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn generates the documentation 8`] = ` +"# Description of no-bez (\`test/no-bez\`) + +⚠️ This rule _warns_ in the \`other\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn, only one config present generates the documentation 1`] = ` +"## Rules + + +⚠️ Configurations set to warn in.\\ +🚫 Configurations disabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | ⚠️ | 🚫 | +| :----------------------------- | :--------------------- | :- | :- | +| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | | βœ… | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | | + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn, only one config present generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +⚠️ This rule _warns_ in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn, only one config present generates the documentation 3`] = ` +"# Description of no-bar (\`test/no-bar\`) + +🚫 This rule is _disabled_ in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn, two configs present generates the documentation 1`] = ` +"## Rules + + +⚠️ Configurations set to warn in.\\ +🚫 Configurations disabled in.\\ +βœ… Set in the \`recommended\` configuration.\\ +⌨️ Set in the \`typescript\` configuration. + +| Name | Description | ⚠️ | 🚫 | +| :----------------------------- | :--------------------- | :- | :- | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | βœ… | ⌨️ | + + +" +`; + +exports[`generate (configs) rules that are disabled or set to warn, two configs present generates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +⚠️🚫 This rule _warns_ in the βœ… \`recommended\` config. This rule is _disabled_ in the ⌨️ \`typescript\` config. + + +" +`; + +exports[`generate (configs) with --ignore-config hides the ignored config 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | + + +" +`; + +exports[`generate (configs) with --ignore-config hides the ignored config 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (configs) with config that does not have any rules uses recommended config emoji since it is the only relevant config 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | + + +" +`; + +exports[`generate (configs) with config that does not have any rules uses recommended config emoji since it is the only relevant config 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; diff --git a/test/lib/generate/__snapshots__/deprecated-rule-test.ts.snap b/test/lib/generate/__snapshots__/deprecated-rule-test.ts.snap new file mode 100644 index 00000000..085001ef --- /dev/null +++ b/test/lib/generate/__snapshots__/deprecated-rule-test.ts.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (deprecated rules) several deprecated rules updates the documentation 1`] = ` +" + +❌ Deprecated. + +| Name | Description | ❌ | +| :----------------------------- | :----------- | :- | +| [no-bar](docs/rules/no-bar.md) | Description. | ❌ | +| [no-baz](docs/rules/no-baz.md) | Description. | ❌ | +| [no-biz](docs/rules/no-biz.md) | Description. | | +| [no-foo](docs/rules/no-foo.md) | Description. | ❌ | + +" +`; + +exports[`generate (deprecated rules) several deprecated rules updates the documentation 2`] = ` +"# Description (\`test/no-foo\`) + +❌ This rule is deprecated. It was replaced by [\`no-bar\`](no-bar.md). + + +" +`; + +exports[`generate (deprecated rules) several deprecated rules updates the documentation 3`] = ` +"# Description (\`test/no-bar\`) + +❌ This rule is deprecated. + + +" +`; + +exports[`generate (deprecated rules) several deprecated rules updates the documentation 4`] = ` +"# Description (\`test/no-baz\`) + +❌ This rule is deprecated. + + +" +`; + +exports[`generate (deprecated rules) several deprecated rules updates the documentation 5`] = ` +"# Description (\`test/no-biz\`) + + +" +`; + +exports[`generate (deprecated rules) using prefix ahead of replacement rule name uses correct replacement rule link 1`] = ` +" + +❌ Deprecated. + +| Name | Description | ❌ | +| :----------------------------- | :----------- | :- | +| [no-bar](docs/rules/no-bar.md) | Description. | | +| [no-foo](docs/rules/no-foo.md) | Description. | ❌ | + +" +`; + +exports[`generate (deprecated rules) using prefix ahead of replacement rule name uses correct replacement rule link 2`] = ` +"# Description (\`test/no-foo\`) + +❌ This rule is deprecated. It was replaced by [\`no-bar\`](no-bar.md). + + +" +`; + +exports[`generate (deprecated rules) using prefix ahead of replacement rule name uses correct replacement rule link 3`] = ` +"# Description (\`test/no-bar\`) + + +" +`; + +exports[`generate (deprecated rules) with nested rule names has the correct links, especially replacement rule link 1`] = ` +" + +❌ Deprecated. + +| Name | Description | ❌ | +| :----------------------------------------------- | :----------- | :- | +| [category/no-bar](docs/rules/category/no-bar.md) | Description. | ❌ | +| [category/no-foo](docs/rules/category/no-foo.md) | Description. | ❌ | + +" +`; + +exports[`generate (deprecated rules) with nested rule names has the correct links, especially replacement rule link 2`] = ` +"# Description (\`test/category/no-foo\`) + +❌ This rule is deprecated. It was replaced by [\`category/no-bar\`](../category/no-bar.md). + + +" +`; + +exports[`generate (deprecated rules) with nested rule names has the correct links, especially replacement rule link 3`] = ` +"# Description (\`test/category/no-bar\`) + +❌ This rule is deprecated. It was replaced by [\`category/no-foo\`](../category/no-foo.md). + + +" +`; + +exports[`generate (deprecated rules) with no rule doc but --ignore-deprecated-rules omits the rule from the README and does not try to update its non-existent rule doc 1`] = ` +" + +| Name | +| :--- | + +" +`; diff --git a/test/lib/generate/__snapshots__/file-paths-test.ts.snap b/test/lib/generate/__snapshots__/file-paths-test.ts.snap new file mode 100644 index 00000000..c69b2aa2 --- /dev/null +++ b/test/lib/generate/__snapshots__/file-paths-test.ts.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (file paths) custom path to rule docs and rules list generates the documentation 1`] = ` +" + +| Name | +| :------------------------------- | +| [no-foo](rules/no-foo/no-foo.md) | + +" +`; + +exports[`generate (file paths) custom path to rule docs and rules list generates the documentation 2`] = ` +"# test/no-foo + + +" +`; + +exports[`generate (file paths) lowercase README file generates the documentation 1`] = ` +" + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + +" +`; diff --git a/test/lib/generate/__snapshots__/general-test.ts.snap b/test/lib/generate/__snapshots__/general-test.ts.snap new file mode 100644 index 00000000..ca2b191a --- /dev/null +++ b/test/lib/generate/__snapshots__/general-test.ts.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (general) basic updates the documentation 1`] = ` +"# eslint-plugin-test +Description. +## Rules + + +πŸ’Ό Configurations enabled in.\\ +🌐 Set in the \`all\` configuration.\\ +βœ… Set in the \`recommended\` configuration.\\ +🎨 Set in the \`style\` configuration.\\ +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\ +πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +| Name | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | +| :----------------------------- | :--------------------- | :---- | :- | :- | +| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | 🌐 🎨 | πŸ”§ | | +| [no-baz](docs/rules/no-baz.md) | Description of no-boz. | | | | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | 🌐 βœ… | πŸ”§ | πŸ’‘ | + + +more content." +`; + +exports[`generate (general) basic updates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the following configs: 🌐 \`all\`, βœ… \`recommended\`. + +πŸ”§πŸ’‘ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + + +## Rule details +details +## Options +optionToDoSomething1 - explanation +optionToDoSomething2 - explanation" +`; + +exports[`generate (general) basic updates the documentation 3`] = ` +"# Description of no-bar (\`test/no-bar\`) + +πŸ’Ό This rule is enabled in the following configs: 🌐 \`all\`, 🎨 \`style\`. + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + +## Rule details +details" +`; + +exports[`generate (general) basic updates the documentation 4`] = ` +"# Description of no-boz (\`test/no-baz\`) + + +## Rule details +details" +`; diff --git a/test/lib/generate/__snapshots__/package-json-test.ts.snap b/test/lib/generate/__snapshots__/package-json-test.ts.snap new file mode 100644 index 00000000..5350cf84 --- /dev/null +++ b/test/lib/generate/__snapshots__/package-json-test.ts.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (package.json) Missing plugin package.json \`name\` field throws an error 1`] = `"Could not find \`name\` field in ESLint plugin's package.json."`; + +exports[`generate (package.json) Missing plugin package.json throws an error 1`] = `"Could not find package.json of ESLint plugin."`; + +exports[`generate (package.json) No configs found omits the config column 1`] = ` +" + +| Name | Description | +| :----------------------------- | :------------ | +| [no-foo](docs/rules/no-foo.md) | disallow foo. | + +" +`; + +exports[`generate (package.json) No configs found omits the config column 2`] = ` +"# Disallow foo (\`test/no-foo\`) + + +" +`; + +exports[`generate (package.json) No exported rules object found throws an error 1`] = `"Could not find exported \`rules\` object in ESLint plugin."`; + +exports[`generate (package.json) Scoped plugin name determines the correct plugin prefix 1`] = ` +"# Disallow foo (\`@my-scope/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; + +exports[`generate (package.json) plugin entry point in JSON format generates the documentation 1`] = ` +" + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | Description | πŸ’Ό | +| :----------------------------- | :--------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo | βœ… | + +" +`; + +exports[`generate (package.json) plugin entry point in JSON format generates the documentation 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-description-test.ts.snap b/test/lib/generate/__snapshots__/rule-description-test.ts.snap new file mode 100644 index 00000000..5cdc88d1 --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-description-test.ts.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (rule descriptions) Rule description needs to be formatted capitalizes the first letter and removes the trailing period from the description 1`] = ` +"# Disallow foo (\`test/no-foo\`) + + +" +`; + +exports[`generate (rule descriptions) no rules with description generates the documentation 1`] = ` +"## Rules + + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + + +" +`; + +exports[`generate (rule descriptions) no rules with description generates the documentation 2`] = ` +"# test/no-foo + + +" +`; + +exports[`generate (rule descriptions) one rule missing description generates the documentation 1`] = ` +"## Rules + + +| Name | Description | +| :----------------------------- | :---------------------- | +| [no-bar](docs/rules/no-bar.md) | | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | + + +" +`; + +exports[`generate (rule descriptions) one rule missing description generates the documentation 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + + +" +`; + +exports[`generate (rule descriptions) one rule missing description generates the documentation 3`] = ` +"# test/no-bar + + +" +`; + +exports[`generate (rule descriptions) rule with long-enough description to require name column wrapping avoidance adds spaces to the name column 1`] = ` +"## Rules + + +| NameΒ Β  | Description | +| :----------------------------- | :---------------------------------------------------------------------------------- | +| [no-foo](docs/rules/no-foo.md) | over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars | + + +" +`; + +exports[`generate (rule descriptions) rule with long-enough description to require name column wrapping avoidance adds spaces to the name column 2`] = ` +"# Over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars (\`test/no-foo\`) + + +" +`; + +exports[`generate (rule descriptions) rule with long-enough description to require name column wrapping avoidance but rule name too short does not add spaces to name column 1`] = ` +"## Rules + + +| Name | Description | +| :----------------------- | :---------------------------------------------------------------------------------- | +| [foo](docs/rules/foo.md) | over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars | + + +" +`; + +exports[`generate (rule descriptions) rule with long-enough description to require name column wrapping avoidance but rule name too short does not add spaces to name column 2`] = ` +"# Over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars (\`test/foo\`) + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-doc-notices-test.ts.snap b/test/lib/generate/__snapshots__/rule-doc-notices-test.ts.snap new file mode 100644 index 00000000..c43d00cc --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-doc-notices-test.ts.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--rule-doc-notices) basic shows the right rule doc notices 1`] = ` +"## Rules + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\ +πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\\ +❌ Deprecated. + +| Name | Description | πŸ”§ | πŸ’‘ | ❌ | +| :----------------------------- | :---------------------- | :- | :- | :- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | πŸ”§ | πŸ’‘ | ❌ | + + +" +`; + +exports[`generate (--rule-doc-notices) basic shows the right rule doc notices 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +πŸ”§ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + +❌ This rule is deprecated. + +❗ This rule identifies problems that could cause errors or unexpected behavior. + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-doc-title-format-test.ts.snap b/test/lib/generate/__snapshots__/rule-doc-title-format-test.ts.snap new file mode 100644 index 00000000..0e84731c --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-doc-title-format-test.ts.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--rule-doc-title-format) desc uses the right rule doc title format 1`] = ` +"# Description for no-foo + + +" +`; + +exports[`generate (--rule-doc-title-format) desc uses the right rule doc title format 2`] = ` +"# test/no-bar + + +" +`; + +exports[`generate (--rule-doc-title-format) desc-parens-name uses the right rule doc title format 1`] = ` +"# Description for no-foo (\`no-foo\`) + + +" +`; + +exports[`generate (--rule-doc-title-format) desc-parens-name uses the right rule doc title format 2`] = ` +"# no-bar + + +" +`; + +exports[`generate (--rule-doc-title-format) desc-parens-prefix-name uses the right rule doc title format, with fallback when missing description 1`] = ` +"# Description for no-foo (\`test/no-foo\`) + + +" +`; + +exports[`generate (--rule-doc-title-format) desc-parens-prefix-name uses the right rule doc title format, with fallback when missing description 2`] = ` +"# test/no-bar + + +" +`; + +exports[`generate (--rule-doc-title-format) name uses the right rule doc title format 1`] = ` +"# no-foo + + +" +`; + +exports[`generate (--rule-doc-title-format) prefix-name uses the right rule doc title format 1`] = ` +"# test/no-foo + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-list-columns-test.ts.snap b/test/lib/generate/__snapshots__/rule-list-columns-test.ts.snap new file mode 100644 index 00000000..edd4c912 --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-list-columns-test.ts.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--rule-list-columns) basic shows the right columns and legend 1`] = ` +"## Rules + + +πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).\\ +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). + +| πŸ’‘ | πŸ”§ | Name | +| :- | :- | :----------------------------- | +| πŸ’‘ | πŸ”§ | [no-foo](docs/rules/no-foo.md) | + + +" +`; + +exports[`generate (--rule-list-columns) basic shows the right columns and legend 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +❌ This rule is deprecated. + +πŸ”§πŸ’‘ This rule is automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + + +" +`; + +exports[`generate (--rule-list-columns) consolidated fixableAndHasSuggestions column shows the right columns and legend 1`] = ` +"## Rules + + +πŸ”§ Automatically fixable by the [\`--fix\` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\ +πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +| Name | πŸ”§πŸ’‘ | +| :----------------------------- | :--- | +| [no-bar](docs/rules/no-bar.md) | πŸ”§ | +| [no-baz](docs/rules/no-baz.md) | | +| [no-foo](docs/rules/no-foo.md) | πŸ”§πŸ’‘ | + + +" +`; + +exports[`generate (--rule-list-columns) shows column and notice for requiresTypeChecking updates the documentation 1`] = ` +" + +πŸ’Ό Configurations enabled in.\\ +🌐 Set in the \`all\` configuration.\\ +πŸ’­ Requires type information. + +| Name | Description | πŸ’Ό | πŸ’­ | +| :----------------------------- | :--------------------- | :- | :- | +| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | | πŸ’­ | +| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | 🌐 | | + +" +`; + +exports[`generate (--rule-list-columns) shows column and notice for requiresTypeChecking updates the documentation 2`] = ` +"# Description of no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the 🌐 \`all\` config. + + +" +`; + +exports[`generate (--rule-list-columns) shows column and notice for requiresTypeChecking updates the documentation 3`] = ` +"# Description of no-bar (\`test/no-bar\`) + +πŸ’­ This rule requires type information. + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-list-split-by-test.ts.snap b/test/lib/generate/__snapshots__/rule-list-split-by-test.ts.snap new file mode 100644 index 00000000..49021532 --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-list-split-by-test.ts.snap @@ -0,0 +1,138 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--split-by) by nested property meta.docs.category splits the list 1`] = ` +"## Rules + + +| Name | +| :----------------------------- | +| [no-baz](docs/rules/no-baz.md) | + +### candy + +| Name | +| :----------------------------- | +| [no-bar](docs/rules/no-bar.md) | + +### fruits + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + + +" +`; + +exports[`generate (--split-by) by type splits the list 1`] = ` +"## Rules + + +| Name | +| :----------------------------- | +| [no-biz](docs/rules/no-biz.md) | + +### problem + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + +### suggestion + +| Name | +| :----------------------------- | +| [no-bar](docs/rules/no-bar.md) | +| [no-baz](docs/rules/no-baz.md) | + + +" +`; + +exports[`generate (--split-by) ignores case splits the list 1`] = ` +"## Rules + + +### a + +| Name | +| :----------------------------- | +| [no-bar](docs/rules/no-bar.md) | + +### B + +| Name | +| :----------------------------- | +| [no-baz](docs/rules/no-baz.md) | + +### c + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + + +" +`; + +exports[`generate (--split-by) with boolean splits the list 1`] = ` +"## Rules + + +πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +| Name | πŸ’‘ | +| :----------------------------- | :- | +| [no-bar](docs/rules/no-bar.md) | | +| [no-baz](docs/rules/no-baz.md) | | + +### Has Suggestions + +| Name | πŸ’‘ | +| :----------------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | πŸ’‘ | + + +" +`; + +exports[`generate (--split-by) with one sub-list having no rules enabled by the config splits the list and still uses recommended config emoji in both lists 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +### bar + +| Name | πŸ’Ό | +| :----------------------------- | :- | +| [no-bar](docs/rules/no-bar.md) | | + +### foo + +| Name | πŸ’Ό | +| :----------------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | βœ… | + + +" +`; + +exports[`generate (--split-by) with unknown variable type splits the list but does not attempt to convert variable name to title 1`] = ` +"## Rules + + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + +### foo_barBIZ-baz3bOz + +| Name | +| :----------------------------- | +| [no-bar](docs/rules/no-bar.md) | + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-metadata-test.ts.snap b/test/lib/generate/__snapshots__/rule-metadata-test.ts.snap new file mode 100644 index 00000000..852799b4 --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-metadata-test.ts.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (rule metadata) deprecated function-style rule generates the documentation 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | πŸ’Ό | +| :----------------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | βœ… | + + +" +`; + +exports[`generate (rule metadata) deprecated function-style rule generates the documentation 2`] = ` +"# test/no-foo + + +" +`; + +exports[`generate (rule metadata) rule with no meta object generates the documentation 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in.\\ +βœ… Set in the \`recommended\` configuration. + +| Name | πŸ’Ό | +| :----------------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | βœ… | + + +" +`; + +exports[`generate (rule metadata) rule with no meta object generates the documentation 2`] = ` +"# test/no-foo + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` config. + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-options-test.ts.snap b/test/lib/generate/__snapshots__/rule-options-test.ts.snap new file mode 100644 index 00000000..d1f7c127 --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-options-test.ts.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (rule options) rule with options, options column/notice enabled displays the column and notice 1`] = ` +"## Rules + + +βš™οΈ Has configuration options. + +| Name | βš™οΈ | +| :----------------------------- | :- | +| [no-bar](docs/rules/no-bar.md) | βš™οΈ | +| [no-baz](docs/rules/no-baz.md) | | +| [no-biz](docs/rules/no-biz.md) | | +| [no-foo](docs/rules/no-foo.md) | βš™οΈ | + + +" +`; + +exports[`generate (rule options) rule with options, options column/notice enabled displays the column and notice 2`] = ` +"# test/no-foo + +βš™οΈ This rule is configurable. + + +## Options +" +`; + +exports[`generate (rule options) rule with options, options column/notice enabled displays the column and notice 3`] = ` +"# test/no-bar + +βš™οΈ This rule is configurable. + + +## Options +" +`; + +exports[`generate (rule options) rule with options, options column/notice enabled displays the column and notice 4`] = ` +"# test/no-biz + + +" +`; + +exports[`generate (rule options) rule with options, options column/notice enabled displays the column and notice 5`] = ` +"# test/no-baz + + +" +`; diff --git a/test/lib/generate/__snapshots__/rule-type-test.ts.snap b/test/lib/generate/__snapshots__/rule-type-test.ts.snap new file mode 100644 index 00000000..c5f7e7bb --- /dev/null +++ b/test/lib/generate/__snapshots__/rule-type-test.ts.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (rule type) rule with type, type column enabled displays the type 1`] = ` +"## Rules + + +πŸ—‚οΈ The type of rule.\\ +❗ Identifies problems that could cause errors or unexpected behavior.\\ +πŸ“– Identifies potential improvements.\\ +πŸ“ Focuses on code formatting. + +| Name | πŸ—‚οΈ | +| :----------------------------- | :-- | +| [no-bar](docs/rules/no-bar.md) | πŸ“– | +| [no-biz](docs/rules/no-biz.md) | πŸ“ | +| [no-boz](docs/rules/no-boz.md) | | +| [no-buz](docs/rules/no-buz.md) | | +| [no-foo](docs/rules/no-foo.md) | ❗ | + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled displays the type 2`] = ` +"# test/no-foo + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled displays the type 3`] = ` +"# test/no-bar + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled displays the type 4`] = ` +"# test/no-biz + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled displays the type 5`] = ` +"# test/no-boz + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled displays the type 6`] = ` +"# test/no-buz + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled, but only an unknown type hides the type column and notice 1`] = ` +"## Rules + + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + + +" +`; + +exports[`generate (rule type) rule with type, type column enabled, but only an unknown type hides the type column and notice 2`] = ` +"# test/no-foo + + +" +`; + +exports[`generate (rule type) rule with type, type column not enabled hides the type column 1`] = ` +"## Rules + + +| Name | +| :----------------------------- | +| [no-foo](docs/rules/no-foo.md) | + + +" +`; + +exports[`generate (rule type) rule with type, type column not enabled hides the type column 2`] = ` +"# test/no-foo + + +" +`; diff --git a/test/lib/generate/__snapshots__/sorting-test.ts.snap b/test/lib/generate/__snapshots__/sorting-test.ts.snap new file mode 100644 index 00000000..4a4bc66a --- /dev/null +++ b/test/lib/generate/__snapshots__/sorting-test.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (sorting) sorting rules and configs case-insensitive sorts correctly 1`] = ` +"## Rules + + +πŸ’Ό Configurations enabled in. + +| Name | πŸ’Ό | +| :------------------- | :------------------- | +| [a](docs/rules/a.md) | ![a][] ![B][] ![c][] | +| [B](docs/rules/B.md) | | +| [c](docs/rules/c.md) | | + + +" +`; + +exports[`generate (sorting) sorting rules and configs case-insensitive sorts correctly 2`] = ` +"# test/a + +πŸ’Ό This rule is enabled in the following configs: \`a\`, \`B\`, \`c\`. + + +" +`; + +exports[`generate (sorting) sorting rules and configs case-insensitive sorts correctly 3`] = ` +"# test/B + + +" +`; + +exports[`generate (sorting) sorting rules and configs case-insensitive sorts correctly 4`] = ` +"# test/c + + +" +`; diff --git a/test/lib/generate/__snapshots__/url-configs-test.ts.snap b/test/lib/generate/__snapshots__/url-configs-test.ts.snap new file mode 100644 index 00000000..1b5f2b4e --- /dev/null +++ b/test/lib/generate/__snapshots__/url-configs-test.ts.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generate (--url-configs) basic includes the config link 1`] = ` +"## Rules + + +πŸ’Ό [Configurations](http://example.com/configs) enabled in.\\ +βœ… Set in the \`recommended\` [configuration](http://example.com/configs). + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :---------------- | +| [no-bar](docs/rules/no-bar.md) | Description for no-bar. | ![customConfig][] | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | + + +" +`; + +exports[`generate (--url-configs) basic includes the config link 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` [config](http://example.com/configs). + + +" +`; + +exports[`generate (--url-configs) basic includes the config link 3`] = ` +"# Description for no-bar (\`test/no-bar\`) + +πŸ’Ό This rule is enabled in the \`customConfig\` [config](http://example.com/configs). + + +" +`; + +exports[`generate (--url-configs) with only recommended config includes the config link 1`] = ` +"## Rules + + +πŸ’Ό [Configurations](http://example.com/configs) enabled in.\\ +βœ… Set in the \`recommended\` [configuration](http://example.com/configs). + +| Name | Description | πŸ’Ό | +| :----------------------------- | :---------------------- | :- | +| [no-foo](docs/rules/no-foo.md) | Description for no-foo. | βœ… | + + +" +`; + +exports[`generate (--url-configs) with only recommended config includes the config link 2`] = ` +"# Description for no-foo (\`test/no-foo\`) + +πŸ’Ό This rule is enabled in the βœ… \`recommended\` [config](http://example.com/configs). + + +" +`; diff --git a/test/lib/generate/check-test.ts b/test/lib/generate/check-test.ts new file mode 100644 index 00000000..aea76e62 --- /dev/null +++ b/test/lib/generate/check-test.ts @@ -0,0 +1,67 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; +import * as sinon from 'sinon'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--check)', function () { + describe('basic', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '# test/no-foo', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('prints the issues, exits with failure, and does not write changes', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.', { check: true }); + expect(consoleErrorStub.callCount).toBe(4); + // Use join to handle both Windows and Unix paths. + expect(consoleErrorStub.firstCall.args).toStrictEqual([ + `Please run eslint-doc-generator. A rule doc is out-of-date: ${join( + 'docs', + 'rules', + 'no-foo.md' + )}`, + ]); + expect(consoleErrorStub.secondCall.args).toMatchSnapshot(); // Diff + expect(consoleErrorStub.thirdCall.args).toStrictEqual([ + 'Please run eslint-doc-generator. README.md is out-of-date.', + ]); + expect(consoleErrorStub.getCall(3).args).toMatchSnapshot(); // Diff + consoleErrorStub.restore(); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate-cjs-test.ts b/test/lib/generate/cjs-test.ts similarity index 97% rename from test/lib/generate-cjs-test.ts rename to test/lib/generate/cjs-test.ts index fda69b6d..88eb1a2f 100644 --- a/test/lib/generate-cjs-test.ts +++ b/test/lib/generate/cjs-test.ts @@ -1,7 +1,7 @@ // This file uses actual test fixtures on the file system instead of mock-fs due to // trouble with the combination of require() for loading CJS plugins, jest, and mock-fs. -import { generate } from '../../lib/generator.js'; +import { generate } from '../../../lib/generator.js'; import { join } from 'node:path'; import { readFileSync } from 'node:fs'; diff --git a/test/lib/generate/comment-markers-test.ts b/test/lib/generate/comment-markers-test.ts new file mode 100644 index 00000000..5de2c9ed --- /dev/null +++ b/test/lib/generate/comment-markers-test.ts @@ -0,0 +1,401 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; +import { outdent } from 'outdent'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (comment markers)', function () { + describe('with one blank line around comment markers', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + type: 'module', + exports: 'index.js', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, + create(context) {} + }, + }, + };`, + + 'README.md': outdent` + # Rules + + One blank line after this. + + + + + + + One blank line before this. + `, + + 'docs/rules/no-foo.md': outdent` + + + One blank line before this. + `, + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with no blank lines around comment markers', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + type: 'module', + exports: 'index.js', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, + create(context) {} + }, + }, + };`, + + 'README.md': outdent` + # Rules + + No blank line after this. + + + No blank line before this. + `, + + 'docs/rules/no-foo.md': outdent` + + No blank line before this. + `, + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('no existing comment markers - with no blank lines in existing content', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + type: 'module', + exports: 'index.js', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, + create(context) {} + }, + }, + };`, + + 'README.md': outdent` + ## Rules + Existing rules section content. + `, + + 'docs/rules/no-foo.md': outdent` + # no-foo + Existing rule doc content. + `, + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('no existing comment markers - with one blank line around existing content', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + type: 'module', + exports: 'index.js', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, + create(context) {} + }, + }, + };`, + + 'README.md': outdent` + ## Rules + + Existing rules section content. + `, + + 'docs/rules/no-foo.md': outdent` + # no-foo + + Existing rule doc content. + `, + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('no existing comment markers - minimal doc content', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + type: 'module', + exports: 'index.js', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, + create(context) {} + }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('README missing rule list markers but with rules section', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {} + }, + }, + configs: {} + };`, + + 'README.md': outdent` + # eslint-plugin-test + + Foo. + + ## Rules + + Old rules list. + + ## Other + + Bar. + `, + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('adds rule list markers to rule section', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('README missing rule list markers and no rules section', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {} + }, + }, + };`, + + 'README.md': '# eslint-plugin-test', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('throws an error', async function () { + await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + + describe('rule doc without header marker but pre-existing header', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description.' }, }, + create(context) {} + }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'error', + } + } + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': outdent` + # Some pre-existing title. + Pre-existing notice about the rule being recommended. + ## Rule details + Details. + `, + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('updates the documentation', async function () { + await generate('.'); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/config-emoji-test.ts b/test/lib/generate/config-emoji-test.ts new file mode 100644 index 00000000..358eeb0b --- /dev/null +++ b/test/lib/generate/config-emoji-test.ts @@ -0,0 +1,352 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; +import { EMOJI_CONFIG_ERROR } from '../../../lib/emojis.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--config-emoji)', function () { + describe('basic', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + 'no-bar': { meta: { docs: { description: 'Description for no-bar.'} }, create(context) {} }, + 'no-baz': { meta: { docs: { description: 'Description for no-boz.'} }, create(context) {} }, + }, + configs: { + recommended: { + rules: { 'test/no-foo': 'error', 'test/no-bar': 'error' }, + }, + stylistic: { + rules: { 'test/no-bar': 'error', 'test/no-baz': 'error' }, + }, + configWithoutEmoji: { + rules: { 'test/no-baz': 'error' }, + } + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('shows the correct emojis', async function () { + await generate('.', { + configEmoji: ['recommended,πŸ”₯', 'stylistic,🎨'], + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('invalid format', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { configEmoji: ['foo,bar,baz'] }) + ).rejects.toThrow( + 'Invalid configEmoji option: foo,bar,baz. Expected format: config,emoji' + ); + }); + }); + + describe('trying to remove a default emoji that does not exist', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { configEmoji: ['config-without-default-emoji'] }) + ).rejects.toThrow( + 'Invalid configEmoji option: config-without-default-emoji. Expected format: config,emoji' + ); + }); + }); + + describe('removing default emoji for a config', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + recommended: { rules: { 'test/no-foo': 'error' } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('reverts to using a badge for the config', async function () { + await generate('.', { + configEmoji: ['recommended'], + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('non-existent config', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: {} + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { configEmoji: ['config-does-not-exist,πŸ”₯'] }) + ).rejects.toThrow( + 'Invalid configEmoji option: config-does-not-exist config not found.' + ); + }); + }); + + describe('using a reserved emoji', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + recommended: { rules: { 'test/no-foo': 'error' } }, + style: { rules: { 'test/no-foo': 'error' } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { configEmoji: [`recommended,${EMOJI_CONFIG_ERROR}`] }) + ).rejects.toThrow(`Cannot specify reserved emoji ${EMOJI_CONFIG_ERROR}.`); + }); + }); + + describe('duplicate config name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + recommended: { rules: { 'test/no-foo': 'error' } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { configEmoji: ['recommended,πŸ”₯', 'recommended,πŸ˜‹'] }) + ).rejects.toThrow( + 'Duplicate config name in configEmoji options: recommended' + ); + }); + }); + + describe('with one config that does not have emoji', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + configWithoutEmoji: { rules: { 'test/no-foo': 'error' } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('shows the default config emoji', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/configs-test.ts b/test/lib/generate/configs-test.ts new file mode 100644 index 00000000..bfaceea3 --- /dev/null +++ b/test/lib/generate/configs-test.ts @@ -0,0 +1,451 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (configs)', function () { + describe('config with overrides', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {} + }, + }, + configs: { + recommended: { + overrides: [{ + files: ['**/foo.js'], + rules: { + 'test/no-foo': 'error', + } + }] + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rule config with options', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {}, + schema: [{ /* some options */ }] + }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': ['error', { /* some options */ }], + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rules that are disabled or set to warn', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {}, + }, + 'no-bar': { + meta: { docs: { description: 'Description of no-bar.' }, }, + create(context) {}, + }, + 'no-baz': { + meta: { docs: { description: 'Description of no-baz.' }, }, + create(context) {}, + }, + 'no-biz': { + meta: { docs: { description: 'Description of no-biz.' }, }, + create(context) {}, + }, + 'no-boz': { + meta: { docs: { description: 'Description of no-boz.' }, }, + create(context) {}, + }, + 'no-buz': { + meta: { docs: { description: 'Description of no-buz.' }, }, + create(context) {}, + }, + 'no-bez': { + meta: { docs: { description: 'Description of no-bez.' }, }, + create(context) {}, + }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'off', + 'test/no-bar': 0, + 'test/no-baz': 'error', + 'test/no-boz': 'warn', + 'test/no-buz': 1, + } + }, + other: { + rules: { + 'test/no-bar': 0, + 'test/no-baz': 'off', + 'test/no-biz': 'off', + 'test/no-buz': 'warn', + 'test/no-bez': 'warn', + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + 'docs/rules/no-biz.md': '', + 'docs/rules/no-boz.md': '', + 'docs/rules/no-buz.md': '', + 'docs/rules/no-bez.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-boz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-buz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bez.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rules that are disabled or set to warn, only one config present', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {}, + }, + 'no-bar': { + meta: { docs: { description: 'Description of no-bar.' }, }, + create(context) {}, + }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 1, + 'test/no-bar': 0, + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rules that are disabled or set to warn, two configs present', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {}, + }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 1, + } + }, + typescript: { + rules: { + 'test/no-foo': 0, + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with config that does not have any rules', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + recommended: { rules: { 'test/no-foo': 'error' } }, + configWithoutRules: { rules: { } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses recommended config emoji since it is the only relevant config', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('only a `recommended` config', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description.' }, }, + create(context) {} + }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'error', + } + } + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('updates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with --ignore-config', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + recommended: { + rules: { 'test/no-foo': 'error' }, + }, + configToIgnore: { + rules: { 'test/no-foo': 'error' }, + } + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('hides the ignored config', async function () { + await generate('.', { + ignoreConfig: ['configToIgnore'], + configEmoji: ['configToIgnore,πŸ˜‹'], // Ensure this config has an emoji that would normally display in the legend. + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/deprecated-rule-test.ts b/test/lib/generate/deprecated-rule-test.ts new file mode 100644 index 00000000..fe53fd5e --- /dev/null +++ b/test/lib/generate/deprecated-rule-test.ts @@ -0,0 +1,244 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (deprecated rules)', function () { + describe('several deprecated rules', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description.' }, + deprecated: true, + replacedBy: ['no-bar'], + }, + create(context) {} + }, + 'no-bar': { + meta: { + docs: { description: 'Description.' }, + deprecated: true, // No replacement specified. + }, + create(context) {} + }, + 'no-baz': { + meta: { + docs: { description: 'Description.' }, + deprecated: true, + replacedBy: [], // Empty array. + }, + create(context) {} + }, + 'no-biz': { + // One rule that isn't deprecated. + meta: { + docs: { description: 'Description.' }, + }, + create(context) {} + }, + }, + configs: {} + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + 'docs/rules/no-biz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('updates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with nested rule names', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'category/no-foo': { + meta: { + docs: { description: 'Description.' }, + deprecated: true, + replacedBy: ['category/no-bar'], // without plugin prefix + }, + create(context) {} + }, + 'category/no-bar': { + meta: { + docs: { description: 'Description.' }, + deprecated: true, + replacedBy: ['test/category/no-foo'], // with plugin prefix + }, + create(context) {} + }, + }, + configs: {} + };`, + + 'README.md': + '', + + 'docs/rules/category/no-foo.md': '', + 'docs/rules/category/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('has the correct links, especially replacement rule link', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect( + readFileSync('docs/rules/category/no-foo.md', 'utf8') + ).toMatchSnapshot(); + expect( + readFileSync('docs/rules/category/no-bar.md', 'utf8') + ).toMatchSnapshot(); + }); + }); + + describe('using prefix ahead of replacement rule name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description.' }, + deprecated: true, + replacedBy: ['test/no-bar'], + }, + create(context) {} + }, + 'no-bar': { + meta: { docs: { description: 'Description.' }, }, + create(context) {} + }, + }, + configs: {} + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses correct replacement rule link', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with no rule doc but --ignore-deprecated-rules', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { deprecated: true, }, + create(context) {} + }, + }, + configs: {} + };`, + + 'README.md': + '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('omits the rule from the README and does not try to update its non-existent rule doc', async function () { + await generate('.', { ignoreDeprecatedRules: true }); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/file-paths-test.ts b/test/lib/generate/file-paths-test.ts new file mode 100644 index 00000000..47931d86 --- /dev/null +++ b/test/lib/generate/file-paths-test.ts @@ -0,0 +1,166 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (file paths)', function () { + describe('missing rule doc', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { }, + create(context) {} + }, + }, + };`, + + 'README.md': + '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + // Use join to handle both Windows and Unix paths. + await expect(generate('.')).rejects.toThrow( + `Could not find rule doc: ${join('docs', 'rules', 'no-foo.md')}` + ); + }); + }); + + describe('missing README', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { }, + create(context) {} + }, + }, + };`, + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect(generate('.')).rejects.toThrow( + 'Could not find README.md in ESLint plugin.' + ); + }); + }); + + describe('lowercase README file', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { }, create(context) {} }, + }, + };`, + + 'readme.md': + '', + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('readme.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('custom path to rule docs and rules list', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { }, create(context) {} }, + }, + };`, + + 'rules/list.md': + '', + 'rules/no-foo/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.', { + pathRuleDoc: 'rules/{name}/{name}.md', + pathRuleList: 'rules/list.md', + }); + expect(readFileSync('rules/list.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('rules/no-foo/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/general-test.ts b/test/lib/generate/general-test.ts new file mode 100644 index 00000000..2c8d6ea3 --- /dev/null +++ b/test/lib/generate/general-test.ts @@ -0,0 +1,157 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { outdent } from 'outdent'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +// Create a mock-fs workspace filesystem for temporary usage in this test because changes will be written to some files. + +describe('generate (general)', function () { + describe('basic', function () { + beforeEach(function () { + mockFs({ + // package.json + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + // entry point + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description of no-foo.' }, + fixable: 'code', + hasSuggestions: true, + schema: [ + { + type: 'object', + properties: { + optionToDoSomething1: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + { + type: 'array', + minItems: 1, + maxItems: 1, + items: [ + { + type: 'object', + properties: { + optionToDoSomething2: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ], + }, + { + type: 'array', + }, + ] + }, + create(context) {} + }, + 'no-bar': { + meta: { + docs: { description: 'Description of no-bar.' }, + fixable: 'code', + schema: [], + }, + create(context) {}, + }, + 'no-baz': { + meta: { docs: { description: 'Description of no-boz.' }, }, + create(context) {} + }, + }, + configs: { + all: { + rules: { + 'test/no-foo': 'error', + 'test/no-bar': 'error', + // test/no-baz rule intentionally not in any config. + } + }, + recommended: { + rules: { + 'test/no-foo': 'error', + } + }, + style: { + rules: { + 'test/no-bar': 'error', + } + } + } + };`, + + // README.md + 'README.md': outdent` + # eslint-plugin-test + Description. + ## Rules + + ... + + more content. + `, + + // RULE DOC FILES + + 'docs/rules/no-foo.md': outdent` + # title (rule-name) + description + + ## Rule details + details + ## Options + optionToDoSomething1 - explanation + optionToDoSomething2 - explanation + `, // rule doc with incorrect header content + 'docs/rules/no-bar.md': outdent` + + ## Rule details + details + `, // marker but no header content + 'docs/rules/no-baz.md': outdent` + ## Rule details + details + `, // content but missing marker + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('updates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/package-json-test.ts b/test/lib/generate/package-json-test.ts new file mode 100644 index 00000000..4a700a38 --- /dev/null +++ b/test/lib/generate/package-json-test.ts @@ -0,0 +1,345 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (package.json)', function () { + describe('Missing plugin package.json', function () { + beforeEach(function () { + mockFs({ + 'index.js': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('throws an error', async function () { + await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + + describe('Missing plugin package.json `name` field', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + exports: 'index.js', + type: 'module', + }), + + 'index.js': 'export default { rules: {} }', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('throws an error', async function () { + await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + + describe('Scoped plugin name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: '@my-scope/eslint-plugin', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'disallow foo.' }, }, + create(context) {} + }, + }, + configs: { + 'recommended': { rules: { '@my-scope/no-foo': 'error', } } + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('determines the correct plugin prefix', async function () { + await generate('.'); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('No configs found', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'disallow foo.' }, }, + create(context) {} + }, + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('omits the config column', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('No exported rules object found', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': 'export default {};', + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('throws an error', async function () { + await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); + }); + }); + + describe('package.json using exports, as string', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: './index.js', + type: 'module', + }), + + 'index.js': `export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'disallow foo.' }, }, + create(context) {} + }, + }, + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('finds the entry point', async function () { + await expect(generate('.')).resolves.toBeUndefined(); + }); + }); + + describe('package.json using exports, object with dot', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: { '.': './index-foo.js' }, + type: 'module', + }), + + 'index-foo.js': `export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'disallow foo.' }, }, + create(context) {} + }, + }, + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('finds the entry point', async function () { + await expect(generate('.')).resolves.toBeUndefined(); + }); + }); + + describe('plugin entry point in JSON format', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: './index.json', + type: 'module', + }), + + 'index.json': ` + { + "rules": { + "no-foo": { + "meta": { + "docs": { + "description": "Description for no-foo" + } + } + } + }, + "configs": { + "recommended": { + "rules": { + "test/no-foo": "error" + } + } + } + } + `, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('generates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('plugin entry point specified but does not exist', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'README.md': + '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect(generate('.')).rejects.toThrow( + 'ESLint plugin entry point does not exist. Tried: index.js' + ); + }); + }); + + describe('passing absolute path for plugin root', function () { + beforeEach(function () { + mockFs({ + '/eslint-plugin-test/package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + '/eslint-plugin-test/index.js': + 'export default { rules: {}, configs: {} };', + + '/eslint-plugin-test/README.md': + '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('finds the entry point', async function () { + await expect(generate('/eslint-plugin-test/')).resolves.toBeUndefined(); + }); + }); +}); diff --git a/test/lib/generate/rule-description-test.ts b/test/lib/generate/rule-description-test.ts new file mode 100644 index 00000000..8ad2c0e0 --- /dev/null +++ b/test/lib/generate/rule-description-test.ts @@ -0,0 +1,211 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (rule descriptions)', function () { + describe('rule with long-enough description to require name column wrapping avoidance', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('adds spaces to the name column', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rule with long-enough description to require name column wrapping avoidance but rule name too short', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'foo': { meta: { docs: { description: 'over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('does not add spaces to name column', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('Rule description needs to be formatted', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'disallow foo.' }, }, + create(context) {} + }, + }, + configs: {} + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('capitalizes the first letter and removes the trailing period from the description', async function () { + await generate('.'); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('no rules with description', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { }, + create(context) {}, + }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('one rule missing description', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description for no-foo.'} }, + create(context) {}, + }, + 'no-bar': { + meta: { }, + create(context) {}, + }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/rule-doc-notices-test.ts b/test/lib/generate/rule-doc-notices-test.ts new file mode 100644 index 00000000..e2fef4b7 --- /dev/null +++ b/test/lib/generate/rule-doc-notices-test.ts @@ -0,0 +1,134 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--rule-doc-notices)', function () { + describe('basic', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description for no-foo.' }, + hasSuggestions: true, + fixable: 'code', + deprecated: true, + type: 'problem' + }, + create(context) {} + }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('shows the right rule doc notices', async function () { + await generate('.', { + ruleDocNotices: 'hasSuggestions,fixable,deprecated,type', // Random values including an optional notice. + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('non-existent notice', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { ruleDocNotices: 'fixable,non-existent' }) + ).rejects.toThrow('Invalid ruleDocNotices option: non-existent'); + }); + }); + + describe('duplicate notice', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { ruleDocNotices: 'fixable,fixable' }) + ).rejects.toThrow('Duplicate value detected in ruleDocNotices option.'); + }); + }); +}); diff --git a/test/lib/generate/rule-doc-section-test.ts b/test/lib/generate/rule-doc-section-test.ts new file mode 100644 index 00000000..020cea8b --- /dev/null +++ b/test/lib/generate/rule-doc-section-test.ts @@ -0,0 +1,99 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { jest } from '@jest/globals'; +import * as sinon from 'sinon'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (rule doc sections)', function () { + describe('with `--rule-doc-section-include` and `--rule-doc-section-exclude` and no problems', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '## Examples\n', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('has no issues', async function () { + await expect( + generate('.', { + ruleDocSectionInclude: ['Examples'], + ruleDocSectionExclude: ['Unwanted Section'], + }) + ).resolves.toBeUndefined(); + }); + }); + + describe('with `--rule-doc-section-include` and `--rule-doc-section-exclude` and problems', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '## Unwanted Section\n', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('prints errors', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.', { + ruleDocSectionInclude: ['Examples'], + ruleDocSectionExclude: ['Unwanted Section'], + }); + expect(consoleErrorStub.callCount).toBe(2); + expect(consoleErrorStub.firstCall.args).toStrictEqual([ + '`no-foo` rule doc should have included the header: Examples', + ]); + expect(consoleErrorStub.secondCall.args).toStrictEqual([ + '`no-foo` rule doc should not have included the header: Unwanted Section', + ]); + consoleErrorStub.restore(); + }); + }); +}); diff --git a/test/lib/generate/rule-doc-title-format-test.ts b/test/lib/generate/rule-doc-title-format-test.ts new file mode 100644 index 00000000..354f2cdb --- /dev/null +++ b/test/lib/generate/rule-doc-title-format-test.ts @@ -0,0 +1,211 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--rule-doc-title-format)', function () { + describe('desc-parens-prefix-name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + 'no-bar': { meta: { docs: {} }, create(context) {} }, // No description. + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses the right rule doc title format, with fallback when missing description', async function () { + await generate('.', { + ruleDocTitleFormat: 'desc-parens-prefix-name', + }); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('desc-parens-name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + 'no-bar': { meta: { docs: { /* one rule without description */ } }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses the right rule doc title format', async function () { + await generate('.', { + ruleDocTitleFormat: 'desc-parens-name', + }); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('desc', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + 'no-bar': { meta: { docs: { /* one rule without description */ } }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses the right rule doc title format', async function () { + await generate('.', { + ruleDocTitleFormat: 'desc', + }); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('prefix-name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses the right rule doc title format', async function () { + await generate('.', { + ruleDocTitleFormat: 'prefix-name', + }); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('name', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('uses the right rule doc title format', async function () { + await generate('.', { + ruleDocTitleFormat: 'name', + }); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/rule-list-columns-test.ts b/test/lib/generate/rule-list-columns-test.ts new file mode 100644 index 00000000..7c7c07d7 --- /dev/null +++ b/test/lib/generate/rule-list-columns-test.ts @@ -0,0 +1,248 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--rule-list-columns)', function () { + describe('basic', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description for no-foo.' }, + hasSuggestions: true, + fixable: 'code', + deprecated: true, + }, + create(context) {} + }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('shows the right columns and legend', async function () { + await generate('.', { + ruleListColumns: 'hasSuggestions,fixable,name', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('consolidated fixableAndHasSuggestions column', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description for no-foo.' }, + hasSuggestions: true, + fixable: 'code', + }, + create(context) {} + }, + 'no-bar': { + meta: { + docs: { description: 'Description for no-bar.' }, + fixable: 'code', + }, + create(context) {} + }, + 'no-baz': { + meta: { + docs: { description: 'Description for no-baz.' }, + }, + create(context) {} + }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('shows the right columns and legend', async function () { + await generate('.', { + ruleListColumns: 'name,fixableAndHasSuggestions', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('non-existent column', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { ruleListColumns: 'name,non-existent' }) + ).rejects.toThrow('Invalid ruleListColumns option: non-existent'); + }); + }); + + describe('duplicate column', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { ruleListColumns: 'name,name' }) + ).rejects.toThrow('Duplicate value detected in ruleListColumns option.'); + }); + }); + + describe('shows column and notice for requiresTypeChecking', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {} + }, + 'no-bar': { + meta: { docs: { description: 'Description of no-bar.', requiresTypeChecking: true }, }, + create(context) {} + }, + }, + configs: { + all: { + rules: { + 'test/no-foo': 'error', + } + }, + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('updates the documentation', async function () { + await generate('.'); + + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/rule-list-split-by-test.ts b/test/lib/generate/rule-list-split-by-test.ts new file mode 100644 index 00000000..c670ec83 --- /dev/null +++ b/test/lib/generate/rule-list-split-by-test.ts @@ -0,0 +1,302 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--split-by)', function () { + describe('by type', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { type: 'problem' }, create(context) {} }, + 'no-bar': { meta: { type: 'suggestion' }, create(context) {} }, + 'no-baz': { meta: { type: 'suggestion' }, create(context) {} }, + 'no-biz': { meta: { /* no type */ }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + 'docs/rules/no-biz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('splits the list', async function () { + await generate('.', { + splitBy: 'meta.type', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('by nested property meta.docs.category', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { category: 'fruits' } }, create(context) {} }, + 'no-bar': { meta: { docs: { category: 'candy' } }, create(context) {} }, + 'no-baz': { meta: { /* no nested object */ }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('splits the list', async function () { + await generate('.', { splitBy: 'meta.docs.category' }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('by property that no rules have', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('throws an error', async function () { + await expect( + generate('.', { splitBy: 'non-existent-property' }) + ).rejects.toThrow( + 'No rules found with --split-by property "non-existent-property".' + ); + }); + }); + + describe('with boolean', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { hasSuggestions: true }, create(context) {} }, + 'no-bar': { meta: { }, create(context) {} }, + 'no-baz': { meta: { hasSuggestions: false }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('splits the list', async function () { + await generate('.', { + splitBy: 'meta.hasSuggestions', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('ignores case', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { foo: 'c' }, create(context) {} }, + 'no-bar': { meta: { foo: 'a' }, create(context) {} }, + 'no-baz': { meta: { foo: 'B' }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-baz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('splits the list', async function () { + await generate('.', { + splitBy: 'meta.foo', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with unknown variable type', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { 'foo_barBIZ-baz3bOz': false, meta: { }, create(context) {} }, + 'no-bar': { 'foo_barBIZ-baz3bOz': true, meta: { }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('splits the list but does not attempt to convert variable name to title', async function () { + await generate('.', { + splitBy: 'foo_barBIZ-baz3bOz', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with one sub-list having no rules enabled by the config', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { 'type': 'foo', meta: { }, create(context) {} }, + 'no-bar': { 'type': 'bar', meta: { }, create(context) {} }, + }, + configs: { + recommended: { rules: { 'test/no-foo': 'error' } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('splits the list and still uses recommended config emoji in both lists', async function () { + await generate('.', { + splitBy: 'type', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/rule-metadata-test.ts b/test/lib/generate/rule-metadata-test.ts new file mode 100644 index 00000000..c306b14c --- /dev/null +++ b/test/lib/generate/rule-metadata-test.ts @@ -0,0 +1,100 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (rule metadata)', function () { + describe('deprecated function-style rule', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': function create () {} + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'error', + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rule with no meta object', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { create(context) {} }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'error', + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('generates the documentation', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/rule-options-test.ts b/test/lib/generate/rule-options-test.ts new file mode 100644 index 00000000..1ef2265a --- /dev/null +++ b/test/lib/generate/rule-options-test.ts @@ -0,0 +1,370 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; +import * as sinon from 'sinon'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (rule options)', function () { + describe('Rule doc has options section but rule has no options', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { docs: { description: 'Description of no-foo.' }, }, + create(context) {} + }, + }, + configs: { + all: { + rules: { + 'test/no-foo': 'error', + } + } + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '## Options\n', // empty + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('prints an error', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.'); + expect(consoleErrorStub.callCount).toBe(1); + expect(consoleErrorStub.firstCall.args).toStrictEqual([ + '`no-foo` rule doc should not have included any of these headers: Options, Config', + ]); + consoleErrorStub.restore(); + }); + }); + + describe('Rule doc missing options section', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description of no-foo.', }, + schema: [{ type: 'object', },] + }, + create(context) {}, + }, + }, + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', // empty + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('prints an error', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.'); + expect(consoleErrorStub.callCount).toBe(1); + expect(consoleErrorStub.firstCall.args).toStrictEqual([ + '`no-foo` rule doc should have included one of these headers: Options, Config', + ]); + consoleErrorStub.restore(); + }); + }); + + describe('Rule doc missing options section with --rule-doc-section-options=true', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description of no-foo.', }, + schema: [{ type: 'object', },] + }, + create(context) {}, + }, + }, + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', // empty + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('prints an error', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.', { ruleDocSectionOptions: true }); + expect(consoleErrorStub.callCount).toBe(1); + expect(consoleErrorStub.firstCall.args).toStrictEqual([ + '`no-foo` rule doc should have included one of these headers: Options, Config', + ]); + consoleErrorStub.restore(); + }); + }); + + describe('Rule doc missing options section with --rule-doc-section-options=false', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description of no-foo.', }, + schema: [{ type: 'object', },] + }, + create(context) {}, + }, + }, + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '', // empty + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('has no error', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.', { ruleDocSectionOptions: false }); + expect(consoleErrorStub.callCount).toBe(0); + consoleErrorStub.restore(); + }); + }); + + describe('Rule has options with quotes', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description of no-foo.', }, + schema: [ + { + type: 'object', + properties: { + 'input[type="foo"]': { + type: 'boolean', + default: false, + }, + "input[type='bar']": { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ] + }, + create(context) {}, + }, + }, + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': + '## Options\n input[type=\\"foo\\"] \n input[type=\\\'bar\\\']', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('successfully finds the options mentioned in the rule doc despite quote escaping', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.'); + expect(consoleErrorStub.callCount).toBe(0); + consoleErrorStub.restore(); + }); + }); + + describe('Rule doc does not mention an option', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { + meta: { + docs: { description: 'Description of no-foo.' }, + schema: [ + { + type: 'object', + properties: { + optionToDoSomething: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ] + }, + create(context) {} + }, + }, + configs: { + all: { + rules: { + 'test/no-foo': 'error', + } + } + } + };`, + + 'README.md': + '', + + 'docs/rules/no-foo.md': '## Options\n', // empty + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + it('prints an error', async function () { + const consoleErrorStub = sinon.stub(console, 'error'); + await generate('.'); + expect(consoleErrorStub.callCount).toBe(1); + expect(consoleErrorStub.firstCall.args).toStrictEqual([ + '`no-foo` rule doc should have included: optionToDoSomething', + ]); + consoleErrorStub.restore(); + }); + }); + + describe('rule with options, options column/notice enabled', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { schema: [{foo:true}] }, create(context) {} }, + 'no-bar': { meta: { schema: {foo:true} }, create(context) {} }, + 'no-biz': { meta: { schema: [] }, create(context) {} }, + 'no-baz': { meta: { }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '## Options\n', + 'docs/rules/no-bar.md': '## Options\n', + 'docs/rules/no-biz.md': '', + 'docs/rules/no-baz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('displays the column and notice', async function () { + await generate('.', { + ruleListColumns: 'name,options', + ruleDocNotices: 'options', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/rule-type-test.ts b/test/lib/generate/rule-type-test.ts new file mode 100644 index 00000000..dec5a305 --- /dev/null +++ b/test/lib/generate/rule-type-test.ts @@ -0,0 +1,135 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (rule type)', function () { + describe('rule with type, type column not enabled', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { type: 'problem' }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('hides the type column', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rule with type, type column enabled', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { type: 'problem' }, create(context) {} }, + 'no-bar': { meta: { type: 'suggestion' }, create(context) {} }, + 'no-biz': { meta: { type: 'layout' }, create(context) {} }, + 'no-boz': { meta: { type: 'unknown' }, create(context) {} }, + 'no-buz': { meta: { /* no type*/ }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + 'docs/rules/no-biz.md': '', + 'docs/rules/no-boz.md': '', + 'docs/rules/no-buz.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('displays the type', async function () { + await generate('.', { ruleListColumns: 'name,type' }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-boz.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-buz.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('rule with type, type column enabled, but only an unknown type', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { type: 'unknown' }, create(context) {} }, + }, + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('hides the type column and notice', async function () { + await generate('.', { ruleListColumns: 'name,type' }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/sorting-test.ts b/test/lib/generate/sorting-test.ts new file mode 100644 index 00000000..3c9614a3 --- /dev/null +++ b/test/lib/generate/sorting-test.ts @@ -0,0 +1,60 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (sorting)', function () { + describe('sorting rules and configs case-insensitive', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'c': { meta: { docs: {} }, create(context) {} }, + 'a': { meta: { docs: {} }, create(context) {} }, + 'B': { meta: { docs: {} }, create(context) {} }, + }, + configs: { + 'c': { rules: { 'test/a': 'error', } }, + 'a': { rules: { 'test/a': 'error', } }, + 'B': { rules: { 'test/a': 'error', } }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/a.md': '', + 'docs/rules/B.md': '', + 'docs/rules/c.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('sorts correctly', async function () { + await generate('.'); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/a.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/B.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/c.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generate/url-configs-test.ts b/test/lib/generate/url-configs-test.ts new file mode 100644 index 00000000..84b083cc --- /dev/null +++ b/test/lib/generate/url-configs-test.ts @@ -0,0 +1,112 @@ +import { generate } from '../../../lib/generator.js'; +import mockFs from 'mock-fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { jest } from '@jest/globals'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const PATH_NODE_MODULES = resolve(__dirname, '..', '..', '..', 'node_modules'); + +describe('generate (--url-configs)', function () { + describe('basic', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + 'no-bar': { meta: { docs: { description: 'Description for no-bar.'} }, create(context) {} }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'error', + } + }, + customConfig: { + rules: { + 'test/no-bar': 'error', + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + 'docs/rules/no-bar.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('includes the config link', async function () { + await generate('.', { + urlConfigs: 'http://example.com/configs', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); + }); + }); + + describe('with only recommended config', function () { + beforeEach(function () { + mockFs({ + 'package.json': JSON.stringify({ + name: 'eslint-plugin-test', + exports: 'index.js', + type: 'module', + }), + + 'index.js': ` + export default { + rules: { + 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, + }, + configs: { + recommended: { + rules: { + 'test/no-foo': 'error', + } + }, + } + };`, + + 'README.md': '## Rules\n', + + 'docs/rules/no-foo.md': '', + + // Needed for some of the test infrastructure to work. + node_modules: mockFs.load(PATH_NODE_MODULES), + }); + }); + + afterEach(function () { + mockFs.restore(); + jest.resetModules(); + }); + + it('includes the config link', async function () { + await generate('.', { + urlConfigs: 'http://example.com/configs', + }); + expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); + expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); + }); + }); +}); diff --git a/test/lib/generator-test.ts b/test/lib/generator-test.ts deleted file mode 100644 index 1dab862d..00000000 --- a/test/lib/generator-test.ts +++ /dev/null @@ -1,4118 +0,0 @@ -import { generate } from '../../lib/generator.js'; -import mockFs from 'mock-fs'; -import { outdent } from 'outdent'; -import { dirname, join, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { readFileSync } from 'node:fs'; -import { jest } from '@jest/globals'; -import * as sinon from 'sinon'; -import { EMOJI_CONFIG_ERROR } from '../../lib/emojis.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -// Create a mock-fs workspace filesystem for temporary usage in this test because changes will be written to some files. - -describe('generator', function () { - describe('#generate', function () { - describe('successful', function () { - beforeEach(function () { - mockFs({ - // package.json - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - // entry point - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description of no-foo.' }, - fixable: 'code', - hasSuggestions: true, - schema: [ - { - type: 'object', - properties: { - optionToDoSomething1: { - type: 'boolean', - default: false, - }, - }, - additionalProperties: false, - }, - { - type: 'array', - minItems: 1, - maxItems: 1, - items: [ - { - type: 'object', - properties: { - optionToDoSomething2: { - type: 'boolean', - default: false, - }, - }, - additionalProperties: false, - }, - ], - }, - { - type: 'array', - }, - ] - }, - create(context) {} - }, - 'no-bar': { - meta: { - docs: { description: 'Description of no-bar.' }, - fixable: 'code', - schema: [], - }, - create(context) {}, - }, - 'no-baz': { - meta: { docs: { description: 'Description of no-boz.' }, }, - create(context) {} - }, - }, - configs: { - all: { - rules: { - 'test/no-foo': 'error', - 'test/no-bar': 'error', - // test/no-baz rule intentionally not in any config. - } - }, - recommended: { - rules: { - 'test/no-foo': 'error', - } - }, - style: { - rules: { - 'test/no-bar': 'error', - } - } - } - };`, - - // README.md - 'README.md': outdent` - # eslint-plugin-test - Description. - ## Rules - - ... - - more content. - `, - - // RULE DOC FILES - - 'docs/rules/no-foo.md': outdent` - # title (rule-name) - description - - ## Rule details - details - ## Options - optionToDoSomething1 - explanation - optionToDoSomething2 - explanation - `, // rule doc with incorrect header content - 'docs/rules/no-bar.md': outdent` - - ## Rule details - details - `, // marker but no header content - 'docs/rules/no-baz.md': outdent` - ## Rule details - details - `, // content but missing marker - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('updates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('only a `recommended` config', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description.' }, }, - create(context) {} - }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'error', - } - } - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('updates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule doc without header marker but pre-existing header', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description.' }, }, - create(context) {} - }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'error', - } - } - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': outdent` - # Some pre-existing title. - Pre-existing notice about the rule being recommended. - ## Rule details - Details. - `, - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('updates the documentation', async function () { - await generate('.'); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('deprecated rules - with nested rule names', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'category/no-foo': { - meta: { - docs: { description: 'Description.' }, - deprecated: true, - replacedBy: ['category/no-bar'], // without plugin prefix - }, - create(context) {} - }, - 'category/no-bar': { - meta: { - docs: { description: 'Description.' }, - deprecated: true, - replacedBy: ['test/category/no-foo'], // with plugin prefix - }, - create(context) {} - }, - }, - configs: {} - };`, - - 'README.md': - '', - - 'docs/rules/category/no-foo.md': '', - 'docs/rules/category/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('has the correct links, especially replacement rule link', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect( - readFileSync('docs/rules/category/no-foo.md', 'utf8') - ).toMatchSnapshot(); - expect( - readFileSync('docs/rules/category/no-bar.md', 'utf8') - ).toMatchSnapshot(); - }); - }); - - describe('deprecated rules', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description.' }, - deprecated: true, - replacedBy: ['no-bar'], - }, - create(context) {} - }, - 'no-bar': { - meta: { - docs: { description: 'Description.' }, - deprecated: true, // No replacement specified. - }, - create(context) {} - }, - 'no-baz': { - meta: { - docs: { description: 'Description.' }, - deprecated: true, - replacedBy: [], // Empty array. - }, - create(context) {} - }, - 'no-biz': { - // One rule that isn't deprecated. - meta: { - docs: { description: 'Description.' }, - }, - create(context) {} - }, - }, - configs: {} - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - 'docs/rules/no-biz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('updates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('deprecated rule - using prefix ahead of replacement rule name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description.' }, - deprecated: true, - replacedBy: ['test/no-bar'], - }, - create(context) {} - }, - 'no-bar': { - meta: { docs: { description: 'Description.' }, }, - create(context) {} - }, - }, - configs: {} - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses correct replacement rule link', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('deprecated rule with no rule doc but --ignore-deprecated-rules', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { deprecated: true, }, - create(context) {} - }, - }, - configs: {} - };`, - - 'README.md': - '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('omits the rule from the README and does not try to update its non-existent rule doc', async function () { - await generate('.', { ignoreDeprecatedRules: true }); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('missing rule doc', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { }, - create(context) {} - }, - }, - };`, - - 'README.md': - '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - // Use join to handle both Windows and Unix paths. - await expect(generate('.')).rejects.toThrow( - `Could not find rule doc: ${join('docs', 'rules', 'no-foo.md')}` - ); - }); - }); - - describe('missing README', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { }, - create(context) {} - }, - }, - };`, - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect(generate('.')).rejects.toThrow( - 'Could not find README.md in ESLint plugin.' - ); - }); - }); - - describe('lowercase README file', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { }, create(context) {} }, - }, - };`, - - 'readme.md': - '', - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('readme.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('custom path to rule docs and rules list', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { }, create(context) {} }, - }, - };`, - - 'rules/list.md': - '', - 'rules/no-foo/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.', { - pathRuleDoc: 'rules/{name}/{name}.md', - pathRuleList: 'rules/list.md', - }); - expect(readFileSync('rules/list.md', 'utf8')).toMatchSnapshot(); - expect( - readFileSync('rules/no-foo/no-foo.md', 'utf8') - ).toMatchSnapshot(); - }); - }); - - describe('shows column and notice for requiresTypeChecking', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {} - }, - 'no-bar': { - meta: { docs: { description: 'Description of no-bar.', requiresTypeChecking: true }, }, - create(context) {} - }, - }, - configs: { - all: { - rules: { - 'test/no-foo': 'error', - } - }, - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('updates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - describe('README missing rule list markers but with rules section', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {} - }, - }, - configs: {} - };`, - - 'README.md': outdent` - # eslint-plugin-test - - Foo. - - ## Rules - - Old rules list. - - ## Other - - Bar. - `, - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('adds rule list markers to rule section', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('README missing rule list markers and no rules section', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {} - }, - }, - };`, - - 'README.md': '# eslint-plugin-test', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('throws an error', async function () { - await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('Rule doc has options section but rule has no options', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {} - }, - }, - configs: { - all: { - rules: { - 'test/no-foo': 'error', - } - } - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '## Options\n', // empty - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('prints an error', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.'); - expect(consoleErrorStub.callCount).toBe(1); - expect(consoleErrorStub.firstCall.args).toStrictEqual([ - '`no-foo` rule doc should not have included any of these headers: Options, Config', - ]); - consoleErrorStub.restore(); - }); - }); - - describe('Rule doc missing options section', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description of no-foo.', }, - schema: [{ type: 'object', },] - }, - create(context) {}, - }, - }, - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', // empty - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('prints an error', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.'); - expect(consoleErrorStub.callCount).toBe(1); - expect(consoleErrorStub.firstCall.args).toStrictEqual([ - '`no-foo` rule doc should have included one of these headers: Options, Config', - ]); - consoleErrorStub.restore(); - }); - }); - - describe('Rule doc missing options section with --rule-doc-section-options=true', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description of no-foo.', }, - schema: [{ type: 'object', },] - }, - create(context) {}, - }, - }, - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', // empty - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('prints an error', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.', { ruleDocSectionOptions: true }); - expect(consoleErrorStub.callCount).toBe(1); - expect(consoleErrorStub.firstCall.args).toStrictEqual([ - '`no-foo` rule doc should have included one of these headers: Options, Config', - ]); - consoleErrorStub.restore(); - }); - }); - - describe('Rule doc missing options section with --rule-doc-section-options=false', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description of no-foo.', }, - schema: [{ type: 'object', },] - }, - create(context) {}, - }, - }, - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', // empty - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('has no error', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.', { ruleDocSectionOptions: false }); - expect(consoleErrorStub.callCount).toBe(0); - consoleErrorStub.restore(); - }); - }); - - describe('Rule has options with quotes', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description of no-foo.', }, - schema: [ - { - type: 'object', - properties: { - 'input[type="foo"]': { - type: 'boolean', - default: false, - }, - "input[type='bar']": { - type: 'boolean', - default: false, - }, - }, - additionalProperties: false, - }, - ] - }, - create(context) {}, - }, - }, - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': - '## Options\n input[type=\\"foo\\"] \n input[type=\\\'bar\\\']', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('successfully finds the options mentioned in the rule doc despite quote escaping', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.'); - expect(consoleErrorStub.callCount).toBe(0); - consoleErrorStub.restore(); - }); - }); - - describe('Rule description needs to be formatted', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'disallow foo.' }, }, - create(context) {} - }, - }, - configs: {} - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('capitalizes the first letter and removes the trailing period from the description', async function () { - await generate('.'); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('Rule doc does not mention an option', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description of no-foo.' }, - schema: [ - { - type: 'object', - properties: { - optionToDoSomething: { - type: 'boolean', - default: false, - }, - }, - additionalProperties: false, - }, - ] - }, - create(context) {} - }, - }, - configs: { - all: { - rules: { - 'test/no-foo': 'error', - } - } - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '## Options\n', // empty - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('prints an error', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.'); - expect(consoleErrorStub.callCount).toBe(1); - expect(consoleErrorStub.firstCall.args).toStrictEqual([ - '`no-foo` rule doc should have included: optionToDoSomething', - ]); - consoleErrorStub.restore(); - }); - }); - - describe('Missing plugin package.json', function () { - beforeEach(function () { - mockFs({ - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('throws an error', async function () { - await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('Missing plugin package.json `name` field', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - exports: 'index.js', - }), - - 'index.js': 'export default { rules: {} }', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('throws an error', async function () { - await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('Scoped plugin name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: '@my-scope/eslint-plugin', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'disallow foo.' }, }, - create(context) {} - }, - }, - configs: { - 'recommended': { rules: { '@my-scope/no-foo': 'error', } } - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('determines the correct plugin prefix', async function () { - await generate('.'); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('No configs found', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'disallow foo.' }, }, - create(context) {} - }, - } - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('omits the config column', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('No exported rules object found', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': 'export default {};', - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('throws an error', async function () { - await expect(generate('.')).rejects.toThrowErrorMatchingSnapshot(); - }); - }); - - describe('package.json using exports, as string', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: './index.js', - }), - - 'index.js': `export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'disallow foo.' }, }, - create(context) {} - }, - }, - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('finds the entry point', async function () { - await expect(generate('.')).resolves.toBeUndefined(); - }); - }); - - describe('package.json using exports, object with dot', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: { '.': './index-foo.js' }, - }), - - 'index-foo.js': `export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'disallow foo.' }, }, - create(context) {} - }, - }, - };`, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('finds the entry point', async function () { - await expect(generate('.')).resolves.toBeUndefined(); - }); - }); - - describe('plugin entry point in JSON format', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: './index.json', - }), - - 'index.json': ` - { - "rules": { - "no-foo": { - "meta": { - "docs": { - "description": "Description for no-foo" - } - } - } - }, - "configs": { - "recommended": { - "rules": { - "test/no-foo": "error" - } - } - } - } - `, - - 'README.md': - '', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - it('generates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with one blank line around comment markers', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - type: 'module', - exports: 'index.js', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, - create(context) {} - }, - }, - };`, - - 'README.md': outdent` - # Rules - - One blank line after this. - - - - - - - One blank line before this. - `, - - 'docs/rules/no-foo.md': outdent` - - - One blank line before this. - `, - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with no blank lines around comment markers', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - type: 'module', - exports: 'index.js', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, - create(context) {} - }, - }, - };`, - - 'README.md': outdent` - # Rules - - No blank line after this. - - - No blank line before this. - `, - - 'docs/rules/no-foo.md': outdent` - - No blank line before this. - `, - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('no existing comment markers - with no blank lines in existing content', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - type: 'module', - exports: 'index.js', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, - create(context) {} - }, - }, - };`, - - 'README.md': outdent` - ## Rules - Existing rules section content. - `, - - 'docs/rules/no-foo.md': outdent` - # no-foo - Existing rule doc content. - `, - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('no existing comment markers - with one blank line around existing content', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - type: 'module', - exports: 'index.js', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, - create(context) {} - }, - }, - };`, - - 'README.md': outdent` - ## Rules - - Existing rules section content. - `, - - 'docs/rules/no-foo.md': outdent` - # no-foo - - Existing rule doc content. - `, - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('no existing comment markers - minimal doc content', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - type: 'module', - exports: 'index.js', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, fixable: 'code' }, - create(context) {} - }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('config with overrides', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {} - }, - }, - configs: { - recommended: { - overrides: [{ - files: ['**/foo.js'], - rules: { - 'test/no-foo': 'error', - } - }] - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule config with options', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {}, - schema: [{ /* some options */ }] - }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': ['error', { /* some options */ }], - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rules that are disabled or set to warn', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {}, - }, - 'no-bar': { - meta: { docs: { description: 'Description of no-bar.' }, }, - create(context) {}, - }, - 'no-baz': { - meta: { docs: { description: 'Description of no-baz.' }, }, - create(context) {}, - }, - 'no-biz': { - meta: { docs: { description: 'Description of no-biz.' }, }, - create(context) {}, - }, - 'no-boz': { - meta: { docs: { description: 'Description of no-boz.' }, }, - create(context) {}, - }, - 'no-buz': { - meta: { docs: { description: 'Description of no-buz.' }, }, - create(context) {}, - }, - 'no-bez': { - meta: { docs: { description: 'Description of no-bez.' }, }, - create(context) {}, - }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'off', - 'test/no-bar': 0, - 'test/no-baz': 'error', - 'test/no-boz': 'warn', - 'test/no-buz': 1, - } - }, - other: { - rules: { - 'test/no-bar': 0, - 'test/no-baz': 'off', - 'test/no-biz': 'off', - 'test/no-buz': 'warn', - 'test/no-bez': 'warn', - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - 'docs/rules/no-biz.md': '', - 'docs/rules/no-boz.md': '', - 'docs/rules/no-buz.md': '', - 'docs/rules/no-bez.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-boz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-buz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bez.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rules that are disabled or set to warn, only one config present', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {}, - }, - 'no-bar': { - meta: { docs: { description: 'Description of no-bar.' }, }, - create(context) {}, - }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 1, - 'test/no-bar': 0, - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rules that are disabled or set to warn, two configs present', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description of no-foo.' }, }, - create(context) {}, - }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 1, - } - }, - typescript: { - rules: { - 'test/no-foo': 0, - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('no rules with description', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { }, - create(context) {}, - }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('one rule missing description', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { docs: { description: 'Description for no-foo.'} }, - create(context) {}, - }, - 'no-bar': { - meta: { }, - create(context) {}, - }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('deprecated function-style rule', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': function create () {} - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'error', - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule with no meta object', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { create(context) {} }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'error', - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('generates the documentation', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--url-configs` option', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - 'no-bar': { meta: { docs: { description: 'Description for no-bar.'} }, create(context) {} }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'error', - } - }, - customConfig: { - rules: { - 'test/no-bar': 'error', - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('includes the config link', async function () { - await generate('.', { - urlConfigs: 'http://example.com/configs', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--url-configs` option with only recommended config', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - recommended: { - rules: { - 'test/no-foo': 'error', - } - }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('includes the config link', async function () { - await generate('.', { - urlConfigs: 'http://example.com/configs', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--rule-doc-title-format` option = desc-parens-prefix-name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - 'no-bar': { meta: { docs: {} }, create(context) {} }, // No description. - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses the right rule doc title format, with fallback when missing description', async function () { - await generate('.', { - ruleDocTitleFormat: 'desc-parens-prefix-name', - }); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--rule-doc-title-format` option = desc-parens-name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - 'no-bar': { meta: { docs: { /* one rule without description */ } }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses the right rule doc title format', async function () { - await generate('.', { - ruleDocTitleFormat: 'desc-parens-name', - }); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--rule-doc-title-format` option = desc', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - 'no-bar': { meta: { docs: { /* one rule without description */ } }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses the right rule doc title format', async function () { - await generate('.', { - ruleDocTitleFormat: 'desc', - }); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--rule-doc-title-format` option = prefix-name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses the right rule doc title format', async function () { - await generate('.', { - ruleDocTitleFormat: 'prefix-name', - }); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--rule-doc-title-format` option = name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses the right rule doc title format', async function () { - await generate('.', { - ruleDocTitleFormat: 'name', - }); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with `--rule-doc-section-include` and `--rule-doc-section-exclude` and no problems', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '## Examples\n', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('has no issues', async function () { - await expect( - generate('.', { - ruleDocSectionInclude: ['Examples'], - ruleDocSectionExclude: ['Unwanted Section'], - }) - ).resolves.toBeUndefined(); - }); - }); - - describe('with `--rule-doc-section-include` and `--rule-doc-section-exclude` and problems', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '## Unwanted Section\n', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('prints errors', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.', { - ruleDocSectionInclude: ['Examples'], - ruleDocSectionExclude: ['Unwanted Section'], - }); - expect(consoleErrorStub.callCount).toBe(2); - expect(consoleErrorStub.firstCall.args).toStrictEqual([ - '`no-foo` rule doc should have included the header: Examples', - ]); - expect(consoleErrorStub.secondCall.args).toStrictEqual([ - '`no-foo` rule doc should not have included the header: Unwanted Section', - ]); - consoleErrorStub.restore(); - }); - }); - - describe('with --ignore-config', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - recommended: { - rules: { 'test/no-foo': 'error' }, - }, - configToIgnore: { - rules: { 'test/no-foo': 'error' }, - } - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('hides the ignored config', async function () { - await generate('.', { - ignoreConfig: ['configToIgnore'], - configEmoji: ['configToIgnore,πŸ˜‹'], // Ensure this config has an emoji that would normally display in the legend. - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --check', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '# test/no-foo', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('prints the issues, exits with failure, and does not write changes', async function () { - const consoleErrorStub = sinon.stub(console, 'error'); - await generate('.', { check: true }); - expect(consoleErrorStub.callCount).toBe(4); - // Use join to handle both Windows and Unix paths. - expect(consoleErrorStub.firstCall.args).toStrictEqual([ - `Please run eslint-doc-generator. A rule doc is out-of-date: ${join( - 'docs', - 'rules', - 'no-foo.md' - )}`, - ]); - expect(consoleErrorStub.secondCall.args).toMatchSnapshot(); // Diff - expect(consoleErrorStub.thirdCall.args).toStrictEqual([ - 'Please run eslint-doc-generator. README.md is out-of-date.', - ]); - expect(consoleErrorStub.getCall(3).args).toMatchSnapshot(); // Diff - consoleErrorStub.restore(); - - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --config-emoji', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - 'no-bar': { meta: { docs: { description: 'Description for no-bar.'} }, create(context) {} }, - 'no-baz': { meta: { docs: { description: 'Description for no-boz.'} }, create(context) {} }, - }, - configs: { - recommended: { - rules: { 'test/no-foo': 'error', 'test/no-bar': 'error' }, - }, - stylistic: { - rules: { 'test/no-bar': 'error', 'test/no-baz': 'error' }, - }, - configWithoutEmoji: { - rules: { 'test/no-baz': 'error' }, - } - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('shows the correct emojis', async function () { - await generate('.', { - configEmoji: ['recommended,πŸ”₯', 'stylistic,🎨'], - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --config-emoji and invalid format', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { configEmoji: ['foo,bar,baz'] }) - ).rejects.toThrow( - 'Invalid configEmoji option: foo,bar,baz. Expected format: config,emoji' - ); - }); - }); - - describe('with --config-emoji and trying to remove a default emoji that does not exist', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { configEmoji: ['config-without-default-emoji'] }) - ).rejects.toThrow( - 'Invalid configEmoji option: config-without-default-emoji. Expected format: config,emoji' - ); - }); - }); - - describe('with --config-emoji and removing default emoji for a config', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - recommended: { rules: { 'test/no-foo': 'error' } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('reverts to using a badge for the config', async function () { - await generate('.', { - configEmoji: ['recommended'], - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --config-emoji and non-existent config', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: {} - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { configEmoji: ['config-does-not-exist,πŸ”₯'] }) - ).rejects.toThrow( - 'Invalid configEmoji option: config-does-not-exist config not found.' - ); - }); - }); - - describe('with --config-emoji and using a reserved emoji', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - recommended: { rules: { 'test/no-foo': 'error' } }, - style: { rules: { 'test/no-foo': 'error' } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { configEmoji: [`recommended,${EMOJI_CONFIG_ERROR}`] }) - ).rejects.toThrow( - `Cannot specify reserved emoji ${EMOJI_CONFIG_ERROR}.` - ); - }); - }); - - describe('with --config-emoji and duplicate config name', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - recommended: { rules: { 'test/no-foo': 'error' } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { configEmoji: ['recommended,πŸ”₯', 'recommended,πŸ˜‹'] }) - ).rejects.toThrow( - 'Duplicate config name in configEmoji options: recommended' - ); - }); - }); - - describe('with one config that does not have emoji', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - configWithoutEmoji: { rules: { 'test/no-foo': 'error' } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('shows the default config emoji', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with config that does not have any rules', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - configs: { - recommended: { rules: { 'test/no-foo': 'error' } }, - configWithoutRules: { rules: { } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('uses recommended config emoji since it is the only relevant config', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --rule-list-columns', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description for no-foo.' }, - hasSuggestions: true, - fixable: 'code', - deprecated: true, - }, - create(context) {} - }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('shows the right columns and legend', async function () { - await generate('.', { - ruleListColumns: 'hasSuggestions,fixable,name', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --rule-list-columns and consolidated fixableAndHasSuggestions column', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description for no-foo.' }, - hasSuggestions: true, - fixable: 'code', - }, - create(context) {} - }, - 'no-bar': { - meta: { - docs: { description: 'Description for no-bar.' }, - fixable: 'code', - }, - create(context) {} - }, - 'no-baz': { - meta: { - docs: { description: 'Description for no-baz.' }, - }, - create(context) {} - }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('shows the right columns and legend', async function () { - await generate('.', { - ruleListColumns: 'name,fixableAndHasSuggestions', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --rule-list-columns and non-existent column', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { ruleListColumns: 'name,non-existent' }) - ).rejects.toThrow('Invalid ruleListColumns option: non-existent'); - }); - }); - - describe('with --rule-list-columns and duplicate column', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { ruleListColumns: 'name,name' }) - ).rejects.toThrow( - 'Duplicate value detected in ruleListColumns option.' - ); - }); - }); - - describe('with --rule-doc-notices', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { - meta: { - docs: { description: 'Description for no-foo.' }, - hasSuggestions: true, - fixable: 'code', - deprecated: true, - type: 'problem' - }, - create(context) {} - }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('shows the right rule doc notices', async function () { - await generate('.', { - ruleDocNotices: 'hasSuggestions,fixable,deprecated,type', // Random values including an optional notice. - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('with --rule-doc-notices and non-existent notice', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { ruleDocNotices: 'fixable,non-existent' }) - ).rejects.toThrow('Invalid ruleDocNotices option: non-existent'); - }); - }); - - describe('with --rule-doc-notices and duplicate notice', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'Description for no-foo.'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { ruleDocNotices: 'fixable,fixable' }) - ).rejects.toThrow('Duplicate value detected in ruleDocNotices option.'); - }); - }); - - describe('rule with type, type column not enabled', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { type: 'problem' }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('hides the type column', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule with type, type column enabled', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { type: 'problem' }, create(context) {} }, - 'no-bar': { meta: { type: 'suggestion' }, create(context) {} }, - 'no-biz': { meta: { type: 'layout' }, create(context) {} }, - 'no-boz': { meta: { type: 'unknown' }, create(context) {} }, - 'no-buz': { meta: { /* no type*/ }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-biz.md': '', - 'docs/rules/no-boz.md': '', - 'docs/rules/no-buz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('displays the type', async function () { - await generate('.', { ruleListColumns: 'name,type' }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-boz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-buz.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule with type, type column enabled, but only an unknown type', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { type: 'unknown' }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('hides the type column and notice', async function () { - await generate('.', { ruleListColumns: 'name,type' }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list by type', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { type: 'problem' }, create(context) {} }, - 'no-bar': { meta: { type: 'suggestion' }, create(context) {} }, - 'no-baz': { meta: { type: 'suggestion' }, create(context) {} }, - 'no-biz': { meta: { /* no type */ }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - 'docs/rules/no-biz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('splits the list', async function () { - await generate('.', { - splitBy: 'meta.type', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list by nested property meta.docs.category', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { category: 'fruits' } }, create(context) {} }, - 'no-bar': { meta: { docs: { category: 'candy' } }, create(context) {} }, - 'no-baz': { meta: { /* no nested object */ }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('splits the list', async function () { - await generate('.', { splitBy: 'meta.docs.category' }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list, with boolean', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { hasSuggestions: true }, create(context) {} }, - 'no-bar': { meta: { }, create(context) {} }, - 'no-baz': { meta: { hasSuggestions: false }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('splits the list', async function () { - await generate('.', { - splitBy: 'meta.hasSuggestions', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list, ignores case', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { foo: 'c' }, create(context) {} }, - 'no-bar': { meta: { foo: 'a' }, create(context) {} }, - 'no-baz': { meta: { foo: 'B' }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - 'docs/rules/no-baz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('splits the list', async function () { - await generate('.', { - splitBy: 'meta.foo', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list, with unknown variable type', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { 'foo_barBIZ-baz3bOz': false, meta: { }, create(context) {} }, - 'no-bar': { 'foo_barBIZ-baz3bOz': true, meta: { }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('splits the list but does not attempt to convert variable name to title', async function () { - await generate('.', { - splitBy: 'foo_barBIZ-baz3bOz', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list, with one sub-list having no rules enabled by the config', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { 'type': 'foo', meta: { }, create(context) {} }, - 'no-bar': { 'type': 'bar', meta: { }, create(context) {} }, - }, - configs: { - recommended: { rules: { 'test/no-foo': 'error' } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - 'docs/rules/no-bar.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('splits the list and still uses recommended config emoji in both lists', async function () { - await generate('.', { - splitBy: 'type', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('splitting list by property that no rules have', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect( - generate('.', { splitBy: 'non-existent-property' }) - ).rejects.toThrow( - 'No rules found with --split-by property "non-existent-property".' - ); - }); - }); - - describe('rule with options, options column/notice enabled', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { schema: [{foo:true}] }, create(context) {} }, - 'no-bar': { meta: { schema: {foo:true} }, create(context) {} }, - 'no-biz': { meta: { schema: [] }, create(context) {} }, - 'no-baz': { meta: { }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '## Options\n', - 'docs/rules/no-bar.md': '## Options\n', - 'docs/rules/no-biz.md': '', - 'docs/rules/no-baz.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('displays the column and notice', async function () { - await generate('.', { - ruleListColumns: 'name,options', - ruleDocNotices: 'options', - }); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-bar.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-biz.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-baz.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule with long-enough description to require name column wrapping avoidance', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'no-foo': { meta: { docs: { description: 'over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/no-foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('adds spaces to the name column', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/no-foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('rule with long-enough description to require name column wrapping avoidance but rule name too short', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'foo': { meta: { docs: { description: 'over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars over 60 chars'} }, create(context) {} }, - }, - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/foo.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('does not add spaces to name column', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/foo.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('sorting rules and configs case-insensitive', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'index.js': ` - export default { - rules: { - 'c': { meta: { docs: {} }, create(context) {} }, - 'a': { meta: { docs: {} }, create(context) {} }, - 'B': { meta: { docs: {} }, create(context) {} }, - }, - configs: { - 'c': { rules: { 'test/a': 'error', } }, - 'a': { rules: { 'test/a': 'error', } }, - 'B': { rules: { 'test/a': 'error', } }, - } - };`, - - 'README.md': '## Rules\n', - - 'docs/rules/a.md': '', - 'docs/rules/B.md': '', - 'docs/rules/c.md': '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('sorts correctly', async function () { - await generate('.'); - expect(readFileSync('README.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/a.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/B.md', 'utf8')).toMatchSnapshot(); - expect(readFileSync('docs/rules/c.md', 'utf8')).toMatchSnapshot(); - }); - }); - - describe('plugin entry point specified but does not exist', function () { - beforeEach(function () { - mockFs({ - 'package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - 'README.md': - '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('throws an error', async function () { - await expect(generate('.')).rejects.toThrow( - 'ESLint plugin entry point does not exist. Tried: index.js' - ); - }); - }); - - describe('passing absolute path for plugin root', function () { - beforeEach(function () { - mockFs({ - '/eslint-plugin-test/package.json': JSON.stringify({ - name: 'eslint-plugin-test', - exports: 'index.js', - type: 'module', - }), - - '/eslint-plugin-test/index.js': - 'export default { rules: {}, configs: {} };', - - '/eslint-plugin-test/README.md': - '', - - // Needed for some of the test infrastructure to work. - node_modules: mockFs.load( - resolve(__dirname, '..', '..', 'node_modules') - ), - }); - }); - - afterEach(function () { - mockFs.restore(); - jest.resetModules(); - }); - - it('finds the entry point', async function () { - await expect(generate('/eslint-plugin-test/')).resolves.toBeUndefined(); - }); - }); - }); -});