@storybook/codemod 7.0.0-rc.0 → 7.0.0-rc.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var csf_2_to_3_exports={};__export(csf_2_to_3_exports,{default:()=>transform,parser:()=>parser});module.exports=__toCommonJS(csf_2_to_3_exports);var import_prettier2=__toESM(require("prettier")),t2=__toESM(require("@babel/types")),import_types=require("@babel/types"),import_csf_tools2=require("@storybook/csf-tools"),babel2=__toESM(require("@babel/core")),recast2=__toESM(require("recast"));var import_prettier=__toESM(require("prettier")),babel=__toESM(require("@babel/core")),import_csf_tools=require("@storybook/csf-tools"),recast=__toESM(require("recast")),t=__toESM(require("@babel/types"));var deprecatedTypes=["ComponentStory","ComponentStoryFn","ComponentStoryObj","ComponentMeta","Story"];function migrateType(oldType){return oldType==="Story"||oldType==="ComponentStory"?"StoryFn":oldType.replace("Component","")}function upgradeDeprecatedTypes(file){let importedNamespaces=new Set,typeReferencesToUpdate=new Set,existingImports=[];file.path.traverse({ImportDeclaration:path=>{existingImports.push(...path.get("specifiers").map(specifier=>({name:specifier.node.local.name,isAlias:!(specifier.isImportSpecifier()&&t.isIdentifier(specifier.node.imported)&&specifier.node.local.name===specifier.node.imported.name),path:specifier}))),path.node.source.value.startsWith("@storybook")&&path.get("specifiers").forEach(specifier=>{if(specifier.isImportNamespaceSpecifier()&&importedNamespaces.add(specifier.node.local.name),!specifier.isImportSpecifier())return;let imported=specifier.get("imported");if(imported.isIdentifier()&&deprecatedTypes.includes(imported.node.name)){imported.node.name===specifier.node.local.name&&typeReferencesToUpdate.add(specifier.node.local.name);let newType=migrateType(imported.node.name);if(!existingImports.some(it=>it.name===newType))imported.replaceWith(t.identifier(newType)),existingImports.push({name:newType,isAlias:!1,path:specifier});else{let existingImport=existingImports.find(it=>it.name===newType&&it.isAlias);if(existingImport)throw existingImport.path.buildCodeFrameError(`This codemod does not support local imports that are called the same as a storybook import.
2
- Rename this local import and try again.`);specifier.remove()}}})}}),file.path.traverse({TSTypeReference:path=>{let typeName=path.get("typeName");if(typeName.isIdentifier())typeReferencesToUpdate.has(typeName.node.name)&&typeName.replaceWith(t.identifier(migrateType(typeName.node.name)));else if(typeName.isTSQualifiedName()){let namespace=typeName.get("left");if(namespace.isIdentifier()&&importedNamespaces.has(namespace.node.name)){let right=typeName.get("right");deprecatedTypes.includes(right.node.name)&&right.replaceWith(t.identifier(migrateType(right.node.name)))}}}})}var logger=console,renameAnnotation=annotation=>annotation==="storyName"?"name":annotation,getTemplateBindVariable=init=>t2.isCallExpression(init)&&t2.isMemberExpression(init.callee)&&t2.isIdentifier(init.callee.object)&&t2.isIdentifier(init.callee.property)&&init.callee.property.name==="bind"&&(init.arguments.length===0||init.arguments.length===1&&t2.isObjectExpression(init.arguments[0])&&init.arguments[0].properties.length===0)?init.callee.object.name:null,isStoryAnnotation=(stmt,objectExports)=>t2.isExpressionStatement(stmt)&&t2.isAssignmentExpression(stmt.expression)&&t2.isMemberExpression(stmt.expression.left)&&t2.isIdentifier(stmt.expression.left.object)&&objectExports[stmt.expression.left.object.name],isTemplateDeclaration=(stmt,templates)=>t2.isVariableDeclaration(stmt)&&stmt.declarations.length===1&&t2.isIdentifier(stmt.declarations[0].id)&&templates[stmt.declarations[0].id.name],getNewExport=(stmt,objectExports)=>{if(t2.isExportNamedDeclaration(stmt)&&t2.isVariableDeclaration(stmt.declaration)&&stmt.declaration.declarations.length===1){let decl=stmt.declaration.declarations[0];if(t2.isVariableDeclarator(decl)&&t2.isIdentifier(decl.id))return objectExports[decl.id.name]}return null},isReactGlobalRenderFn=(csf,storyFn)=>{var _a;if((_a=csf._meta)!=null&&_a.component&&t2.isArrowFunctionExpression(storyFn)&&storyFn.params.length===1&&t2.isJSXElement(storyFn.body)){let{openingElement}=storyFn.body;if(openingElement.selfClosing&&t2.isJSXIdentifier(openingElement.name)&&openingElement.attributes.length===1){let attr=openingElement.attributes[0],param=storyFn.params[0];if(t2.isJSXSpreadAttribute(attr)&&t2.isIdentifier(attr.argument)&&t2.isIdentifier(param)&&param.name===attr.argument.name&&csf._meta.component===openingElement.name.name)return!0}}return!1},isSimpleCSFStory=(init,annotations)=>annotations.length===0&&t2.isArrowFunctionExpression(init)&&init.params.length===0;function transform(info,api,options){let makeTitle=userTitle=>userTitle||"FIXME",csf=(0,import_csf_tools2.loadCsf)(info.source,{makeTitle});try{csf.parse()}catch(err){return logger.log(`Error ${err}, skipping`),info.source}let file=new babel2.File({filename:info.path},{code:info.source,ast:csf._ast}),importHelper=new StorybookImportHelper(file,info),objectExports={};Object.entries(csf._storyExports).forEach(([key,decl])=>{let annotations=Object.entries(csf._storyAnnotations[key]).map(([annotation,val])=>t2.objectProperty(t2.identifier(renameAnnotation(annotation)),val));if(t2.isVariableDeclarator(decl)){let{init,id}=decl,template=getTemplateBindVariable(init);if(!t2.isArrowFunctionExpression(init)&&!template)return;if(isSimpleCSFStory(init,annotations)){objectExports[key]=t2.exportNamedDeclaration(t2.variableDeclaration("const",[t2.variableDeclarator(importHelper.updateTypeTo(id,"StoryFn"),init)]));return}let storyFn=template&&csf._templates[template];storyFn||(storyFn=init);let renderAnnotation=isReactGlobalRenderFn(csf,storyFn)?[]:[t2.objectProperty(t2.identifier("render"),storyFn)];objectExports[key]=t2.exportNamedDeclaration(t2.variableDeclaration("const",[t2.variableDeclarator(importHelper.updateTypeTo(id,"StoryObj"),t2.objectExpression([...renderAnnotation,...annotations]))]))}}),importHelper.removeDeprecatedStoryImport(),csf._ast.program.body=csf._ast.program.body.reduce((acc,stmt)=>{let statement=stmt;if(isStoryAnnotation(statement,objectExports)||isTemplateDeclaration(statement,csf._templates))return acc;let newExport=getNewExport(statement,objectExports);return newExport?(acc.push(newExport),acc):(acc.push(statement),acc)},[]),upgradeDeprecatedTypes(file);let output=recast2.print(csf._ast,{}).code;try{let prettierConfig=import_prettier2.default.resolveConfig.sync(".",{editorconfig:!0})||{printWidth:100,tabWidth:2,bracketSpacing:!0,trailingComma:"es5",singleQuote:!0};output=import_prettier2.default.format(output,{...prettierConfig,filepath:info.path})}catch{logger.log(`Failed applying prettier to ${info.path}.`)}return output}var StorybookImportHelper=class{constructor(file,info){this.getAllSbImportDeclarations=file=>{let found=[];return file.path.traverse({ImportDeclaration:path=>{let source=path.node.source.value;if(source.startsWith("@storybook/csf")||!source.startsWith("@storybook"))return;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.
2
+ Rename this local import and try again.`);specifier.remove()}}})}}),file.path.traverse({TSTypeReference:path=>{let typeName=path.get("typeName");if(typeName.isIdentifier())typeReferencesToUpdate.has(typeName.node.name)&&typeName.replaceWith(t.identifier(migrateType(typeName.node.name)));else if(typeName.isTSQualifiedName()){let namespace=typeName.get("left");if(namespace.isIdentifier()&&importedNamespaces.has(namespace.node.name)){let right=typeName.get("right");deprecatedTypes.includes(right.node.name)&&right.replaceWith(t.identifier(migrateType(right.node.name)))}}}})}var logger=console,renameAnnotation=annotation=>annotation==="storyName"?"name":annotation,getTemplateBindVariable=init=>t2.isCallExpression(init)&&t2.isMemberExpression(init.callee)&&t2.isIdentifier(init.callee.object)&&t2.isIdentifier(init.callee.property)&&init.callee.property.name==="bind"&&(init.arguments.length===0||init.arguments.length===1&&t2.isObjectExpression(init.arguments[0])&&init.arguments[0].properties.length===0)?init.callee.object.name:null,isStoryAnnotation=(stmt,objectExports)=>t2.isExpressionStatement(stmt)&&t2.isAssignmentExpression(stmt.expression)&&t2.isMemberExpression(stmt.expression.left)&&t2.isIdentifier(stmt.expression.left.object)&&objectExports[stmt.expression.left.object.name],getNewExport=(stmt,objectExports)=>{if(t2.isExportNamedDeclaration(stmt)&&t2.isVariableDeclaration(stmt.declaration)&&stmt.declaration.declarations.length===1){let decl=stmt.declaration.declarations[0];if(t2.isVariableDeclarator(decl)&&t2.isIdentifier(decl.id))return objectExports[decl.id.name]}return null},isReactGlobalRenderFn=(csf,storyFn)=>{var _a;if((_a=csf._meta)!=null&&_a.component&&t2.isArrowFunctionExpression(storyFn)&&storyFn.params.length===1&&t2.isJSXElement(storyFn.body)){let{openingElement}=storyFn.body;if(openingElement.selfClosing&&t2.isJSXIdentifier(openingElement.name)&&openingElement.attributes.length===1){let attr=openingElement.attributes[0],param=storyFn.params[0];if(t2.isJSXSpreadAttribute(attr)&&t2.isIdentifier(attr.argument)&&t2.isIdentifier(param)&&param.name===attr.argument.name&&csf._meta.component===openingElement.name.name)return!0}}return!1},isSimpleCSFStory=(init,annotations)=>annotations.length===0&&t2.isArrowFunctionExpression(init)&&init.params.length===0;function removeUnusedTemplates(csf){Object.entries(csf._templates).forEach(([template,templateExpression])=>{let references=[];if(babel2.traverse(csf._ast,{Identifier:path=>{path.node.name===template&&references.push(path)}}),references.length===1){let reference=references[0];reference.parentPath.isVariableDeclarator()&&reference.parentPath.node.init===templateExpression&&reference.parentPath.remove()}})}function transform(info,api,options){let makeTitle=userTitle=>userTitle||"FIXME",csf=(0,import_csf_tools2.loadCsf)(info.source,{makeTitle});try{csf.parse()}catch(err){return logger.log(`Error ${err}, skipping`),info.source}let file=new babel2.File({filename:info.path},{code:info.source,ast:csf._ast}),importHelper=new StorybookImportHelper(file,info),objectExports={};Object.entries(csf._storyExports).forEach(([key,decl])=>{let annotations=Object.entries(csf._storyAnnotations[key]).map(([annotation,val])=>t2.objectProperty(t2.identifier(renameAnnotation(annotation)),val));if(t2.isVariableDeclarator(decl)){let{init,id}=decl,template=getTemplateBindVariable(init);if(!t2.isArrowFunctionExpression(init)&&!template)return;if(isSimpleCSFStory(init,annotations)){objectExports[key]=t2.exportNamedDeclaration(t2.variableDeclaration("const",[t2.variableDeclarator(importHelper.updateTypeTo(id,"StoryFn"),init)]));return}let storyFn=template&&t2.identifier(template);storyFn||(storyFn=init);let renderAnnotation=isReactGlobalRenderFn(csf,template?csf._templates[template]:storyFn)?[]:[t2.objectProperty(t2.identifier("render"),storyFn)];objectExports[key]=t2.exportNamedDeclaration(t2.variableDeclaration("const",[t2.variableDeclarator(importHelper.updateTypeTo(id,"StoryObj"),t2.objectExpression([...renderAnnotation,...annotations]))]))}}),csf._ast.program.body=csf._ast.program.body.reduce((acc,stmt)=>{let statement=stmt;if(isStoryAnnotation(statement,objectExports))return acc;let newExport=getNewExport(statement,objectExports);return newExport?(acc.push(newExport),acc):(acc.push(statement),acc)},[]),upgradeDeprecatedTypes(file),importHelper.removeDeprecatedStoryImport(),removeUnusedTemplates(csf);let output=recast2.print(csf._ast,{}).code;try{let prettierConfig=import_prettier2.default.resolveConfig.sync(".",{editorconfig:!0})||{printWidth:100,tabWidth:2,bracketSpacing:!0,trailingComma:"es5",singleQuote:!0};output=import_prettier2.default.format(output,{...prettierConfig,filepath:info.path})}catch{logger.log(`Failed applying prettier to ${info.path}.`)}return output}var StorybookImportHelper=class{constructor(file,info){this.getAllSbImportDeclarations=file=>{let found=[];return file.path.traverse({ImportDeclaration:path=>{let source=path.node.source.value;if(source.startsWith("@storybook/csf")||!source.startsWith("@storybook"))return;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.
3
3
  Replace the namespace import with named imports and try again.`);if(!specifier.isImportSpecifier())return!1;let imported=specifier.get("imported");return imported.isIdentifier()?["Story","StoryFn","StoryObj","Meta","ComponentStory","ComponentStoryFn","ComponentStoryObj","ComponentMeta"].includes(imported.node.name):!1})&&found.push(path)}}),found};this.getOrAddImport=type=>{let sbImport=this.sbImportDeclarations.find(path=>path.node.importKind==="type")??this.sbImportDeclarations[0];if(sbImport==null)return;let specifiers=sbImport.get("specifiers"),importSpecifier2=specifiers.find(specifier=>{if(!specifier.isImportSpecifier())return!1;let imported=specifier.get("imported");return imported.isIdentifier()?imported.node.name===type:!1});return importSpecifier2?importSpecifier2.node.local.name:(specifiers[0].insertBefore(t2.importSpecifier(t2.identifier(type),t2.identifier(type))),type)};this.removeDeprecatedStoryImport=()=>{this.sbImportDeclarations.flatMap(it=>it.get("specifiers")).filter(specifier=>{if(!specifier.isImportSpecifier())return!1;let imported=specifier.get("imported");return imported.isIdentifier()?imported.node.name==="Story":!1}).forEach(path=>path.remove())};this.getAllLocalImports=()=>this.sbImportDeclarations.flatMap(it=>it.get("specifiers")).map(it=>it.node.local.name);this.updateTypeTo=(id,type)=>{if((0,import_types.isIdentifier)(id)&&(0,import_types.isTSTypeAnnotation)(id.typeAnnotation)&&(0,import_types.isTSTypeReference)(id.typeAnnotation.typeAnnotation)&&(0,import_types.isIdentifier)(id.typeAnnotation.typeAnnotation.typeName)){let{name}=id.typeAnnotation.typeAnnotation.typeName;if(this.getAllLocalImports().includes(name)){let localTypeImport=this.getOrAddImport(type);return{...id,typeAnnotation:t2.tsTypeAnnotation(t2.tsTypeReference(t2.identifier(localTypeImport),id.typeAnnotation.typeAnnotation.typeParameters))}}}return id};this.sbImportDeclarations=this.getAllSbImportDeclarations(file)}},parser="tsx";0&&(module.exports={parser});
@@ -49,7 +49,7 @@ Defaulting to 2020, but this will stop working in the future.`)),options.ecmaVer
49
49
  `),map3)):attributesOnOneLine&&(value+=tracker.move(" "+attributesOnOneLine)),attributesOnTheirOwnLine&&(value+=tracker.move(`
50
50
  `)),selfClosing&&(value+=tracker.move((tightSelfClosing||attributesOnTheirOwnLine?"":" ")+"/")),value+=tracker.move(">"),node3.children&&node3.children.length>0&&(node3.type==="mdxJsxFlowElement"?(tracker.shift(2),value+=tracker.move(`
51
51
  `),value+=tracker.move(indentLines(containerFlow(node3,context,tracker.current()),map3)),value+=tracker.move(`
52
- `)):value+=tracker.move(containerPhrasing(node3,context,{...tracker.current(),before:"<",after:">"}))),selfClosing||(value+=tracker.move("</"+(node3.name||"")+">")),exit2(),value}function map3(line,_,blank){return(blank?"":" ")+line}function peekElement(){return"<"}}var mdxjsEsmFromMarkdown={enter:{mdxjsEsm:enterMdxjsEsm},exit:{mdxjsEsm:exitMdxjsEsm,mdxjsEsmData:exitMdxjsEsmData}},mdxjsEsmToMarkdown={handlers:{mdxjsEsm:handleMdxjsEsm}};function enterMdxjsEsm(token){this.enter({type:"mdxjsEsm",value:""},token),this.buffer()}function exitMdxjsEsm(token){let value=this.resume(),node3=this.exit(token),estree=token.estree;node3.value=value,estree&&(node3.data={estree})}function exitMdxjsEsmData(token){this.config.enter.data.call(this,token),this.config.exit.data.call(this,token)}function handleMdxjsEsm(node3){return node3.value||""}function mdxFromMarkdown(){return[mdxExpressionFromMarkdown,mdxJsxFromMarkdown(),mdxjsEsmFromMarkdown]}function mdxToMarkdown(options){return{extensions:[mdxExpressionToMarkdown,mdxJsxToMarkdown(options),mdxjsEsmToMarkdown]}}function remarkMdx(options){let data2=this.data();add("micromarkExtensions",mdxjs(options)),add("fromMarkdownExtensions",mdxFromMarkdown()),add("toMarkdownExtensions",mdxToMarkdown(options));function add(field,value){(data2[field]?data2[field]:data2[field]=[]).push(value)}}var t=__toESM(require("@babel/types")),babel=__toESM(require("@babel/core")),recast=__toESM(require("recast")),path=__toESM(require("path")),import_prettier=__toESM(require("prettier")),fs=__toESM(require("fs")),import_camelCase=__toESM(require("lodash/camelCase")),mdxProcessor=remark().use(remarkMdx);function jscodeshift(info){let parsed=path.parse(info.path),baseName=path.join(parsed.dir,parsed.name.replace(".mdx","").replace(".stories","").replace(".story",""));for(;fs.existsSync(`${baseName}.stories.js`);)baseName+="_";let result=transform(info.source,path.basename(baseName));if(result==null)return;let[mdx,csf]=result;return fs.writeFileSync(`${baseName}.stories.js`,csf),mdx}function transform(source,baseName){let root2=mdxProcessor.parse(source),storyNamespaceName=nameToValidExport(`${baseName}Stories`),metaAttributes=[],storiesMap=new Map;visit(root2,["mdxjsEsm"],node3=>{node3.value=node3.value.replaceAll("@storybook/addon-docs/blocks","@storybook/blocks").replaceAll("@storybook/addon-docs","@storybook/blocks")});let file=getEsmAst(root2);addStoriesImport(root2,baseName,storyNamespaceName),visit(root2,["mdxJsxFlowElement","mdxJsxTextElement"],(node3,index4,parent)=>{if(is(node3,{name:"Meta"})&&(metaAttributes.push(...node3.attributes),node3.attributes=[{type:"mdxJsxAttribute",name:"of",value:{type:"mdxJsxAttributeValueExpression",value:storyNamespaceName}}]),is(node3,{name:"Story"})){let nameAttribute=node3.attributes.find(it=>it.type==="mdxJsxAttribute"&&it.name==="name"),idAttribute=node3.attributes.find(it=>it.type==="mdxJsxAttribute"&&it.name==="id"),storyAttribute=node3.attributes.find(it=>it.type==="mdxJsxAttribute"&&it.name==="story");if(typeof(nameAttribute==null?void 0:nameAttribute.value)=="string"){let name2=nameToValidExport(nameAttribute.value);for(;variableNameExists(name2);)name2+="_";storiesMap.set(name2,{type:"value",attributes:node3.attributes,children:node3.children}),node3.attributes=[{type:"mdxJsxAttribute",name:"of",value:{type:"mdxJsxAttributeValueExpression",value:`${storyNamespaceName}.${name2}`}}],node3.children=[]}else if(idAttribute!=null&&idAttribute.value){let newNode={type:"mdxFlowExpression",value:`/* ${mdxProcessor.stringify({type:"root",children:[node3]}).trim()} is deprecated, please migrate it to <Story of={referenceToStory} /> */`};return storiesMap.set(idAttribute.value,{type:"id"}),parent.children.splice(index4,0,newNode),[SKIP,index4+2]}else if((storyAttribute==null?void 0:storyAttribute.type)==="mdxJsxAttribute"&&typeof storyAttribute.value=="object"&&storyAttribute.value.type==="mdxJsxAttributeValueExpression"){let name2=storyAttribute.value.value;node3.attributes=[{type:"mdxJsxAttribute",name:"of",value:{type:"mdxJsxAttributeValueExpression",value:`${storyNamespaceName}.${name2}`}}],node3.children=[],storiesMap.set(name2,{type:"reference"})}else return parent.children.splice(index4,1),[SKIP,index4]}});let metaProperties=metaAttributes.flatMap(attribute=>attribute.type==="mdxJsxAttribute"?typeof attribute.value=="string"?[t.objectProperty(t.identifier(attribute.name),t.stringLiteral(attribute.value))]:[t.objectProperty(t.identifier(attribute.name),(0,import_csf_tools.babelParseExpression)(attribute.value.value))]:[]);if(file.path.traverse({ImportDeclaration(path2){path2.node.source.value==="@storybook/blocks"&&path2.remove()},ExportNamedDeclaration(path2){path2.replaceWith(path2.node.declaration)}}),storiesMap.size===0)return null;let newStatements=[t.exportDefaultDeclaration(t.objectExpression(metaProperties))];function mapChildrenToRender(children){let child=children[0];if(!child)return;if(child.type==="text")return t.arrowFunctionExpression([],t.stringLiteral(child.value));if(child.type==="mdxFlowExpression"||child.type==="mdxTextExpression"){let expression2=(0,import_csf_tools.babelParseExpression)(child.value);return/\.bind\(.*\)/.test(child.value)||t.isIdentifier(expression2)||t.isArrowFunctionExpression(expression2)?expression2:t.arrowFunctionExpression([],expression2)}let expression=(0,import_csf_tools.babelParseExpression)(mdxProcessor.stringify({type:"root",children:[child]}));return t.arrowFunctionExpression([],expression)}function variableNameExists(name2){let found=!1;return file.path.traverse({VariableDeclarator:path2=>{let lVal=path2.node.id;t.isIdentifier(lVal)&&lVal.name===name2&&(found=!0)}}),found}newStatements.push(...[...storiesMap].flatMap(([key,value])=>{if(value.type==="id")return[];if(value.type==="reference")return[t.exportNamedDeclaration(null,[t.exportSpecifier(t.identifier(key),t.identifier(key))])];let renderProperty=mapChildrenToRender(value.children),newObject=t.objectExpression([...renderProperty?[t.objectProperty(t.identifier("render"),mapChildrenToRender(value.children))]:[],...value.attributes.flatMap(attribute=>attribute.type==="mdxJsxAttribute"?typeof attribute.value=="string"?[t.objectProperty(t.identifier(attribute.name),t.stringLiteral(attribute.value))]:[t.objectProperty(t.identifier(attribute.name),(0,import_csf_tools.babelParseExpression)(attribute.value.value))]:[])]);return[t.exportNamedDeclaration(t.variableDeclaration("const",[t.variableDeclarator(t.identifier(key),newObject)]))]})),file.path.node.body=[...file.path.node.body,...newStatements];let newMdx=mdxProcessor.stringify(root2),output=recast.print(file.path.node).code,prettierConfig=import_prettier.default.resolveConfig.sync(".",{editorconfig:!0})||{printWidth:100,tabWidth:2,bracketSpacing:!0,trailingComma:"es5",singleQuote:!0};return output=import_prettier.default.format(output,{...prettierConfig,filepath:"file.jsx"}),[newMdx,output]}function getEsmAst(root2){let esm=[];visit(root2,["mdxjsEsm"],node3=>{esm.push(node3.value)});let esmSource=`${esm.join(`
52
+ `)):value+=tracker.move(containerPhrasing(node3,context,{...tracker.current(),before:"<",after:">"}))),selfClosing||(value+=tracker.move("</"+(node3.name||"")+">")),exit2(),value}function map3(line,_,blank){return(blank?"":" ")+line}function peekElement(){return"<"}}var mdxjsEsmFromMarkdown={enter:{mdxjsEsm:enterMdxjsEsm},exit:{mdxjsEsm:exitMdxjsEsm,mdxjsEsmData:exitMdxjsEsmData}},mdxjsEsmToMarkdown={handlers:{mdxjsEsm:handleMdxjsEsm}};function enterMdxjsEsm(token){this.enter({type:"mdxjsEsm",value:""},token),this.buffer()}function exitMdxjsEsm(token){let value=this.resume(),node3=this.exit(token),estree=token.estree;node3.value=value,estree&&(node3.data={estree})}function exitMdxjsEsmData(token){this.config.enter.data.call(this,token),this.config.exit.data.call(this,token)}function handleMdxjsEsm(node3){return node3.value||""}function mdxFromMarkdown(){return[mdxExpressionFromMarkdown,mdxJsxFromMarkdown(),mdxjsEsmFromMarkdown]}function mdxToMarkdown(options){return{extensions:[mdxExpressionToMarkdown,mdxJsxToMarkdown(options),mdxjsEsmToMarkdown]}}function remarkMdx(options){let data2=this.data();add("micromarkExtensions",mdxjs(options)),add("fromMarkdownExtensions",mdxFromMarkdown()),add("toMarkdownExtensions",mdxToMarkdown(options));function add(field,value){(data2[field]?data2[field]:data2[field]=[]).push(value)}}var t=__toESM(require("@babel/types")),babel=__toESM(require("@babel/core")),recast=__toESM(require("recast")),path=__toESM(require("path")),import_prettier=__toESM(require("prettier")),fs=__toESM(require("fs")),import_camelCase=__toESM(require("lodash/camelCase")),mdxProcessor=remark().use(remarkMdx);function jscodeshift(info){let parsed=path.parse(info.path),baseName=path.join(parsed.dir,parsed.name.replace(".mdx","").replace(".stories","").replace(".story",""));for(;fs.existsSync(`${baseName}.stories.js`);)baseName+="_";let result=transform(info.source,path.basename(baseName)),[mdx,csf]=result;return csf!=null&&fs.writeFileSync(`${baseName}.stories.js`,csf),mdx}function transform(source,baseName){let root2=mdxProcessor.parse(source),storyNamespaceName=nameToValidExport(`${baseName}Stories`),metaAttributes=[],storiesMap=new Map;visit(root2,["mdxjsEsm"],node3=>{node3.value=node3.value.replaceAll("@storybook/addon-docs/blocks","@storybook/blocks").replaceAll("@storybook/addon-docs","@storybook/blocks")});let file=getEsmAst(root2);visit(root2,["mdxJsxFlowElement","mdxJsxTextElement"],(node3,index4,parent)=>{if(is(node3,{name:"Meta"})&&(metaAttributes.push(...node3.attributes),node3.attributes=[{type:"mdxJsxAttribute",name:"of",value:{type:"mdxJsxAttributeValueExpression",value:storyNamespaceName}}]),is(node3,{name:"Story"})){let nameAttribute=node3.attributes.find(it=>it.type==="mdxJsxAttribute"&&it.name==="name"),idAttribute=node3.attributes.find(it=>it.type==="mdxJsxAttribute"&&it.name==="id"),storyAttribute=node3.attributes.find(it=>it.type==="mdxJsxAttribute"&&it.name==="story");if(typeof(nameAttribute==null?void 0:nameAttribute.value)=="string"){let name2=nameToValidExport(nameAttribute.value);for(;variableNameExists(name2);)name2+="_";storiesMap.set(name2,{type:"value",attributes:node3.attributes,children:node3.children}),node3.attributes=[{type:"mdxJsxAttribute",name:"of",value:{type:"mdxJsxAttributeValueExpression",value:`${storyNamespaceName}.${name2}`}}],node3.children=[]}else if(idAttribute!=null&&idAttribute.value){let newNode={type:"mdxFlowExpression",value:`/* ${mdxProcessor.stringify({type:"root",children:[node3]}).trim()} is deprecated, please migrate it to <Story of={referenceToStory} /> see: https://storybook.js.org/migration-guides/7.0 */`};return storiesMap.set(idAttribute.value,{type:"id"}),parent.children.splice(index4,0,newNode),[SKIP,index4+2]}else if((storyAttribute==null?void 0:storyAttribute.type)==="mdxJsxAttribute"&&typeof storyAttribute.value=="object"&&storyAttribute.value.type==="mdxJsxAttributeValueExpression"){let name2=storyAttribute.value.value;node3.attributes=[{type:"mdxJsxAttribute",name:"of",value:{type:"mdxJsxAttributeValueExpression",value:`${storyNamespaceName}.${name2}`}}],node3.children=[],storiesMap.set(name2,{type:"reference"})}else return parent.children.splice(index4,1),[SKIP,index4]}});let metaProperties=metaAttributes.flatMap(attribute=>attribute.type==="mdxJsxAttribute"?typeof attribute.value=="string"?[t.objectProperty(t.identifier(attribute.name),t.stringLiteral(attribute.value))]:[t.objectProperty(t.identifier(attribute.name),(0,import_csf_tools.babelParseExpression)(attribute.value.value))]:[]);if(file.path.traverse({ImportDeclaration(path2){path2.node.source.value==="@storybook/blocks"&&path2.remove()},ExportNamedDeclaration(path2){path2.replaceWith(path2.node.declaration)}}),storiesMap.size===0&&metaAttributes.length===0)return[mdxProcessor.stringify(root2),null];addStoriesImport(root2,baseName,storyNamespaceName);let newStatements=[t.exportDefaultDeclaration(t.objectExpression(metaProperties))];function mapChildrenToRender(children){let child=children[0];if(!child)return;if(child.type==="text")return t.arrowFunctionExpression([],t.stringLiteral(child.value));if(child.type==="mdxFlowExpression"||child.type==="mdxTextExpression"){let expression2=(0,import_csf_tools.babelParseExpression)(child.value);return/\.bind\(.*\)/.test(child.value)||t.isIdentifier(expression2)||t.isArrowFunctionExpression(expression2)?expression2:t.arrowFunctionExpression([],expression2)}let expression=(0,import_csf_tools.babelParseExpression)(mdxProcessor.stringify({type:"root",children:[child]}));return t.arrowFunctionExpression([],expression)}function variableNameExists(name2){let found=!1;return file.path.traverse({VariableDeclarator:path2=>{let lVal=path2.node.id;t.isIdentifier(lVal)&&lVal.name===name2&&(found=!0)}}),found}newStatements.push(...[...storiesMap].flatMap(([key,value])=>{if(value.type==="id")return[];if(value.type==="reference")return[t.exportNamedDeclaration(null,[t.exportSpecifier(t.identifier(key),t.identifier(key))])];let renderProperty=mapChildrenToRender(value.children),newObject=t.objectExpression([...renderProperty?[t.objectProperty(t.identifier("render"),mapChildrenToRender(value.children))]:[],...value.attributes.flatMap(attribute=>attribute.type==="mdxJsxAttribute"?typeof attribute.value=="string"?[t.objectProperty(t.identifier(attribute.name),t.stringLiteral(attribute.value))]:[t.objectProperty(t.identifier(attribute.name),(0,import_csf_tools.babelParseExpression)(attribute.value.value))]:[])]);return[t.exportNamedDeclaration(t.variableDeclaration("const",[t.variableDeclarator(t.identifier(key),newObject)]))]})),file.path.node.body=[...file.path.node.body,...newStatements];let newMdx=mdxProcessor.stringify(root2),output=recast.print(file.path.node).code,prettierConfig=import_prettier.default.resolveConfig.sync(".",{editorconfig:!0})||{printWidth:100,tabWidth:2,bracketSpacing:!0,trailingComma:"es5",singleQuote:!0};return output=import_prettier.default.format(output,{...prettierConfig,filepath:"file.jsx"}),[newMdx,output]}function getEsmAst(root2){let esm=[];visit(root2,["mdxjsEsm"],node3=>{esm.push(node3.value)});let esmSource=`${esm.join(`
53
53
 
54
54
  `)}`;return new babel.File({filename:"info.path"},{code:esmSource,ast:(0,import_csf_tools.babelParse)(esmSource)})}function addStoriesImport(root2,baseName,storyNamespaceName){let found=!1;visit(root2,["mdxjsEsm"],node3=>{found||(node3.value+=`
55
55
  import * as ${storyNamespaceName} from './${baseName}.stories';`,found=!0)})}function nameToValidExport(name2){let[first,...rest]=Array.from((0,import_camelCase.default)(name2));return`${first.match(/[a-zA-Z_$]/)?first.toUpperCase():`$${first}`}${rest.join("")}`}0&&(module.exports={nameToValidExport,transform});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybook/codemod",
3
- "version": "7.0.0-rc.0",
3
+ "version": "7.0.0-rc.10",
4
4
  "description": "A collection of codemod scripts written with JSCodeshift",
5
5
  "keywords": [
6
6
  "storybook"
@@ -49,9 +49,9 @@
49
49
  "@babel/preset-env": "~7.20.2",
50
50
  "@babel/types": "~7.21.2",
51
51
  "@storybook/csf": "next",
52
- "@storybook/csf-tools": "7.0.0-rc.0",
53
- "@storybook/node-logger": "7.0.0-rc.0",
54
- "@storybook/types": "7.0.0-rc.0",
52
+ "@storybook/csf-tools": "7.0.0-rc.10",
53
+ "@storybook/node-logger": "7.0.0-rc.10",
54
+ "@storybook/types": "7.0.0-rc.10",
55
55
  "cross-spawn": "^7.0.3",
56
56
  "globby": "^11.0.2",
57
57
  "jscodeshift": "^0.14.0",
@@ -97,5 +97,5 @@
97
97
  "cjs"
98
98
  ]
99
99
  },
100
- "gitHead": "6d1bcb2579203a3075467a2ba83738d854545053"
100
+ "gitHead": "dd10adf7821c47f0d3a2e6a45d80eea2b906c3a2"
101
101
  }
@@ -136,14 +136,60 @@ describe('csf-2-to-3', () => {
136
136
  `)
137
137
  ).toMatchInlineSnapshot(`
138
138
  export default { title: 'Cat' };
139
+ const Template = (args) => <Cat {...args} />;
139
140
 
140
141
  export const A = {
141
- render: (args) => <Cat {...args} />,
142
+ render: Template,
142
143
  args: { isPrimary: false },
143
144
  };
144
145
  `);
145
146
  });
146
147
 
148
+ it('should reuse the template when there are multiple Template.bind references but no component defined', () => {
149
+ expect(
150
+ jsTransform(dedent`
151
+ export default { title: 'Cat' };
152
+ const Template = (args) => <Cat {...args} />;
153
+
154
+ export const A = Template.bind({});
155
+ A.args = { isPrimary: false };
156
+
157
+ export const B = Template.bind({});
158
+ B.args = { isPrimary: true };
159
+
160
+
161
+ export const C = Template.bind({});
162
+ C.args = { bla: true };
163
+
164
+ export const D = Template.bind({});
165
+ D.args = { bla: false };
166
+ `)
167
+ ).toMatchInlineSnapshot(`
168
+ export default { title: 'Cat' };
169
+ const Template = (args) => <Cat {...args} />;
170
+
171
+ export const A = {
172
+ render: Template,
173
+ args: { isPrimary: false },
174
+ };
175
+
176
+ export const B = {
177
+ render: Template,
178
+ args: { isPrimary: true },
179
+ };
180
+
181
+ export const C = {
182
+ render: Template,
183
+ args: { bla: true },
184
+ };
185
+
186
+ export const D = {
187
+ render: Template,
188
+ args: { bla: false },
189
+ };
190
+ `);
191
+ });
192
+
147
193
  it('should remove implicit global render for template.bind', () => {
148
194
  expect(
149
195
  jsTransform(dedent`
@@ -166,8 +212,10 @@ describe('csf-2-to-3', () => {
166
212
  args: { isPrimary: false },
167
213
  };
168
214
 
215
+ const Template2 = (args) => <Banana {...args} />;
216
+
169
217
  export const B = {
170
- render: (args) => <Banana {...args} />,
218
+ render: Template2,
171
219
  args: { isPrimary: true },
172
220
  };
173
221
  `);
@@ -359,5 +407,33 @@ describe('csf-2-to-3', () => {
359
407
  };
360
408
  `);
361
409
  });
410
+
411
+ it('migrate Story type to StoryFn when used in an not exported Template function', () => {
412
+ expect(
413
+ tsTransform(dedent`
414
+ import { Story, Meta } from '@storybook/react'
415
+
416
+ export default {
417
+ component: Cat,
418
+ } satisfies Meta
419
+
420
+ const Template: Story = () => <div>Hello World</div>;
421
+
422
+ export const Default = Template.bind({})
423
+ `)
424
+ ).toMatchInlineSnapshot(`
425
+ import { StoryFn, Meta } from '@storybook/react';
426
+
427
+ export default {
428
+ component: Cat,
429
+ } satisfies Meta;
430
+
431
+ const Template: StoryFn = () => <div>Hello World</div>;
432
+
433
+ export const Default = {
434
+ render: Template,
435
+ };
436
+ `);
437
+ });
362
438
  });
363
439
  });
@@ -15,22 +15,39 @@ beforeEach(() => {
15
15
  fs.existsSync.mockImplementation(() => false);
16
16
  });
17
17
 
18
+ test('update import even when no stories can be extracted', () => {
19
+ const input = dedent`
20
+ import { Heading } from '@storybook/addon-docs';
21
+
22
+ <Heading />
23
+ `;
24
+
25
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
26
+
27
+ expect(mdx).toMatchInlineSnapshot(`
28
+ import { Heading } from '@storybook/blocks';
29
+
30
+ <Heading />
31
+
32
+ `);
33
+ });
34
+
18
35
  test('drop invalid story nodes', () => {
19
36
  const input = dedent`
20
- import { Meta } from '@storybook/addon-docs';
37
+ import { Meta, Story } from '@storybook/addon-docs';
21
38
 
22
39
  <Meta title="Foobar" />
23
40
 
24
41
  <Story>No name!</Story>
25
42
 
26
- <Story name="Primary">Story</Story>
27
-
43
+ <Story name="Primary">Story</Story>
28
44
  `;
29
45
 
30
46
  const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
31
47
 
32
48
  expect(mdx).toMatchInlineSnapshot(`
33
- import { Meta } from '@storybook/addon-docs';
49
+ import { Meta, Story } from '@storybook/blocks';
50
+ import * as FoobarStories from './Foobar.stories';
34
51
 
35
52
  <Meta of={FoobarStories} />
36
53
 
@@ -96,7 +113,7 @@ test('Comment out story nodes with id', () => {
96
113
 
97
114
  <Meta of={FoobarStories} />
98
115
 
99
- {/* <Story id="button--primary" /> is deprecated, please migrate it to <Story of={referenceToStory} /> */}
116
+ {/* <Story id="button--primary" /> is deprecated, please migrate it to <Story of={referenceToStory} /> see: https://storybook.js.org/migration-guides/7.0 */}
100
117
 
101
118
  <Story id="button--primary" />
102
119
 
@@ -38,12 +38,6 @@ const isStoryAnnotation = (stmt: t.Statement, objectExports: Record<string, any>
38
38
  t.isIdentifier(stmt.expression.left.object) &&
39
39
  objectExports[stmt.expression.left.object.name];
40
40
 
41
- const isTemplateDeclaration = (stmt: t.Statement, templates: Record<string, any>) =>
42
- t.isVariableDeclaration(stmt) &&
43
- stmt.declarations.length === 1 &&
44
- t.isIdentifier(stmt.declarations[0].id) &&
45
- templates[stmt.declarations[0].id.name];
46
-
47
41
  const getNewExport = (stmt: t.Statement, objectExports: Record<string, any>) => {
48
42
  if (
49
43
  t.isExportNamedDeclaration(stmt) &&
@@ -94,6 +88,28 @@ const isReactGlobalRenderFn = (csf: CsfFile, storyFn: t.Expression) => {
94
88
  const isSimpleCSFStory = (init: t.Expression, annotations: t.ObjectProperty[]) =>
95
89
  annotations.length === 0 && t.isArrowFunctionExpression(init) && init.params.length === 0;
96
90
 
91
+ function removeUnusedTemplates(csf: CsfFile) {
92
+ Object.entries(csf._templates).forEach(([template, templateExpression]) => {
93
+ const references: NodePath[] = [];
94
+ babel.traverse(csf._ast, {
95
+ Identifier: (path) => {
96
+ if (path.node.name === template) references.push(path);
97
+ },
98
+ });
99
+ // if there is only one reference and this reference is the variable declaration initializing the template
100
+ // then we are sure the template is unused
101
+ if (references.length === 1) {
102
+ const reference = references[0];
103
+ if (
104
+ reference.parentPath.isVariableDeclarator() &&
105
+ reference.parentPath.node.init === templateExpression
106
+ ) {
107
+ reference.parentPath.remove();
108
+ }
109
+ }
110
+ });
111
+ }
112
+
97
113
  export default function transform(info: FileInfo, api: API, options: { parser?: string }) {
98
114
  const makeTitle = (userTitle?: string) => {
99
115
  return userTitle || 'FIXME';
@@ -137,15 +153,18 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
137
153
  return;
138
154
  }
139
155
 
140
- // Remove the render function when we can hoist the template
141
- // const Template = (args) => <Cat {...args} />;
142
- // export const A = Template.bind({});
143
- let storyFn: t.Expression = template && (csf._templates[template] as any as t.Expression);
156
+ let storyFn: t.Expression = template && t.identifier(template);
144
157
  if (!storyFn) {
145
158
  storyFn = init;
146
159
  }
147
160
 
148
- const renderAnnotation = isReactGlobalRenderFn(csf, storyFn)
161
+ // Remove the render function when we can hoist the template
162
+ // const Template = (args) => <Cat {...args} />;
163
+ // export const A = Template.bind({});
164
+ const renderAnnotation = isReactGlobalRenderFn(
165
+ csf,
166
+ template ? csf._templates[template] : storyFn
167
+ )
149
168
  ? []
150
169
  : [t.objectProperty(t.identifier('render'), storyFn)];
151
170
 
@@ -160,15 +179,10 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
160
179
  }
161
180
  });
162
181
 
163
- importHelper.removeDeprecatedStoryImport();
164
-
165
182
  csf._ast.program.body = csf._ast.program.body.reduce((acc, stmt) => {
166
183
  const statement = stmt as t.Statement;
167
184
  // remove story annotations & template declarations
168
- if (
169
- isStoryAnnotation(statement, objectExports) ||
170
- isTemplateDeclaration(statement, csf._templates)
171
- ) {
185
+ if (isStoryAnnotation(statement, objectExports)) {
172
186
  return acc;
173
187
  }
174
188
 
@@ -185,6 +199,8 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
185
199
  }, []);
186
200
 
187
201
  upgradeDeprecatedTypes(file);
202
+ importHelper.removeDeprecatedStoryImport();
203
+ removeUnusedTemplates(csf);
188
204
 
189
205
  let output = recast.print(csf._ast, {}).code;
190
206
 
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-param-reassign,@typescript-eslint/no-shadow,consistent-return */
1
+ /* eslint-disable no-param-reassign,@typescript-eslint/no-shadow */
2
2
  import type { FileInfo } from 'jscodeshift';
3
3
  import { babelParse, babelParseExpression } from '@storybook/csf-tools';
4
4
  import { remark } from 'remark';
@@ -40,14 +40,11 @@ export default function jscodeshift(info: FileInfo) {
40
40
 
41
41
  const result = transform(info.source, path.basename(baseName));
42
42
 
43
- if (result == null) {
44
- // We can not make a valid migration.
45
- return;
46
- }
47
-
48
43
  const [mdx, csf] = result;
49
44
 
50
- fs.writeFileSync(`${baseName}.stories.js`, csf);
45
+ if (csf != null) {
46
+ fs.writeFileSync(`${baseName}.stories.js`, csf);
47
+ }
51
48
 
52
49
  return mdx;
53
50
  }
@@ -80,7 +77,6 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
80
77
  });
81
78
 
82
79
  const file = getEsmAst(root);
83
- addStoriesImport(root, baseName, storyNamespaceName);
84
80
 
85
81
  visit(
86
82
  root,
@@ -135,7 +131,7 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
135
131
  const nodeString = mdxProcessor.stringify({ type: 'root', children: [node] }).trim();
136
132
  const newNode: MdxFlowExpression = {
137
133
  type: 'mdxFlowExpression',
138
- value: `/* ${nodeString} is deprecated, please migrate it to <Story of={referenceToStory} /> */`,
134
+ value: `/* ${nodeString} is deprecated, please migrate it to <Story of={referenceToStory} /> see: https://storybook.js.org/migration-guides/7.0 */`,
139
135
  };
140
136
  storiesMap.set(idAttribute.value as string, { type: 'id' });
141
137
  parent.children.splice(index, 0, newNode);
@@ -201,11 +197,13 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
201
197
  },
202
198
  });
203
199
 
204
- if (storiesMap.size === 0) {
200
+ if (storiesMap.size === 0 && metaAttributes.length === 0) {
205
201
  // A CSF file must have at least one story, so skip migrating if this is the case.
206
- return null;
202
+ return [mdxProcessor.stringify(root), null];
207
203
  }
208
204
 
205
+ addStoriesImport(root, baseName, storyNamespaceName);
206
+
209
207
  const newStatements: t.Statement[] = [
210
208
  t.exportDefaultDeclaration(t.objectExpression(metaProperties)),
211
209
  ];