UNPKG

@storybook/codemod

Version:

A collection of codemod scripts written with JSCodeshift

279 lines (275 loc) • 11.3 kB
import CJS_COMPAT_NODE_URL_9smq7eu765a from 'node:url'; import CJS_COMPAT_NODE_PATH_9smq7eu765a from 'node:path'; import CJS_COMPAT_NODE_MODULE_9smq7eu765a from "node:module"; var __filename = CJS_COMPAT_NODE_URL_9smq7eu765a.fileURLToPath(import.meta.url); var __dirname = CJS_COMPAT_NODE_PATH_9smq7eu765a.dirname(__filename); var require = CJS_COMPAT_NODE_MODULE_9smq7eu765a.createRequire(import.meta.url); // ------------------------------------------------------------ // end of CJS compatibility banner, injected by Storybook's esbuild configuration // ------------------------------------------------------------ import { upgradeDeprecatedTypes } from "../_node-chunks/chunk-I3TVWROC.js"; import { __name } from "../_node-chunks/chunk-HVEWKEW6.js"; // src/transforms/csf-2-to-3.ts import { core as babel, types as t } from "storybook/internal/babel"; import { loadCsf, printCsf } from "storybook/internal/csf-tools"; import { logger } from "storybook/internal/node-logger"; import prettier from "prettier"; import invariant from "tiny-invariant"; var { isIdentifier, isTSTypeAnnotation, isTSTypeReference } = t; var renameAnnotation = /* @__PURE__ */ __name((annotation) => { return annotation === "storyName" ? "name" : annotation; }, "renameAnnotation"); var getTemplateBindVariable = /* @__PURE__ */ __name((init) => t.isCallExpression(init) && t.isMemberExpression(init.callee) && t.isIdentifier(init.callee.object) && t.isIdentifier(init.callee.property) && init.callee.property.name === "bind" && (init.arguments.length === 0 || init.arguments.length === 1 && t.isObjectExpression(init.arguments[0]) && init.arguments[0].properties.length === 0) ? init.callee.object.name : null, "getTemplateBindVariable"); var isStoryAnnotation = /* @__PURE__ */ __name((stmt, objectExports) => t.isExpressionStatement(stmt) && t.isAssignmentExpression(stmt.expression) && t.isMemberExpression(stmt.expression.left) && t.isIdentifier(stmt.expression.left.object) && objectExports[stmt.expression.left.object.name], "isStoryAnnotation"); var getNewExport = /* @__PURE__ */ __name((stmt, objectExports) => { if (t.isExportNamedDeclaration(stmt) && t.isVariableDeclaration(stmt.declaration) && stmt.declaration.declarations.length === 1) { const decl = stmt.declaration.declarations[0]; if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) { return objectExports[decl.id.name]; } } return null; }, "getNewExport"); var isReactGlobalRenderFn = /* @__PURE__ */ __name((csf, storyFn) => { if (csf._meta?.component && t.isArrowFunctionExpression(storyFn) && storyFn.params.length === 1 && t.isJSXElement(storyFn.body)) { const { openingElement } = storyFn.body; if (openingElement.selfClosing && t.isJSXIdentifier(openingElement.name) && openingElement.attributes.length === 1) { const attr = openingElement.attributes[0]; const param = storyFn.params[0]; if (t.isJSXSpreadAttribute(attr) && t.isIdentifier(attr.argument) && t.isIdentifier(param) && param.name === attr.argument.name && csf._meta.component === openingElement.name.name) { return true; } } } return false; }, "isReactGlobalRenderFn"); var isSimpleCSFStory = /* @__PURE__ */ __name((init, annotations) => annotations.length === 0 && t.isArrowFunctionExpression(init) && init.params.length === 0, "isSimpleCSFStory"); function removeUnusedTemplates(csf) { Object.entries(csf._templates).forEach(([template, templateExpression]) => { const references = []; babel.traverse(csf._ast, { Identifier: /* @__PURE__ */ __name((path) => { if (path.node.name === template) { references.push(path); } }, "Identifier") }); if (references.length === 1) { const reference = references[0]; if (reference.parentPath?.isVariableDeclarator() && reference.parentPath.node.init === templateExpression) { reference.parentPath.remove(); } } }); } __name(removeUnusedTemplates, "removeUnusedTemplates"); async function transform(info, api, options) { const makeTitle = /* @__PURE__ */ __name((userTitle) => { return userTitle || "FIXME"; }, "makeTitle"); const csf = loadCsf(info.source, { makeTitle }); try { csf.parse(); } catch (err) { logger.log(`Error ${err}, skipping`); return info.source; } const file = new babel.File( { filename: info.path }, { code: info.source, ast: csf._ast } ); const importHelper = new StorybookImportHelper(file, info); const objectExports = {}; Object.entries(csf._storyExports).forEach(([key, decl]) => { const annotations = Object.entries(csf._storyAnnotations[key]).map(([annotation, val]) => { return t.objectProperty(t.identifier(renameAnnotation(annotation)), val); }); if (t.isVariableDeclarator(decl)) { const { init, id } = decl; invariant(init, "Inital value should be declared"); const template = getTemplateBindVariable(init); if (!t.isArrowFunctionExpression(init) && !template) { return; } if (isSimpleCSFStory(init, annotations)) { objectExports[key] = t.exportNamedDeclaration( t.variableDeclaration("const", [ t.variableDeclarator(importHelper.updateTypeTo(id, "StoryFn"), init) ]) ); return; } const storyFn = template ? t.identifier(template) : init; const renderAnnotation = isReactGlobalRenderFn( csf, template ? csf._templates[template] : storyFn ) ? [] : [t.objectProperty(t.identifier("render"), storyFn)]; objectExports[key] = t.exportNamedDeclaration( t.variableDeclaration("const", [ t.variableDeclarator( importHelper.updateTypeTo(id, "StoryObj"), t.objectExpression([...renderAnnotation, ...annotations]) ) ]) ); } }); csf._ast.program.body = csf._ast.program.body.reduce((acc, stmt) => { const statement = stmt; if (isStoryAnnotation(statement, objectExports)) { return acc; } const newExport = getNewExport(statement, objectExports); if (newExport) { acc.push(newExport); return acc; } acc.push(statement); return acc; }, []); upgradeDeprecatedTypes(file); importHelper.removeDeprecatedStoryImport(); removeUnusedTemplates(csf); let output = printCsf(csf).code; try { output = await prettier.format(output, { ...await prettier.resolveConfig(info.path), filepath: info.path }); } catch (e) { logger.log(`Failed applying prettier to ${info.path}.`); } return output; } __name(transform, "transform"); var StorybookImportHelper = class { constructor(file, info) { this.getAllSbImportDeclarations = /* @__PURE__ */ __name((file) => { const found = []; file.path.traverse({ ImportDeclaration: /* @__PURE__ */ __name((path) => { const source = path.node.source.value; if (source.startsWith("@storybook/csf") || !source.startsWith("@storybook")) { return; } const isRendererImport = path.get("specifiers").some((specifier) => { if (specifier.isImportNamespaceSpecifier()) { throw new Error( `This codemod does not support namespace imports for a ${path.node.source.value} package. Replace the namespace import with named imports and try again.` ); } if (!specifier.isImportSpecifier()) { return false; } const imported = specifier.get("imported"); if (Array.isArray(imported)) { return imported.some((importedSpecifier) => { if (!importedSpecifier.isIdentifier()) { return false; } return [ "Story", "StoryFn", "StoryObj", "Meta", "ComponentStory", "ComponentStoryFn", "ComponentStoryObj", "ComponentMeta" ].includes(importedSpecifier.node.name); }); } if (!imported.isIdentifier()) { return false; } return [ "Story", "StoryFn", "StoryObj", "Meta", "ComponentStory", "ComponentStoryFn", "ComponentStoryObj", "ComponentMeta" ].includes(imported.node.name); }); if (isRendererImport) { found.push(path); } }, "ImportDeclaration") }); return found; }, "getAllSbImportDeclarations"); this.getOrAddImport = /* @__PURE__ */ __name((type) => { const sbImport = this.sbImportDeclarations.find((path) => path.node.importKind === "type") ?? this.sbImportDeclarations[0]; if (sbImport == null) { return void 0; } const specifiers = sbImport.get("specifiers"); const importSpecifier = specifiers.find((specifier) => { if (!specifier.isImportSpecifier()) { return false; } const imported = specifier.get("imported"); if (!imported.isIdentifier()) { return false; } return imported.node.name === type; }); if (importSpecifier) { return importSpecifier.node.local.name; } specifiers[0].insertBefore(t.importSpecifier(t.identifier(type), t.identifier(type))); return type; }, "getOrAddImport"); this.removeDeprecatedStoryImport = /* @__PURE__ */ __name(() => { const specifiers = this.sbImportDeclarations.flatMap((it) => it.get("specifiers")); const storyImports = specifiers.filter((specifier) => { if (!specifier.isImportSpecifier()) { return false; } const imported = specifier.get("imported"); if (!imported.isIdentifier()) { return false; } return imported.node.name === "Story"; }); storyImports.forEach((path) => path.remove()); }, "removeDeprecatedStoryImport"); this.getAllLocalImports = /* @__PURE__ */ __name(() => { return this.sbImportDeclarations.flatMap((it) => it.get("specifiers")).map((it) => it.node.local.name); }, "getAllLocalImports"); this.updateTypeTo = /* @__PURE__ */ __name((id, type) => { if (isIdentifier(id) && isTSTypeAnnotation(id.typeAnnotation) && isTSTypeReference(id.typeAnnotation.typeAnnotation) && isIdentifier(id.typeAnnotation.typeAnnotation.typeName)) { const { name } = id.typeAnnotation.typeAnnotation.typeName; if (this.getAllLocalImports().includes(name)) { const localTypeImport = this.getOrAddImport(type); return { ...id, typeAnnotation: t.tsTypeAnnotation( t.tsTypeReference( t.identifier(localTypeImport ?? ""), id.typeAnnotation.typeAnnotation.typeParameters ) ) }; } } return id; }, "updateTypeTo"); this.sbImportDeclarations = this.getAllSbImportDeclarations(file); } static { __name(this, "StorybookImportHelper"); } }; var parser = "tsx"; export { transform as default, parser };