diff --git a/.eslintrc.js b/.eslintrc.js index 3ac46e2..6e55d70 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,9 +5,11 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:eslint-plugin/recommended', - 'plugin:node/recommended', 'plugin:prettier/recommended', + 'plugin:@typescript-eslint/recommended', ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], env: { node: true, }, diff --git a/lib/rules/await-interactions.ts b/lib/rules/await-interactions.ts index 0d91315..d546250 100644 --- a/lib/rules/await-interactions.ts +++ b/lib/rules/await-interactions.ts @@ -159,7 +159,7 @@ export = createStorybookRule({ */ let isImportedFromStorybook = true - let invocationsThatShouldBeAwaited = [] as Array<{ + const invocationsThatShouldBeAwaited = [] as Array<{ node: TSESTree.Node method: TSESTree.Identifier }> diff --git a/lib/rules/context-in-play-function.ts b/lib/rules/context-in-play-function.ts index 4aefdcf..8ab50b7 100644 --- a/lib/rules/context-in-play-function.ts +++ b/lib/rules/context-in-play-function.ts @@ -3,7 +3,7 @@ * @author Yann Braga */ -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' import { createStorybookRule } from '../utils/create-storybook-rule' import { CategoryId } from '../utils/constants' @@ -100,7 +100,7 @@ export = createStorybookRule({ // Public //---------------------------------------------------------------------- - let invocationsWithoutProperContext: TSESTree.Node[] = []; + const invocationsWithoutProperContext: TSESTree.Node[] = [] return { CallExpression(node: TSESTree.CallExpression) { diff --git a/lib/rules/csf-component.ts b/lib/rules/csf-component.ts index eeef85b..b2f2e3a 100644 --- a/lib/rules/csf-component.ts +++ b/lib/rules/csf-component.ts @@ -3,7 +3,7 @@ * @author Yann Braga */ -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' import { getMetaObjectExpression } from '../utils' import { CategoryId } from '../utils/constants' import { createStorybookRule } from '../utils/create-storybook-rule' diff --git a/lib/rules/hierarchy-separator.ts b/lib/rules/hierarchy-separator.ts index e6c186c..ab32bc0 100644 --- a/lib/rules/hierarchy-separator.ts +++ b/lib/rules/hierarchy-separator.ts @@ -3,7 +3,7 @@ * @author Yann Braga */ -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' import { getMetaObjectExpression } from '../utils' import { isLiteral, isSpreadElement } from '../utils/ast' import { CategoryId } from '../utils/constants' diff --git a/lib/rules/meta-inline-properties.ts b/lib/rules/meta-inline-properties.ts index 9b98eb1..3df3f71 100644 --- a/lib/rules/meta-inline-properties.ts +++ b/lib/rules/meta-inline-properties.ts @@ -78,7 +78,7 @@ export = createStorybookRule({ } const ruleProperties = ['title', 'args'] - let dynamicProperties: TDynamicProperty[] = [] + const dynamicProperties: TDynamicProperty[] = [] const metaNodes = meta.properties.filter( (prop) => 'key' in prop && 'name' in prop.key && ruleProperties.includes(prop.key.name) diff --git a/lib/rules/no-title-property-in-meta.ts b/lib/rules/no-title-property-in-meta.ts index c7beb0d..76909fd 100644 --- a/lib/rules/no-title-property-in-meta.ts +++ b/lib/rules/no-title-property-in-meta.ts @@ -3,7 +3,7 @@ * @author Yann Braga */ -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' import { getMetaObjectExpression } from '../utils' import { CategoryId } from '../utils/constants' import { createStorybookRule } from '../utils/create-storybook-rule' diff --git a/lib/rules/no-uninstalled-addons.ts b/lib/rules/no-uninstalled-addons.ts index 033dcbc..e3694fa 100644 --- a/lib/rules/no-uninstalled-addons.ts +++ b/lib/rules/no-uninstalled-addons.ts @@ -18,7 +18,7 @@ import { isVariableDeclarator, isVariableDeclaration, } from '../utils/ast' -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' //------------------------------------------------------------------------------ // Rule Definition @@ -66,7 +66,7 @@ export = createStorybookRule({ return !!item } - type MergeDepsWithDevDeps = (packageJson: Record) => string[] + type MergeDepsWithDevDeps = (packageJson: PackageJsonDependencies) => string[] const mergeDepsWithDevDeps: MergeDepsWithDevDeps = (packageJson) => { const deps = Object.keys(packageJson.dependencies || {}) const devDeps = Object.keys(packageJson.devDependencies || {}) @@ -108,7 +108,12 @@ export = createStorybookRule({ return result.length ? result : false } - type GetPackageJson = (path: string) => Record + type PackageJsonDependencies = { + devDependencies: Record + dependencies: Record + } + + type GetPackageJson = (path: string) => PackageJsonDependencies const getPackageJson: GetPackageJson = (path) => { const packageJson = { @@ -168,7 +173,7 @@ export = createStorybookRule({ function reportUninstalledAddons(addonsProp: TSESTree.ArrayExpression) { const packageJsonPath = resolve(packageJsonLocation || `./package.json`) - let packageJsonObject: Record + let packageJsonObject: PackageJsonDependencies try { packageJsonObject = getPackageJson(packageJsonPath) } catch (e) { diff --git a/lib/rules/prefer-pascal-case.ts b/lib/rules/prefer-pascal-case.ts index 25cdb85..882bbbb 100644 --- a/lib/rules/prefer-pascal-case.ts +++ b/lib/rules/prefer-pascal-case.ts @@ -3,7 +3,7 @@ * @author Yann Braga */ -import { ASTUtils, TSESTree } from "@typescript-eslint/utils"; +import { ASTUtils, TSESTree } from '@typescript-eslint/utils' import { IncludeExcludeOptions, isExportStory } from '@storybook/csf' import { getDescriptor, getMetaObjectExpression } from '../utils' @@ -83,7 +83,7 @@ export = createStorybookRule({ const referenceCount = variable?.references?.length || 0 for (let i = 0; i < referenceCount; i++) { - const ref = variable!.references[i] + const ref = variable?.references[i] if (ref && !ref.init) { yield fixer.replaceTextRange(ref.identifier.range, pascal) } @@ -102,7 +102,7 @@ export = createStorybookRule({ let meta let nonStoryExportsConfig: IncludeExcludeOptions - let namedExports: TSESTree.Identifier[] = [] + const namedExports: TSESTree.Identifier[] = [] let hasStoriesOfImport = false return { @@ -119,7 +119,9 @@ export = createStorybookRule({ excludeStories: getDescriptor(meta, 'excludeStories'), includeStories: getDescriptor(meta, 'includeStories'), } - } catch (err) {} + } catch (err) { + // + } } }, ExportNamedDeclaration: function (node: TSESTree.ExportNamedDeclaration) { diff --git a/lib/rules/story-exports.ts b/lib/rules/story-exports.ts index 5006e4d..35e59fc 100644 --- a/lib/rules/story-exports.ts +++ b/lib/rules/story-exports.ts @@ -3,7 +3,6 @@ * @author Yann Braga */ - import { createStorybookRule } from '../utils/create-storybook-rule' import { CategoryId } from '../utils/constants' import { @@ -14,7 +13,7 @@ import { } from '../utils' import { isImportDeclaration } from '../utils/ast' import { IncludeExcludeOptions } from '@storybook/csf' -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' //------------------------------------------------------------------------------ // Rule Definition @@ -54,7 +53,7 @@ export = createStorybookRule({ let hasStoriesOfImport = false let nonStoryExportsConfig: IncludeExcludeOptions = {} let meta: TSESTree.ObjectExpression | null - let namedExports: TSESTree.Identifier[] = [] + const namedExports: TSESTree.Identifier[] = [] return { ImportSpecifier(node) { @@ -70,7 +69,9 @@ export = createStorybookRule({ excludeStories: getDescriptor(meta, 'excludeStories'), includeStories: getDescriptor(meta, 'includeStories'), } - } catch (err) {} + } catch (err) { + // + } } }, ExportNamedDeclaration: function (node) { diff --git a/lib/rules/use-storybook-expect.ts b/lib/rules/use-storybook-expect.ts index a199bc4..d31168c 100644 --- a/lib/rules/use-storybook-expect.ts +++ b/lib/rules/use-storybook-expect.ts @@ -7,7 +7,7 @@ import { CategoryId } from '../utils/constants' import { isIdentifier, isImportSpecifier } from '../utils/ast' import { createStorybookRule } from '../utils/create-storybook-rule' -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' //------------------------------------------------------------------------------ // Rule Definition @@ -56,7 +56,7 @@ export = createStorybookRule({ //---------------------------------------------------------------------- let isImportingFromStorybookExpect = false - let expectInvocations: TSESTree.Identifier[] = [] + const expectInvocations: TSESTree.Identifier[] = [] return { ImportDeclaration(node) { diff --git a/lib/rules/use-storybook-testing-library.ts b/lib/rules/use-storybook-testing-library.ts index 98955be..107ac8a 100644 --- a/lib/rules/use-storybook-testing-library.ts +++ b/lib/rules/use-storybook-testing-library.ts @@ -6,7 +6,7 @@ import { isImportDefaultSpecifier } from '../utils/ast' import { CategoryId } from '../utils/constants' import { createStorybookRule } from '../utils/create-storybook-rule' -import { TSESTree } from "@typescript-eslint/utils"; +import { TSESTree } from '@typescript-eslint/utils' //------------------------------------------------------------------------------ // Rule Definition diff --git a/lib/types/index.ts b/lib/types/index.ts index 5c0017c..538df07 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -5,16 +5,9 @@ export type RuleModule = TSESLint.RuleModule<'', []> & { meta: { hasSuggestions?: boolean; docs: { recommendedConfig?: 'error' | 'warn' } } } -type RecommendedConfig = - | TSESLint.RuleMetaDataDocs['recommended'] - | [TSESLint.RuleMetaDataDocs['recommended'], ...TOptions] - // These 2 types are copied from @typescript-eslint/experimental-utils' CreateRuleMeta // and modified to our needs -export type StorybookRuleMetaDocs = Omit< - TSESLint.RuleMetaDataDocs, - 'url' -> & { +export type StorybookRuleMetaDocs = Omit & { /** * Whether or not this rule should be excluded from linter config */ @@ -24,20 +17,21 @@ export type StorybookRuleMetaDocs = Omit< */ categories?: CategoryId[] } -export type StorybookRuleMeta< - TMessageIds extends string, - TOptions extends readonly unknown[] -> = Omit, 'docs'> & { - docs: StorybookRuleMetaDocs + +export type StorybookRuleMeta = Omit< + TSESLint.RuleMetaData, + 'docs' +> & { + docs: StorybookRuleMetaDocs } -// const docs: StorybookRuleMetaDocs = { -// recommended: true, +// Comment out for testing purposes: +// const docs: StorybookRuleMetaDocs = { // description: 'bla', // recommended: 'error', // } -// const meta: StorybookRuleMeta<'someId', any> = { +// const meta: StorybookRuleMeta<'someId'> = { // messages: { // someId: 'yea', // }, diff --git a/lib/utils/create-storybook-rule.ts b/lib/utils/create-storybook-rule.ts index fa0adc0..3c20e84 100644 --- a/lib/utils/create-storybook-rule.ts +++ b/lib/utils/create-storybook-rule.ts @@ -13,7 +13,7 @@ export function createStorybookRule< ...remainingConfig }: Readonly<{ name: string - meta: StorybookRuleMeta + meta: StorybookRuleMeta defaultOptions: Readonly create: ( context: Readonly>, diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 951ada5..efb7225 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -57,12 +57,11 @@ export const getDescriptor = ( if (!['StringLiteral', 'Literal'].includes(t.type)) { throw new Error(`Unexpected descriptor element: ${t.type}`) } - // @ts-ignore + // @ts-expect-error TODO: t should be only StringLiteral or Literal, and the type is not resolving correctly return t.value }) case 'Literal': - // TODO: Investigation needed. Type systems says, that "RegExpLiteral" does not exist - // @ts-ignore + // @ts-expect-error TODO: Investigation needed. Type systems says, that "RegExpLiteral" does not exist case 'RegExpLiteral': // @ts-ignore return property.value.value diff --git a/package.json b/package.json index 2f7ddd4..3693736 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@types/jest": "^27.0.2", "@types/node": "^16.11.6", "@types/requireindex": "^1.2.0", + "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.3.0", "auto": "^10.32.2", "eslint": "^7.1.0", diff --git a/tools/update-rules-list.ts b/tools/update-rules-list.ts index ef16fe0..66cb118 100644 --- a/tools/update-rules-list.ts +++ b/tools/update-rules-list.ts @@ -30,7 +30,9 @@ const rulesList: TRulesList[] = Object.entries(rules) createRuleLink(rule.name), rule.meta.docs.description, rule.meta.fixable ? emojiKey.fixable : '', - `
    ${rule.meta.docs.categories.map((c) => `
  • ${c}
  • `).join('')}
`, + rule.meta.docs.categories + ? `
    ${rule.meta.docs.categories.map((c) => `
  • ${c}
  • `).join('')}
` + : '', ] }) diff --git a/tools/utils/categories.ts b/tools/utils/categories.ts index 24cf203..775e74b 100644 --- a/tools/utils/categories.ts +++ b/tools/utils/categories.ts @@ -18,7 +18,7 @@ const categoriesConfig: TCategoriesConfig = { }, } -export const categoryIds = Object.keys(categoriesConfig) +export const categoryIds = Object.keys(categoriesConfig) as CategoryId[] for (const categoryId of categoryIds) { categoriesConfig[categoryId].rules = [] @@ -26,19 +26,19 @@ for (const categoryId of categoryIds) { for (const rule of rules) { const ruleCategories = rule.meta.docs.categories // Throw if rule does not have a category - if (!ruleCategories.length) { + if (!ruleCategories?.length) { throw new Error(`Rule "${rule.ruleId}" does not have any category.`) } if (ruleCategories.includes(categoryId)) { - categoriesConfig[categoryId].rules.push(rule) + categoriesConfig[categoryId].rules?.push(rule) } } } export const categories = categoryIds .map((categoryId) => { - if (!categoriesConfig[categoryId].rules.length) { + if (!categoriesConfig[categoryId].rules?.length) { throw new Error( `Category "${categoryId}" has no rules. Make sure that at least one rule is linked to this category.` ) @@ -47,11 +47,11 @@ export const categories = categoryIds return { categoryId, title: categoriesConfig[categoryId], - rules: categoriesConfig[categoryId].rules.filter((rule) => !rule.meta.deprecated), + rules: categoriesConfig[categoryId].rules?.filter((rule) => !rule.meta.deprecated) ?? [], } }) .filter((category) => { - return category.rules.length >= 1 + return (category.rules?.length ?? 0) >= 1 }) export type TCategory = typeof categories extends (infer TCat)[] ? TCat : never diff --git a/yarn.lock b/yarn.lock index f35e80d..17441b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1655,6 +1655,21 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz#50cc5085578a7fa22cd46a0806c2e5eae858af02" + integrity sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg== + dependencies: + "@typescript-eslint/scope-manager" "5.47.1" + "@typescript-eslint/type-utils" "5.47.1" + "@typescript-eslint/utils" "5.47.1" + debug "^4.3.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/parser@^5.3.0": version "5.45.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.45.0.tgz#b18a5f6b3cf1c2b3e399e9d2df4be40d6b0ddd0e" @@ -1673,11 +1688,34 @@ "@typescript-eslint/types" "5.45.0" "@typescript-eslint/visitor-keys" "5.45.0" +"@typescript-eslint/scope-manager@5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz#0d302b3c2f20ab24e4787bf3f5a0d8c449b823bd" + integrity sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw== + dependencies: + "@typescript-eslint/types" "5.47.1" + "@typescript-eslint/visitor-keys" "5.47.1" + +"@typescript-eslint/type-utils@5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz#aee13314f840ab336c1adb49a300856fd16d04ce" + integrity sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w== + dependencies: + "@typescript-eslint/typescript-estree" "5.47.1" + "@typescript-eslint/utils" "5.47.1" + debug "^4.3.4" + tsutils "^3.21.0" + "@typescript-eslint/types@5.45.0": version "5.45.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.45.0.tgz#794760b9037ee4154c09549ef5a96599621109c5" integrity sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA== +"@typescript-eslint/types@5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.47.1.tgz#459f07428aec5a8c4113706293c2ae876741ac8e" + integrity sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A== + "@typescript-eslint/typescript-estree@5.45.0": version "5.45.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz#f70a0d646d7f38c0dfd6936a5e171a77f1e5291d" @@ -1691,6 +1729,33 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz#b9d8441308aca53df7f69b2c67a887b82c9ed418" + integrity sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA== + dependencies: + "@typescript-eslint/types" "5.47.1" + "@typescript-eslint/visitor-keys" "5.47.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.47.1.tgz#595f25ac06e9ee28c339fd43c709402820b13d7b" + integrity sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.47.1" + "@typescript-eslint/types" "5.47.1" + "@typescript-eslint/typescript-estree" "5.47.1" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" + "@typescript-eslint/utils@^5.45.0": version "5.45.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.45.0.tgz#9cca2996eee1b8615485a6918a5c763629c7acf5" @@ -1713,6 +1778,14 @@ "@typescript-eslint/types" "5.45.0" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.47.1": + version "5.47.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz#d35c2da544dbb685db9c5b5b85adac0a1d74d1f2" + integrity sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig== + dependencies: + "@typescript-eslint/types" "5.47.1" + eslint-visitor-keys "^3.3.0" + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -4657,6 +4730,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5167,7 +5245,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^3.0.0, regexpp@^3.1.0: +regexpp@^3.0.0, regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==