diff --git a/README.md b/README.md index 4a3efa3..6e78907 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,10 @@ npm test -- -u # update snapshots ``` ##### TODO: -- Parsing constants and pseudo-enum object constants +- Indexed access of pseudo-enum object constants - Parsing plain JS in addition to TypeScript - Multiple files and directories on CLI - Settings for control prefences (`select` vs `radio`, etc) -- Array types and other nested props - More unit test coverage ##### TODONT: diff --git a/index.ts b/index.ts index 34acd76..beff191 100644 --- a/index.ts +++ b/index.ts @@ -32,84 +32,94 @@ if (!inputFilePath) { if (!inputFilePath.match(/\.tsx?$/)) { nope("Only TypeScript currently supported") } -const inputDirPath = inputFilePath.replace(/^(.+)\/[^/]+$/, "$1/") -if (!inputDirPath || inputDirPath === inputFilePath) { - nope("Only relative paths supported: use './myFile.ts', not 'myFile.ts'") -} -let state: State = { +export const emptyState: State = { + componentsMap: {}, enumsMap: {}, importsMap: {}, importsUsed: {}, inputFilePath, - componentsMap: {}, propsFormat: program.opts().propsformat || '{Component}Props', } -const sourceFile: ts.SourceFile = getSourceFile(inputFilePath) -if (!sourceFile) nope(`An error occurred reading "${inputFilePath}"`) +const filesWritten = [] -sourceFile.statements.forEach(statement => { - switch (statement.kind) { - case ts.SyntaxKind.EnumDeclaration: - if (!ts.isEnumDeclaration(statement)) return - return state = handleEnum(state, statement) +function createStoryForFile(filePath: string) { + const dirPath = filePath.replace(/^(.+)\/[^/]+$/, "$1/") + if (!dirPath || dirPath === filePath) { + warn("Only relative paths supported: use './myFile.ts', not 'myFile.ts'") + return + } - case ts.SyntaxKind.VariableStatement: - if (!ts.isVariableStatement(statement)) return - return state = handleObjectEnum(state, statement) + const sourceFile: ts.SourceFile = getSourceFile(filePath) + if (!sourceFile) nope(`An error occurred reading "${filePath}"`) - case ts.SyntaxKind.ImportDeclaration: - if (!ts.isImportDeclaration(statement)) return - return state = handleImport(state, statement) - } -}) + let state = {...emptyState} -sourceFile.statements.forEach(statement => { - switch (statement.kind) { - case ts.SyntaxKind.TypeAliasDeclaration: - if (!ts.isTypeAliasDeclaration(statement)) return - return state = handleType(state, statement) + sourceFile.statements.forEach(statement => { + switch (statement.kind) { + case ts.SyntaxKind.EnumDeclaration: + if (!ts.isEnumDeclaration(statement)) return + return state = handleEnum(state, statement) - case ts.SyntaxKind.InterfaceDeclaration: - if (!ts.isInterfaceDeclaration(statement)) return - return state = handleInterface(state, statement) - } -}) + case ts.SyntaxKind.VariableStatement: + if (!ts.isVariableStatement(statement)) return + return state = handleObjectEnum(state, statement) -sourceFile.statements.forEach(fn => { - switch (fn.kind) { - case ts.SyntaxKind.FunctionDeclaration: - if (!ts.isFunctionDeclaration(fn)) return - return state = handleFunction(state, fn) - } -}) + case ts.SyntaxKind.ImportDeclaration: + if (!ts.isImportDeclaration(statement)) return + return state = handleImport(state, statement) + } + }) -// Write the story template for each component to files -const filesWritten = [] -Object.keys(state.componentsMap).forEach((componentName) => { - const outputFilePath = `${inputDirPath}${componentName}.stories.tsx` - if (!program.opts().overwrite && fileExists(outputFilePath)) { - warn(`Error: story file "${outputFilePath}" already exists. Use --overwrite to replace it.`) - return - } - const {props, isDefaultExport} = state.componentsMap[componentName] - if (!props) return - const {importsUsed} = state - const renderedStory = renderStory({ - componentName, - importsUsed, - props, - isDefaultExport, - wrap: program.opts().wrap || 'div', + sourceFile.statements.forEach(statement => { + switch (statement.kind) { + case ts.SyntaxKind.TypeAliasDeclaration: + if (!ts.isTypeAliasDeclaration(statement)) return + return state = handleType(state, statement) + + case ts.SyntaxKind.InterfaceDeclaration: + if (!ts.isInterfaceDeclaration(statement)) return + return state = handleInterface(state, statement) + } + }) + + sourceFile.statements.forEach(fn => { + switch (fn.kind) { + case ts.SyntaxKind.FunctionDeclaration: + if (!ts.isFunctionDeclaration(fn)) return + return state = handleFunction(state, fn) + } + }) + + Object.keys(state.componentsMap).forEach((componentName) => { + const outputFilePath = `${dirPath}${componentName}.stories.tsx` + if (!program.opts().overwrite && fileExists(outputFilePath)) { + warn(`Error: story file "${outputFilePath}" already exists. Use --overwrite to replace it.`) + return + } + const {props, isDefaultExport} = state.componentsMap[componentName] + if (!props) return + const {importsUsed} = state + const renderedStory = renderStory({ + componentName, + importsUsed, + props, + isDefaultExport, + wrap: program.opts().wrap || 'div', + }) + // console.log({outputFilePath}) + // console.log({renderedStory}) + try { + writeFileSync(outputFilePath, renderedStory) + filesWritten.push(outputFilePath) + } catch(e) { + warn(`Something went wrong writing ${outputFilePath}: ${e}` + )} }) - // console.log({outputFilePath}) - // console.log({renderedStory}) - try { - writeFileSync(outputFilePath, renderedStory) - filesWritten.push(outputFilePath) - } catch(e) {warn(`Something went wrong writing ${outputFilePath}: ${e}`)} -}) +} + +createStoryForFile(inputFilePath) if (filesWritten.length) { const plur = filesWritten.length > 1 ? 's' : '' diff --git a/src/components/ExampleInput.stories.tsx b/src/components/ExampleInput.stories.tsx index 873d221..e558972 100644 --- a/src/components/ExampleInput.stories.tsx +++ b/src/components/ExampleInput.stories.tsx @@ -3,13 +3,14 @@ import type {Story} from "@ladle/react" import {ExampleInput} from "./ExampleInput" import {FontSize, FontWeightObj} from "./ExampleInput" -import {ExportedFontSize} from "../utils" +import {ExportedFontSize, ExportedFontWeightObj} from "../types" export const ExampleInputStory: Story<{ allowNegative?: boolean fontSize?: FontSize fontSizeLabel?: ExportedFontSize fontWeight?: string + fontWeightLabel?: string labelString: string | null minValue?: -100 | 0 | 100 maxValue?: number @@ -20,6 +21,7 @@ export const ExampleInputStory: Story<{ fontSize, fontSizeLabel, fontWeight, + fontWeightLabel, labelString, minValue, maxValue, @@ -34,6 +36,7 @@ export const ExampleInputStory: Story<{ fontSize={fontSize} fontSizeLabel={fontSizeLabel} fontWeight={fontWeight} + fontWeightLabel={fontWeightLabel} labelString={labelString} minValue={minValue} maxValue={maxValue} @@ -49,6 +52,7 @@ ExampleInputStory.args = { fontSize: FontSize.medium, fontSizeLabel: ExportedFontSize.medium, fontWeight: FontWeightObj.normal, + fontWeightLabel: ExportedFontWeightObj.normal, labelString: '', roundToNearest: "none", startingValue: 0, @@ -58,31 +62,59 @@ ExampleInputStory.argTypes = { fontSize: { control: { type: "select", - options: [FontSize.small, FontSize.medium, FontSize.large], - } + }, + options: [ + FontSize.small, + FontSize.medium, + FontSize.large + ], }, fontSizeLabel: { control: { type: "select", - options: [ExportedFontSize.small, ExportedFontSize.medium, ExportedFontSize.large], - } + }, + options: [ + ExportedFontSize.small, + ExportedFontSize.medium, + ExportedFontSize.large + ], }, fontWeight: { control: { type: "radio", - options: [FontWeightObj.normal, FontWeightObj.bold], - } + }, + options: [ + FontWeightObj.normal, + FontWeightObj.bold + ], + }, + fontWeightLabel: { + control: { + type: "radio", + }, + options: [ + ExportedFontWeightObj.normal, + ExportedFontWeightObj.bold + ], }, minValue: { control: { type: "select", - options: [-100, 0, 100], - } + }, + options: [ + -100, + 0, + 100 + ], }, roundToNearest: { control: { type: "select", - options: ["none", "ten", "hundred"], - } + }, + options: [ + "none", + "ten", + "hundred" + ], } } \ No newline at end of file diff --git a/src/components/ExampleInput.tsx b/src/components/ExampleInput.tsx index 50414cd..7c4864f 100644 --- a/src/components/ExampleInput.tsx +++ b/src/components/ExampleInput.tsx @@ -2,9 +2,10 @@ import React, {useState} from "react" import { ExportedFontSize, + ExportedFontWeightObj, ExportedButIgnored as _ExportedButIgnored, EXPORTED_MAX_VALUE as _EXPORTED_MAX_VALUE, -} from "../utils" +} from "../types" export enum FontSizeNoValues { small, @@ -29,6 +30,7 @@ export type ExampleInputProps = { fontSizeLabel?: ExportedFontSize // fontWeight?: typeof FontSizeObj[keyof typeof FontSizeObj] fontWeight?: string + fontWeightLabel?: string labelString: string | null minValue?: -100 | 0 | 100 maxValue?: number @@ -41,6 +43,7 @@ export function ExampleInput({ fontSize = FontSize.medium, fontSizeLabel = ExportedFontSize.medium, fontWeight = FontWeightObj.normal, + fontWeightLabel = ExportedFontWeightObj.normal, labelString, minValue, maxValue = MAX_VALUE, @@ -61,7 +64,7 @@ export function ExampleInput({ return (