diff --git a/lib/rules/no-uninstalled-addons.ts b/lib/rules/no-uninstalled-addons.ts index afb8c67..c723c5d 100644 --- a/lib/rules/no-uninstalled-addons.ts +++ b/lib/rules/no-uninstalled-addons.ts @@ -17,6 +17,7 @@ import { isLiteral, isVariableDeclarator, isVariableDeclaration, + isTSSatisfiesExpression, } from '../utils/ast' import { TSESTree } from '@typescript-eslint/utils' @@ -228,6 +229,17 @@ export = createStorybookRule({ } } + function findAddonsPropAndReport(node: TSESTree.ObjectExpression) { + const addonsProp = node.properties.find( + (prop): prop is TSESTree.Property => + isProperty(prop) && isIdentifier(prop.key) && prop.key.name === 'addons' + ) + + if (addonsProp?.value && isArrayExpression(addonsProp.value)) { + reportUninstalledAddons(addonsProp.value) + } + } + //---------------------------------------------------------------------- // Public //---------------------------------------------------------------------- @@ -235,26 +247,37 @@ export = createStorybookRule({ return { AssignmentExpression: function (node) { if (isObjectExpression(node.right)) { - const addonsProp = node.right.properties.find( - (prop): prop is TSESTree.Property => - isProperty(prop) && isIdentifier(prop.key) && prop.key.name === 'addons' - ) - - if (addonsProp && addonsProp.value && isArrayExpression(addonsProp.value)) { - reportUninstalledAddons(addonsProp.value) - } + findAddonsPropAndReport(node.right) } }, ExportDefaultDeclaration: function (node) { + if (isIdentifier(node.declaration)) { + const variable = context + .getScope() + .variables.find((v) => + node.declaration.type === 'Identifier' ? v.name === node.declaration.name : false + ) + + if ( + variable && + variable.defs[0] && + isVariableDeclarator(variable.defs[0].node) && + isObjectExpression(variable.defs[0].node.init) + ) { + const objectNode = variable.defs[0].node.init + findAddonsPropAndReport(objectNode) + } + } + if (isObjectExpression(node.declaration)) { - const addonsProp = node.declaration.properties.find( - (prop): prop is TSESTree.Property => - isProperty(prop) && isIdentifier(prop.key) && prop.key.name === 'addons' - ) + findAddonsPropAndReport(node.declaration) + } - if (addonsProp && addonsProp.value && isArrayExpression(addonsProp.value)) { - reportUninstalledAddons(addonsProp.value) - } + if ( + isTSSatisfiesExpression(node.declaration) && + isObjectExpression(node.declaration.expression) + ) { + findAddonsPropAndReport(node.declaration.expression) } }, ExportNamedDeclaration: function (node) { diff --git a/tests/lib/rules/no-uninstalled-addons.test.ts b/tests/lib/rules/no-uninstalled-addons.test.ts index 5b95d83..79742ad 100644 --- a/tests/lib/rules/no-uninstalled-addons.test.ts +++ b/tests/lib/rules/no-uninstalled-addons.test.ts @@ -44,6 +44,27 @@ ruleTester.run('no-uninstalled-addons', rule, { "@storybook/preset-create-react-app" ] } + `, + ` + export default { + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "@storybook/preset-create-react-app" + ] + } satisfies StorybookConfig + `, + ` + const config: StorybookConfig = { + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "@storybook/preset-create-react-app" + ] + } + export default config `, ` export const addons = [ @@ -291,6 +312,69 @@ ruleTester.run('no-uninstalled-addons', rule, { }, ], }, + { + code: ` + export default { + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "addon-withut-the-prefix", + "@storybook/addon-esentials", + ] + } satisfies StorybookConfig + `, + errors: [ + { + messageId: 'addonIsNotInstalled', // comes from the rule file + type: AST_NODE_TYPES.Literal, + data: { + addonName: 'addon-withut-the-prefix', + packageJsonPath: `eslint-plugin-storybook${sep}`, + }, + }, + { + messageId: 'addonIsNotInstalled', // comes from the rule file + type: AST_NODE_TYPES.Literal, + data: { + addonName: '@storybook/addon-esentials', + packageJsonPath: `eslint-plugin-storybook${sep}`, + }, + }, + ], + }, + { + code: ` + const config: StorybookConfig = { + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "addon-withut-the-prefix", + "@storybook/addon-esentials", + ] + } + export default config + `, + errors: [ + { + messageId: 'addonIsNotInstalled', // comes from the rule file + type: AST_NODE_TYPES.Literal, + data: { + addonName: 'addon-withut-the-prefix', + packageJsonPath: `eslint-plugin-storybook${sep}`, + }, + }, + { + messageId: 'addonIsNotInstalled', // comes from the rule file + type: AST_NODE_TYPES.Literal, + data: { + addonName: '@storybook/addon-esentials', + packageJsonPath: `eslint-plugin-storybook${sep}`, + }, + }, + ], + }, { code: ` export const addons = [