diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d4f9848..3db7164 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ import { CategoryId } from '../utils/constants' module.exports = { meta: { + severity: 'error', // whether the rule should yield 'warn' or 'error' docs: { categories: [CategoryId.RECOMMENDED], // You should always use an existing category from the CategoryId enum], or create a new one there excludeFromConfig: true, // If the rule is not ready to be shipped in any category, set this flag to true, otherwise remove it @@ -82,7 +83,7 @@ ruleTester.run('my-rule-name', rule, { When you make changes to rules or create/delete rules, the configuration files and documentation have to be updated. For that, run the following command: ```sh -yarn update-all +pnpm run update-all ``` ### Useful resources diff --git a/README.md b/README.md index 490840e..ea0d498 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ This plugin does not support MDX files. | [`storybook/no-uninstalled-addons`](./docs/rules/no-uninstalled-addons.md) | This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name. | | | | [`storybook/prefer-pascal-case`](./docs/rules/prefer-pascal-case.md) | Stories should use PascalCase | 🔧 | | | [`storybook/story-exports`](./docs/rules/story-exports.md) | A story file must contain at least one story export | | | -| [`storybook/use-storybook-expect`](./docs/rules/use-storybook-expect.md) | Use expect from `@storybook/jest` | 🔧 | | +| [`storybook/use-storybook-expect`](./docs/rules/use-storybook-expect.md) | Use expect from `@storybook/test` or `@storybook/jest` | 🔧 | | | [`storybook/use-storybook-testing-library`](./docs/rules/use-storybook-testing-library.md) | Do not use testing-library directly on stories | 🔧 | | diff --git a/lib/configs/flat/addon-interactions.ts b/lib/configs/flat/addon-interactions.ts index 82d6aab..f0ed440 100644 --- a/lib/configs/flat/addon-interactions.ts +++ b/lib/configs/flat/addon-interactions.ts @@ -8,6 +8,7 @@ export = [ name: 'storybook:addon-interactions:setup', plugins: { get storybook() { + // eslint-disable-next-line @typescript-eslint/no-require-imports return require('../../index') }, }, diff --git a/lib/configs/flat/csf.ts b/lib/configs/flat/csf.ts index 23384d1..b4f7faa 100644 --- a/lib/configs/flat/csf.ts +++ b/lib/configs/flat/csf.ts @@ -8,6 +8,7 @@ export = [ name: 'storybook:csf:setup', plugins: { get storybook() { + // eslint-disable-next-line @typescript-eslint/no-require-imports return require('../../index') }, }, diff --git a/lib/configs/flat/recommended.ts b/lib/configs/flat/recommended.ts index 4dd59fe..5ece3b3 100644 --- a/lib/configs/flat/recommended.ts +++ b/lib/configs/flat/recommended.ts @@ -8,6 +8,7 @@ export = [ name: 'storybook:recommended:setup', plugins: { get storybook() { + // eslint-disable-next-line @typescript-eslint/no-require-imports return require('../../index') }, }, diff --git a/lib/rules/await-interactions.ts b/lib/rules/await-interactions.ts index 13bc314..6650ec8 100644 --- a/lib/rules/await-interactions.ts +++ b/lib/rules/await-interactions.ts @@ -29,10 +29,10 @@ export = createStorybookRule({ name: 'await-interactions', defaultOptions: [], meta: { + severity: 'error', docs: { description: 'Interactions should be awaited', categories: [CategoryId.ADDON_INTERACTIONS, CategoryId.RECOMMENDED], - recommended: 'strict', }, messages: { interactionShouldBeAwaited: 'Interaction should be awaited: {{method}}', diff --git a/lib/rules/context-in-play-function.ts b/lib/rules/context-in-play-function.ts index c4c5da7..8288171 100644 --- a/lib/rules/context-in-play-function.ts +++ b/lib/rules/context-in-play-function.ts @@ -27,10 +27,10 @@ export = createStorybookRule({ defaultOptions: [], meta: { type: 'problem', + severity: 'error', docs: { description: 'Pass a context when invoking play function of another story', categories: [CategoryId.RECOMMENDED, CategoryId.ADDON_INTERACTIONS], - recommended: 'strict', }, messages: { passContextToPlayFunction: 'Pass a context when invoking play function of another story', diff --git a/lib/rules/csf-component.ts b/lib/rules/csf-component.ts index eb25b63..ea1729f 100644 --- a/lib/rules/csf-component.ts +++ b/lib/rules/csf-component.ts @@ -18,10 +18,10 @@ export = createStorybookRule({ defaultOptions: [], meta: { type: 'suggestion', + severity: 'warn', docs: { description: 'The component property should be set', categories: [CategoryId.CSF], - recommended: 'recommended', }, messages: { missingComponentProperty: 'Missing component property.', diff --git a/lib/rules/default-exports.ts b/lib/rules/default-exports.ts index f728223..e4c18ba 100644 --- a/lib/rules/default-exports.ts +++ b/lib/rules/default-exports.ts @@ -19,10 +19,10 @@ export = createStorybookRule({ defaultOptions: [], meta: { type: 'problem', + severity: 'error', docs: { description: 'Story files should have a default export', categories: [CategoryId.CSF, CategoryId.RECOMMENDED], - recommended: 'strict', }, messages: { shouldHaveDefaultExport: 'The file should have a default export.', diff --git a/lib/rules/hierarchy-separator.ts b/lib/rules/hierarchy-separator.ts index 0c35105..139e360 100644 --- a/lib/rules/hierarchy-separator.ts +++ b/lib/rules/hierarchy-separator.ts @@ -20,10 +20,10 @@ export = createStorybookRule({ type: 'problem', fixable: 'code', hasSuggestions: true, + severity: 'warn', docs: { description: 'Deprecated hierarchy separator in title property', categories: [CategoryId.CSF, CategoryId.RECOMMENDED], - recommended: 'recommended', }, messages: { useCorrectSeparators: 'Use correct separators', diff --git a/lib/rules/meta-inline-properties.ts b/lib/rules/meta-inline-properties.ts index 05c700d..97cc533 100644 --- a/lib/rules/meta-inline-properties.ts +++ b/lib/rules/meta-inline-properties.ts @@ -21,11 +21,11 @@ export = createStorybookRule({ defaultOptions: [{ csfVersion: 3 }], meta: { type: 'problem', + severity: 'error', docs: { description: 'Meta should only have inline properties', categories: [CategoryId.CSF, CategoryId.RECOMMENDED], excludeFromConfig: true, - recommended: 'strict', }, messages: { metaShouldHaveInlineProperties: 'Meta should only have inline properties: {{property}}', diff --git a/lib/rules/no-redundant-story-name.ts b/lib/rules/no-redundant-story-name.ts index aab64a3..f5c98f8 100644 --- a/lib/rules/no-redundant-story-name.ts +++ b/lib/rules/no-redundant-story-name.ts @@ -29,10 +29,10 @@ export = createStorybookRule({ type: 'suggestion', fixable: 'code', hasSuggestions: true, + severity: 'warn', docs: { description: 'A story should not have a redundant name property', categories: [CategoryId.CSF, CategoryId.RECOMMENDED], - recommended: 'recommended', }, messages: { removeRedundantName: 'Remove redundant name', diff --git a/lib/rules/no-stories-of.ts b/lib/rules/no-stories-of.ts index 419716a..9616107 100644 --- a/lib/rules/no-stories-of.ts +++ b/lib/rules/no-stories-of.ts @@ -15,10 +15,10 @@ export = createStorybookRule({ defaultOptions: [], meta: { type: 'problem', + severity: 'error', docs: { description: 'storiesOf is deprecated and should not be used', categories: [CategoryId.CSF_STRICT], - recommended: 'strict', }, messages: { doNotUseStoriesOf: 'storiesOf is deprecated and should not be used', diff --git a/lib/rules/no-title-property-in-meta.ts b/lib/rules/no-title-property-in-meta.ts index 3a22e32..0a05f32 100644 --- a/lib/rules/no-title-property-in-meta.ts +++ b/lib/rules/no-title-property-in-meta.ts @@ -20,10 +20,10 @@ export = createStorybookRule({ type: 'problem', fixable: 'code', hasSuggestions: true, + severity: 'error', docs: { description: 'Do not define a title in meta', categories: [CategoryId.CSF_STRICT], - recommended: 'strict', }, messages: { removeTitleInMeta: 'Remove title property from meta', diff --git a/lib/rules/no-uninstalled-addons.ts b/lib/rules/no-uninstalled-addons.ts index 990c5a4..25365c4 100644 --- a/lib/rules/no-uninstalled-addons.ts +++ b/lib/rules/no-uninstalled-addons.ts @@ -35,11 +35,11 @@ export = createStorybookRule({ ], meta: { type: 'problem', + severity: 'error', docs: { description: 'This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name.', categories: [CategoryId.RECOMMENDED], - recommended: 'strict', }, messages: { addonIsNotInstalled: `The {{ addonName }} is not installed in {{packageJsonPath}}. Did you forget to install it or is your package.json in a different location?`, @@ -146,7 +146,8 @@ export = createStorybookRule({ const parsedFile = JSON.parse(file) packageJson.dependencies = parsedFile.dependencies || {} packageJson.devDependencies = parsedFile.devDependencies || {} - } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (err) { throw new Error( dedent`The provided path in your eslintrc.json - ${path} is not a valid path to a package.json file or your package.json file is not in the same folder as ESLint is running from. diff --git a/lib/rules/prefer-pascal-case.ts b/lib/rules/prefer-pascal-case.ts index fe06439..2422d80 100644 --- a/lib/rules/prefer-pascal-case.ts +++ b/lib/rules/prefer-pascal-case.ts @@ -22,10 +22,10 @@ export = createStorybookRule({ type: 'suggestion', fixable: 'code', hasSuggestions: true, + severity: 'warn', docs: { description: 'Stories should use PascalCase', categories: [CategoryId.RECOMMENDED], - recommended: 'stylistic', }, messages: { convertToPascalCase: 'Use pascal case', @@ -137,6 +137,7 @@ export = createStorybookRule({ excludeStories: getDescriptor(meta, 'excludeStories'), includeStories: getDescriptor(meta, 'includeStories'), } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { // } diff --git a/lib/rules/story-exports.ts b/lib/rules/story-exports.ts index fea74f7..7993e5d 100644 --- a/lib/rules/story-exports.ts +++ b/lib/rules/story-exports.ts @@ -24,10 +24,10 @@ export = createStorybookRule({ defaultOptions: [], meta: { type: 'problem', + severity: 'error', docs: { description: 'A story file must contain at least one story export', categories: [CategoryId.RECOMMENDED, CategoryId.CSF], - recommended: 'strict', }, messages: { shouldHaveStoryExport: 'The file should have at least one story export', @@ -69,6 +69,7 @@ export = createStorybookRule({ excludeStories: getDescriptor(meta, 'excludeStories'), includeStories: getDescriptor(meta, 'includeStories'), } + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { // } diff --git a/lib/rules/use-storybook-expect.ts b/lib/rules/use-storybook-expect.ts index f2c3f9e..8c78ce0 100644 --- a/lib/rules/use-storybook-expect.ts +++ b/lib/rules/use-storybook-expect.ts @@ -23,17 +23,15 @@ export = createStorybookRule({ meta: { type: 'suggestion', fixable: 'code', - hasSuggestions: true, schema: [], + severity: 'error', docs: { description: 'Use expect from `@storybook/test` or `@storybook/jest`', categories: [CategoryId.ADDON_INTERACTIONS, CategoryId.RECOMMENDED], - recommended: 'strict', }, messages: { - updateImports: 'Update imports', useExpectFromStorybook: - 'Do not use global expect directly in the story. You should import it from `@storybook/test` or `@storybook/jest` instead.', + 'Do not use global expect directly in the story. You should import it from `@storybook/test` (preferrably) or `@storybook/jest` instead.', }, }, diff --git a/lib/rules/use-storybook-testing-library.ts b/lib/rules/use-storybook-testing-library.ts index b4430c2..07b09af 100644 --- a/lib/rules/use-storybook-testing-library.ts +++ b/lib/rules/use-storybook-testing-library.ts @@ -18,16 +18,16 @@ export = createStorybookRule({ type: 'suggestion', fixable: 'code', hasSuggestions: true, + severity: 'error', docs: { description: 'Do not use testing-library directly on stories', categories: [CategoryId.ADDON_INTERACTIONS, CategoryId.RECOMMENDED], - recommended: 'strict', }, schema: [], messages: { updateImports: 'Update imports', dontUseTestingLibraryDirectly: - 'Do not use `{{library}}` directly in the story. You should import the functions from `@storybook/testing-library` instead.', + 'Do not use `{{library}}` directly in the story. You should import the functions from `@storybook/test` (preferrably) or `@storybook/testing-library` instead.', }, }, diff --git a/lib/types/index.ts b/lib/types/index.ts index 9f772fd..6289305 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -1,12 +1,8 @@ import { TSESLint } from '@typescript-eslint/utils' import { CategoryId } from '../utils/constants' -import { - RuleRecommendation, - RuleRecommendationAcrossConfigs, -} from '@typescript-eslint/utils/ts-eslint' export type RuleModule = TSESLint.RuleModule<'', []> & { - meta: { hasSuggestions?: boolean; docs: { recommendedConfig?: 'error' | 'warn' } } + meta: { hasSuggestions?: boolean } } // These 2 types are copied from @typescript-eslint/experimental-utils' CreateRuleMeta @@ -20,23 +16,21 @@ export type StorybookRuleMetaDocs = TSESLint.RuleMetaDataDocs & { * Which configs the rule should be part of */ categories?: CategoryId[] - - /** - * If a string config name, which starting config this rule is enabled in. - * If an object, which settings it has enabled in each of those configs. - */ - recommended?: RuleRecommendation | RuleRecommendationAcrossConfigs } -export type StorybookRuleMeta = TSESLint.RuleMetaData< +export type StorybookRuleMeta = TSESLint.RuleMetaData< TMessageIds, StorybookRuleMetaDocs -> +> & { + /** + * Severity of the rule to be defined in eslint config + */ + severity: 'off' | 'warn' | 'error' +} // Comment out for testing purposes: // const docs: StorybookRuleMetaDocs = { // description: 'bla', -// recommended: 'strict', // } // const meta: StorybookRuleMeta<'someId'> = { @@ -46,4 +40,5 @@ export type StorybookRuleMeta = TSESLint.RuleMetaDat // type: 'problem', // schema: [], // docs, +// severity: 'error', // } diff --git a/tests/integrations/flat-config.spec.ts b/tests/integrations/flat-config.spec.ts index 27099da..8a9f43e 100644 --- a/tests/integrations/flat-config.spec.ts +++ b/tests/integrations/flat-config.spec.ts @@ -14,7 +14,9 @@ describe('Integration with flat config', () => { cp.execSync('pnpm i -f', { stdio: 'inherit' }) }) afterEach(() => { - originalCwd && process.chdir(originalCwd) + if (originalCwd) { + process.chdir(originalCwd) + } }) it('should work with config', () => { diff --git a/tests/integrations/legacy-config.spec.ts b/tests/integrations/legacy-config.spec.ts index 1c1fc32..f441480 100644 --- a/tests/integrations/legacy-config.spec.ts +++ b/tests/integrations/legacy-config.spec.ts @@ -14,7 +14,9 @@ describe('Integration with legacy config', () => { cp.execSync('pnpm i -f', { stdio: 'inherit' }) }) afterEach(() => { - originalCwd && process.chdir(originalCwd) + if (originalCwd) { + process.chdir(originalCwd) + } }) it('should work with config', () => { diff --git a/tools/generate-rule.ts b/tools/generate-rule.ts index 92b91f5..fe93cbe 100644 --- a/tools/generate-rule.ts +++ b/tools/generate-rule.ts @@ -80,11 +80,11 @@ const generateRule = async () => { defaultOptions: [], meta: { type: 'problem', // \`problem\`, \`suggestion\`, or \`layout\` + severity: 'error', // or 'warn' docs: { description: '${ruleDescription}', // Add the categories that suit this rule. categories: [CategoryId.RECOMMENDED], - recommended: 'recommended', }, messages: { anyMessageIdHere: 'Fill me in', diff --git a/tools/update-lib-flat-configs.ts b/tools/update-lib-flat-configs.ts index eb99ad8..a1fc2c8 100644 --- a/tools/update-lib-flat-configs.ts +++ b/tools/update-lib-flat-configs.ts @@ -28,6 +28,7 @@ function formatCategory(category: TCategory) { name: 'storybook:${category.categoryId}:setup', plugins: { get storybook() { + // eslint-disable-next-line @typescript-eslint/no-require-imports return require('../../index') } } diff --git a/tools/utils/rules.ts b/tools/utils/rules.ts index 86d03ec..6b1c2fa 100644 --- a/tools/utils/rules.ts +++ b/tools/utils/rules.ts @@ -2,17 +2,12 @@ import fs from 'fs' import path from 'path' import { createStorybookRule } from '../../lib/utils/create-storybook-rule' +import { StorybookRuleMeta } from '../../lib/types' const ROOT = path.resolve(__dirname, '../../lib/rules') export type TRule = ReturnType & { - meta: { - docs: { - categories?: string[] - category?: string - excludeFromConfig?: boolean - } - } + meta: StorybookRuleMeta } const rules = fs @@ -21,20 +16,12 @@ const rules = fs .map((file) => path.basename(file, '.ts')) .map((name) => { const rule = require(path.join(ROOT, name)) as TRule - const meta = { ...rule.meta } + const meta: StorybookRuleMeta = { ...rule.meta } if (meta.docs && !meta.docs.categories) { meta.docs = { ...meta.docs } meta.docs.categories = [] - - if (meta.docs.category) { - meta.docs.categories.push(meta.docs.category) - delete meta.docs.category - } - - if (meta.docs.recommended) { - meta.docs.categories.push('recommended') - } } + return { ruleId: `storybook/${name}`, name, @@ -42,7 +29,7 @@ const rules = fs } }) // We might have rules which are almost ready but should not be shipped - .filter((rule) => !rule.meta.docs.excludeFromConfig) + .filter((rule) => !rule.meta.docs?.excludeFromConfig) export type TRules = typeof rules diff --git a/tools/utils/updates.ts b/tools/utils/updates.ts index 8d389e0..ec33be6 100644 --- a/tools/utils/updates.ts +++ b/tools/utils/updates.ts @@ -15,7 +15,7 @@ export function formatRules(rules: TCategory['rules'], exclude?: string[]) { const obj = rules.reduce( (setting, rule) => { if (!exclude?.includes(rule.ruleId)) { - setting[rule.ruleId] = rule.meta.docs.recommended || 'error' + setting[rule.ruleId] = rule.meta.severity || 'error' } return setting }, @@ -26,7 +26,7 @@ export function formatRules(rules: TCategory['rules'], exclude?: string[]) { } export function formatSingleRule(rules: TCategory['rules'], ruleId: string) { - const ruleOpt = rules.find((rule) => rule.ruleId === ruleId)?.meta.docs.recommended || 'error' + const ruleOpt = rules.find((rule) => rule.ruleId === ruleId)?.meta.severity || 'error' return JSON.stringify({ [ruleId]: ruleOpt }, null, 2) }