mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-15 05:02:24 +08:00
Merge pull request #23373 from aditya1/fix/convert-to-strict-ts-codemod-pkg
Typescript: Strict compile option for `codemods` package
This commit is contained in:
commit
f998cc921b
@ -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",
|
||||
|
@ -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) => <Cat {...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
|
||||
)
|
||||
),
|
||||
|
@ -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 <Story of={referenceToStory} /> 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. <Story story={Primary} />
|
||||
|
||||
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';`;
|
||||
|
@ -3,7 +3,8 @@
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"strict": false
|
||||
"strict": true,
|
||||
"lib": ["ES2021.String"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "__testfixtures__", "__tests__"]
|
||||
|
@ -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<null>((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);
|
||||
};
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user