@storybook/codemod
Version:
A collection of codemod scripts written with JSCodeshift
279 lines (275 loc) • 11.3 kB
JavaScript
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
};