123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- "use strict";
- /**
- * @fileoverview This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name.
- * @author Andre "andrelas1" Santos
- */
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- const fs_1 = require("fs");
- const ts_dedent_1 = __importDefault(require("ts-dedent"));
- const path_1 = require("path");
- const create_storybook_rule_1 = require("../utils/create-storybook-rule");
- const constants_1 = require("../utils/constants");
- const ast_1 = require("../utils/ast");
- const utils_1 = require("../utils");
- module.exports = (0, create_storybook_rule_1.createStorybookRule)({
- name: 'no-uninstalled-addons',
- defaultOptions: [
- {
- packageJsonLocation: '',
- ignore: [],
- },
- ],
- meta: {
- type: 'problem',
- docs: {
- description: 'This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name.',
- categories: [constants_1.CategoryId.RECOMMENDED],
- recommended: 'error', // or 'error'
- },
- messages: {
- addonIsNotInstalled: `The {{ addonName }} is not installed in {{packageJsonPath}}. Did you forget to install it or is your package.json in a different location?`,
- },
- schema: [
- {
- type: 'object',
- properties: {
- packageJsonLocation: {
- type: 'string',
- },
- ignore: {
- type: 'array',
- items: {
- type: 'string',
- },
- },
- },
- },
- ],
- },
- create(context) {
- // variables should be defined here
- const { packageJsonLocation, ignore } = context.options.reduce((acc, val) => {
- return {
- packageJsonLocation: val['packageJsonLocation'] || acc.packageJsonLocation,
- ignore: val['ignore'] || acc.ignore,
- };
- }, { packageJsonLocation: '', ignore: [] });
- //----------------------------------------------------------------------
- // Helpers
- //----------------------------------------------------------------------
- // this will not only exclude the nullables but it will also exclude the type undefined from them, so that TS does not complain
- function excludeNullable(item) {
- return !!item;
- }
- const mergeDepsWithDevDeps = (packageJson) => {
- const deps = Object.keys(packageJson.dependencies || {});
- const devDeps = Object.keys(packageJson.devDependencies || {});
- return [...deps, ...devDeps];
- };
- const isAddonInstalled = (addon, installedAddons) => {
- // cleanup /register or /preset + file extension from registered addon
- const addonName = addon
- .replace(/\.[mc]?js$/, '')
- .replace(/\/register$/, '')
- .replace(/\/preset$/, '');
- return installedAddons.includes(addonName);
- };
- const filterLocalAddons = (addon) => {
- const isLocalAddon = (addon) => addon.startsWith('.') ||
- addon.startsWith('/') ||
- // for local Windows files e.g. (C: F: D:)
- /\w:.*/.test(addon) ||
- addon.startsWith('\\');
- return !isLocalAddon(addon);
- };
- const areThereAddonsNotInstalled = (addons, installedSbAddons) => {
- const result = addons
- // remove local addons (e.g. ./my-addon/register.js)
- .filter(filterLocalAddons)
- .filter((addon) => !isAddonInstalled(addon, installedSbAddons) && !ignore.includes(addon))
- .map((addon) => ({ name: addon }));
- return result.length ? result : false;
- };
- const getPackageJson = (path) => {
- const packageJson = {
- devDependencies: {},
- dependencies: {},
- };
- try {
- const file = (0, fs_1.readFileSync)(path, 'utf8');
- const parsedFile = JSON.parse(file);
- packageJson.dependencies = parsedFile.dependencies || {};
- packageJson.devDependencies = parsedFile.devDependencies || {};
- }
- catch (e) {
- throw new Error((0, ts_dedent_1.default) `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.
- Read more at: https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-uninstalled-addons.md
- `);
- }
- return packageJson;
- };
- const extractAllAddonsFromTheStorybookConfig = (addonsExpression) => {
- if (addonsExpression === null || addonsExpression === void 0 ? void 0 : addonsExpression.elements) {
- // extract all nodes taht are a string inside the addons array
- const nodesWithAddons = addonsExpression.elements
- .map((elem) => ((0, ast_1.isLiteral)(elem) ? { value: elem.value, node: elem } : undefined))
- .filter(excludeNullable);
- const listOfAddonsInString = nodesWithAddons.map((elem) => elem.value);
- // extract all nodes that are an object inside the addons array
- const nodesWithAddonsInObj = addonsExpression.elements
- .map((elem) => ((0, ast_1.isObjectExpression)(elem) ? elem : { properties: [] }))
- .map((elem) => {
- const property = elem.properties.find((prop) => (0, ast_1.isProperty)(prop) && (0, ast_1.isIdentifier)(prop.key) && prop.key.name === 'name');
- return (0, ast_1.isLiteral)(property === null || property === void 0 ? void 0 : property.value)
- ? { value: property.value.value, node: property.value }
- : undefined;
- })
- .filter(excludeNullable);
- const listOfAddonsInObj = nodesWithAddonsInObj.map((elem) => elem.value);
- const listOfAddons = [...listOfAddonsInString, ...listOfAddonsInObj];
- const listOfAddonElements = [...nodesWithAddons, ...nodesWithAddonsInObj];
- return { listOfAddons, listOfAddonElements };
- }
- return { listOfAddons: [], listOfAddonElements: [] };
- };
- function reportUninstalledAddons(addonsProp) {
- const packageJsonPath = (0, path_1.resolve)(packageJsonLocation || `./package.json`);
- let packageJsonObject;
- try {
- packageJsonObject = getPackageJson(packageJsonPath);
- }
- catch (e) {
- // if we cannot find the package.json, we cannot check if the addons are installed
- throw new Error(e);
- }
- const depsAndDevDeps = mergeDepsWithDevDeps(packageJsonObject);
- const { listOfAddons, listOfAddonElements } = extractAllAddonsFromTheStorybookConfig(addonsProp);
- const result = areThereAddonsNotInstalled(listOfAddons, depsAndDevDeps);
- if (result) {
- const elemsWithErrors = listOfAddonElements.filter((elem) => !!result.find((addon) => addon.name === elem.value));
- const rootDir = process.cwd().split(path_1.sep).pop();
- const packageJsonPath = `${rootDir}${path_1.sep}${(0, path_1.relative)(process.cwd(), packageJsonLocation)}`;
- elemsWithErrors.forEach((elem) => {
- context.report({
- node: elem.node,
- messageId: 'addonIsNotInstalled',
- data: {
- addonName: elem.value,
- packageJsonPath,
- },
- });
- });
- }
- }
- function findAddonsPropAndReport(node) {
- const addonsProp = node.properties.find((prop) => (0, ast_1.isProperty)(prop) && (0, ast_1.isIdentifier)(prop.key) && prop.key.name === 'addons');
- if ((addonsProp === null || addonsProp === void 0 ? void 0 : addonsProp.value) && (0, ast_1.isArrayExpression)(addonsProp.value)) {
- reportUninstalledAddons(addonsProp.value);
- }
- }
- //----------------------------------------------------------------------
- // Public
- //----------------------------------------------------------------------
- return {
- AssignmentExpression: function (node) {
- if ((0, ast_1.isObjectExpression)(node.right)) {
- findAddonsPropAndReport(node.right);
- }
- },
- ExportDefaultDeclaration: function (node) {
- const meta = (0, utils_1.getMetaObjectExpression)(node, context);
- if (!meta)
- return null;
- findAddonsPropAndReport(meta);
- },
- ExportNamedDeclaration: function (node) {
- const addonsProp = (0, ast_1.isVariableDeclaration)(node.declaration) &&
- node.declaration.declarations.find((decl) => (0, ast_1.isVariableDeclarator)(decl) && (0, ast_1.isIdentifier)(decl.id) && decl.id.name === 'addons');
- if (addonsProp && (0, ast_1.isArrayExpression)(addonsProp.init)) {
- reportUninstalledAddons(addonsProp.init);
- }
- },
- };
- },
- });
|