diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json
index d20fa00879b3..1e7650b6b268 100644
--- a/code/lib/codemod/package.json
+++ b/code/lib/codemod/package.json
@@ -65,7 +65,8 @@
"jscodeshift": "^0.15.1",
"lodash": "^4.17.21",
"prettier": "^2.8.0",
- "recast": "^0.23.1"
+ "recast": "^0.23.1",
+ "tiny-invariant": "^1.3.1"
},
"devDependencies": {
"@types/jscodeshift": "^0.11.10",
diff --git a/code/lib/codemod/src/transforms/csf-2-to-3.ts b/code/lib/codemod/src/transforms/csf-2-to-3.ts
index 945f3542b6e8..72f283df95f3 100644
--- a/code/lib/codemod/src/transforms/csf-2-to-3.ts
+++ b/code/lib/codemod/src/transforms/csf-2-to-3.ts
@@ -7,6 +7,7 @@ import { loadCsf, printCsf } from '@storybook/csf-tools';
import type { API, FileInfo } from 'jscodeshift';
import type { BabelFile, NodePath } from '@babel/core';
import * as babel from '@babel/core';
+import invariant from 'tiny-invariant';
import { upgradeDeprecatedTypes } from './upgrade-deprecated-types';
const logger = console;
@@ -15,7 +16,7 @@ const renameAnnotation = (annotation: string) => {
return annotation === 'storyName' ? 'name' : annotation;
};
-const getTemplateBindVariable = (init: t.Expression) =>
+const getTemplateBindVariable = (init: t.Expression | undefined) =>
t.isCallExpression(init) &&
t.isMemberExpression(init.callee) &&
t.isIdentifier(init.callee.object) &&
@@ -92,7 +93,7 @@ function removeUnusedTemplates(csf: CsfFile) {
const references: NodePath[] = [];
babel.traverse(csf._ast, {
Identifier: (path) => {
- if (path.node.name === template) references.push(path);
+ if (path.node.name === template) references.push(path as NodePath);
},
});
// if there is only one reference and this reference is the variable declaration initializing the template
@@ -100,7 +101,7 @@ function removeUnusedTemplates(csf: CsfFile) {
if (references.length === 1) {
const reference = references[0];
if (
- reference.parentPath.isVariableDeclarator() &&
+ reference.parentPath?.isVariableDeclarator() &&
reference.parentPath.node.init === templateExpression
) {
reference.parentPath.remove();
@@ -124,6 +125,7 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
// This allows for showing buildCodeFrameError messages
// @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
+
const file: BabelFile = new babel.File(
{ filename: info.path },
{ code: info.source, ast: csf._ast }
@@ -137,8 +139,9 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
return t.objectProperty(t.identifier(renameAnnotation(annotation)), val as t.Expression);
});
- if (t.isVariableDeclarator(decl)) {
- const { init, id } = decl;
+ if (t.isVariableDeclarator(decl as t.Node)) {
+ const { init, id } = decl as any;
+ invariant(init, 'Inital value should be declared');
// only replace arrow function expressions && template
const template = getTemplateBindVariable(init);
if (!t.isArrowFunctionExpression(init) && !template) return;
@@ -152,10 +155,7 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
return;
}
- let storyFn: t.Expression = template && t.identifier(template);
- if (!storyFn) {
- storyFn = init;
- }
+ const storyFn: t.Expression = template ? t.identifier(template) : init;
// Remove the render function when we can hoist the template
// const Template = (args) => ;
@@ -178,8 +178,8 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
}
});
- csf._ast.program.body = csf._ast.program.body.reduce((acc, stmt) => {
- const statement = stmt as t.Statement;
+ csf._ast.program.body = csf._ast.program.body.reduce((acc: t.Statement[], stmt: t.Statement) => {
+ const statement = stmt;
// remove story annotations & template declarations
if (isStoryAnnotation(statement, objectExports)) {
return acc;
@@ -251,8 +251,23 @@ class StorybookImportHelper {
}
if (!specifier.isImportSpecifier()) return false;
const imported = specifier.get('imported');
- if (!imported.isIdentifier()) return false;
+ 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',
@@ -321,7 +336,7 @@ class StorybookImportHelper {
...id,
typeAnnotation: t.tsTypeAnnotation(
t.tsTypeReference(
- t.identifier(localTypeImport),
+ t.identifier(localTypeImport ?? ''),
id.typeAnnotation.typeAnnotation.typeParameters
)
),
diff --git a/code/lib/codemod/src/transforms/mdx-to-csf.ts b/code/lib/codemod/src/transforms/mdx-to-csf.ts
index 48e527f237f6..b5f68b8a7b50 100644
--- a/code/lib/codemod/src/transforms/mdx-to-csf.ts
+++ b/code/lib/codemod/src/transforms/mdx-to-csf.ts
@@ -1,4 +1,4 @@
-/* eslint-disable no-param-reassign,@typescript-eslint/no-shadow */
+/* eslint-disable @typescript-eslint/ban-ts-comment,no-param-reassign,@typescript-eslint/no-shadow */
import type { FileInfo } from 'jscodeshift';
import { babelParse, babelParseExpression } from '@storybook/csf-tools';
import { remark } from 'remark';
@@ -49,7 +49,7 @@ export default function jscodeshift(info: FileInfo) {
return mdx;
}
-export function transform(source: string, baseName: string): [mdx: string, csf: string] {
+export function transform(source: string, baseName: string): [string, string] {
const root = mdxProcessor.parse(source);
const storyNamespaceName = nameToValidExport(`${baseName}Stories`);
@@ -70,6 +70,7 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
>();
// rewrite addon docs import
+ // @ts-ignore
visit(root, ['mdxjsEsm'], (node: MdxjsEsm) => {
node.value = node.value
.replaceAll('@storybook/addon-docs/blocks', '@storybook/blocks')
@@ -78,6 +79,7 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
const file = getEsmAst(root);
+ // @ts-ignore
visit(
root,
['mdxJsxFlowElement', 'mdxJsxTextElement'],
@@ -134,18 +136,18 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
value: `/* ${nodeString} is deprecated, please migrate it to see: https://storybook.js.org/migration-guides/7.0 */`,
};
storiesMap.set(idAttribute.value as string, { type: 'id' });
- parent.children.splice(index, 0, newNode);
+ parent?.children.splice(index as number, 0, newNode);
// current index is the new comment, and index + 1 is current node
// SKIP traversing current node, and continue with the node after that
- return [SKIP, index + 2];
+ return [SKIP, (index as number) + 2];
} else if (
storyAttribute?.type === 'mdxJsxAttribute' &&
typeof storyAttribute.value === 'object' &&
- storyAttribute.value.type === 'mdxJsxAttributeValueExpression'
+ storyAttribute.value?.type === 'mdxJsxAttributeValueExpression'
) {
// e.g.
- const name = storyAttribute.value.value;
+ const name = storyAttribute.value?.value;
node.attributes = [
{
type: 'mdxJsxAttribute',
@@ -158,9 +160,9 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
];
node.children = [];
- storiesMap.set(name, { type: 'reference' });
+ storiesMap.set(name ?? '', { type: 'reference' });
} else {
- parent.children.splice(index, 1);
+ parent?.children.splice(index as number, 1);
// Do not traverse `node`, continue at the node *now* at `index`.
return [SKIP, index];
}
@@ -177,7 +179,7 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
return [
t.objectProperty(
t.identifier(attribute.name),
- babelParseExpression(attribute.value.value) as any as t.Expression
+ babelParseExpression(attribute.value?.value ?? '') as any as t.Expression
),
];
}
@@ -193,13 +195,14 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
},
// remove exports from csf file
ExportNamedDeclaration(path) {
+ // @ts-ignore
path.replaceWith(path.node.declaration);
},
});
if (storiesMap.size === 0 && metaAttributes.length === 0) {
// A CSF file must have at least one story, so skip migrating if this is the case.
- return [mdxProcessor.stringify(root), null];
+ return [mdxProcessor.stringify(root), ''];
}
addStoriesImport(root, baseName, storyNamespaceName);
@@ -260,9 +263,7 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
}
const renderProperty = mapChildrenToRender(value.children);
const newObject = t.objectExpression([
- ...(renderProperty
- ? [t.objectProperty(t.identifier('render'), mapChildrenToRender(value.children))]
- : []),
+ ...(renderProperty ? [t.objectProperty(t.identifier('render'), renderProperty)] : []),
...value.attributes.flatMap((attribute) => {
if (attribute.type === 'mdxJsxAttribute') {
if (typeof attribute.value === 'string') {
@@ -273,7 +274,7 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
return [
t.objectProperty(
t.identifier(attribute.name),
- babelParseExpression(attribute.value.value) as any as t.Expression
+ babelParseExpression(attribute.value?.value ?? '') as any as t.Expression
),
];
}
@@ -309,12 +310,13 @@ export function transform(source: string, baseName: string): [mdx: string, csf:
function getEsmAst(root: Root) {
const esm: string[] = [];
+ // @ts-expect-error (not valid BuildVisitor)
visit(root, ['mdxjsEsm'], (node: MdxjsEsm) => {
esm.push(node.value);
});
const esmSource = `${esm.join('\n\n')}`;
- // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
+ // @ts-expect-error (File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606)
const file: BabelFile = new babel.File(
{ filename: 'info.path' },
{ code: esmSource, ast: babelParse(esmSource) }
@@ -324,7 +326,7 @@ function getEsmAst(root: Root) {
function addStoriesImport(root: Root, baseName: string, storyNamespaceName: string): void {
let found = false;
-
+ // @ts-expect-error (not valid BuildVisitor)
visit(root, ['mdxjsEsm'], (node: MdxjsEsm) => {
if (!found) {
node.value += `\nimport * as ${storyNamespaceName} from './${baseName}.stories';`;
diff --git a/code/lib/codemod/tsconfig.json b/code/lib/codemod/tsconfig.json
index 3a9315f11d34..0e884ab34b9d 100644
--- a/code/lib/codemod/tsconfig.json
+++ b/code/lib/codemod/tsconfig.json
@@ -3,7 +3,8 @@
"compilerOptions": {
"skipLibCheck": true,
"allowJs": true,
- "strict": false
+ "strict": true,
+ "lib": ["ES2021.String"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "__testfixtures__", "__tests__"]
diff --git a/code/lib/react-dom-shim/src/react-16.tsx b/code/lib/react-dom-shim/src/react-16.tsx
index 8d2e60aea9e5..a519dbd51bb6 100644
--- a/code/lib/react-dom-shim/src/react-16.tsx
+++ b/code/lib/react-dom-shim/src/react-16.tsx
@@ -1,14 +1,13 @@
+/* eslint-disable react/no-deprecated */
import type { ReactElement } from 'react';
import ReactDOM from 'react-dom';
export const renderElement = async (node: ReactElement, el: Element) => {
return new Promise((resolve) => {
- // eslint-disable-next-line react/no-deprecated
ReactDOM.render(node, el, () => resolve(null));
});
};
export const unmountElement = (el: Element) => {
- // eslint-disable-next-line react/no-deprecated
ReactDOM.unmountComponentAtNode(el);
};
diff --git a/code/yarn.lock b/code/yarn.lock
index fe60f7899219..c5118b9cd016 100644
--- a/code/yarn.lock
+++ b/code/yarn.lock
@@ -5491,6 +5491,7 @@ __metadata:
recast: "npm:^0.23.1"
remark: "npm:^14.0.2"
remark-mdx: "npm:^2.3.0"
+ tiny-invariant: "npm:^1.3.1"
ts-dedent: "npm:^2.2.0"
typescript: "npm:^5.3.2"
unist-util-is: "npm:^5.2.0"