diff --git a/src/index.test.js b/src/index.test.js index 63158bc..03c68d4 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -52,6 +52,12 @@ Index("no processors", async () => { theme: 1, }, }, + Button: { + instances: 1, + props: { + onClick: 1, + }, + }, Home: { instances: 1, props: {}, @@ -97,6 +103,7 @@ Index("single processor", async () => { Text: 2, App: 1, BasisProvider: 1, + Button: 1, Home: 1, Link: 1, div: 1, diff --git a/src/run.js b/src/run.js index 11133fb..f5e7752 100644 --- a/src/run.js +++ b/src/run.js @@ -35,6 +35,8 @@ async function run({ } let report = {}; + let styled = {}; + const { components, includeSubComponents, @@ -55,6 +57,7 @@ async function run({ importedFrom, getComponentName, report, + styled, getPropValue, }); } diff --git a/src/scan.js b/src/scan.js index b932e25..96ce1fc 100644 --- a/src/scan.js +++ b/src/scan.js @@ -10,7 +10,8 @@ const parseOptions = { function getComponentNameFromAST(nameObj) { switch (nameObj.type) { - case "JSXIdentifier": { + case "JSXIdentifier": + case "Identifier": { return nameObj.name; } @@ -72,6 +73,7 @@ function getInstanceInfo({ if (attribute.type === "JSXAttribute") { const { name, value } = attribute; const propName = name.name; + const propValue = customGetPropValue ? customGetPropValue({ node: value, @@ -99,6 +101,7 @@ function scan({ getComponentName = ({ imported, local }) => imported === "default" ? local : imported || local, report, + styled, getPropValue, }) { let ast; @@ -112,6 +115,22 @@ function scan({ const importsMap = {}; + const getComponentNamePath = (name) => { + const nameParts = name.split("."); + const [firstPart, ...restParts] = nameParts; + const actualFirstPart = importsMap[firstPart] + ? getComponentName({ + ...importsMap[firstPart], + }) + : firstPart; + + const componentParts = [actualFirstPart, ...restParts]; + const componentPath = componentParts.join(".components."); + const componentName = componentParts.join("."); + + return { componentParts, componentPath, componentName }; + }; + astray.walk(ast, { ImportDeclaration(node) { const { source, specifiers } = node; @@ -146,13 +165,41 @@ function scan({ } } }, + VariableDeclarator: { + exit(node) { + if (node.init?.type === "CallExpression") { + if ( + node.init?.callee.type === "Identifier" && + node.init?.callee.name === "styled" + ) { + const firstArg = node.init.arguments[0]; + const { componentPath } = getComponentNamePath(node.id.name); + let componentInfo = getObjectPath(report, componentPath); + + if (firstArg.type === "Identifier" || firstArg.type === "Literal") { + dset(styled, componentPath, firstArg); + + const styledName = firstArg.name || firstArg.value; + const { componentName: componentSName } = + getComponentNamePath(styledName); + + if (componentInfo && !componentInfo.styledFrom) { + componentInfo.styledFrom = componentSName; + } + } + } + } + }, + }, JSXOpeningElement: { exit(node) { const name = getComponentNameFromAST(node.name); const nameParts = name.split("."); const [firstPart, ...restParts] = nameParts; const actualFirstPart = importsMap[firstPart] - ? getComponentName(importsMap[firstPart]) + ? getComponentName({ + ...importsMap[firstPart], + }) : firstPart; const shouldReportComponent = () => { if (components) { @@ -204,7 +251,6 @@ function scan({ } const componentParts = [actualFirstPart, ...restParts]; - const componentPath = componentParts.join(".components."); const componentName = componentParts.join("."); let componentInfo = getObjectPath(report, componentPath); @@ -218,6 +264,14 @@ function scan({ componentInfo.instances = []; } + let styledPath = getObjectPath(styled, componentPath); + if (styledPath && !componentInfo.styledFrom) { + const styledName = styledPath.name || styledPath.value; + const { componentName: componentSName } = + getComponentNamePath(styledName); + componentInfo.styledFrom = componentSName; + } + const info = getInstanceInfo({ node, filePath, diff --git a/src/scan.test.js b/src/scan.test.js index be5423d..52ed72e 100644 --- a/src/scan.test.js +++ b/src/scan.test.js @@ -18,6 +18,7 @@ Scan.before((context) => { } = {} ) => { const report = {}; + const styled = {}; scan({ code, @@ -27,6 +28,8 @@ Scan.before((context) => { Header: true, Text: true, Input: true, + Button: true, + StyledButton: true, }, ...(components !== undefined && { components }), ...(includeSubComponents !== undefined && { includeSubComponents }), @@ -34,6 +37,7 @@ Scan.before((context) => { ...(getComponentName !== undefined && { getComponentName }), ...(getPropValue !== undefined && { getPropValue }), report, + styled, }); return report; @@ -65,7 +69,7 @@ Scan("unknown components", ({ getReport }) => { "unknown-components.js", `
- + Submit
` @@ -1122,4 +1126,127 @@ Scan("importAlias", ({ getReport }) => { }); }); +Scan("add styledFrom attribute for Styled Components", ({ getReport }) => { + const report = getReport( + "add-styled-from-attribute.js", + ` + const styled = (element: El, {children, ...argOpts}) => {children}; + + const Button = styled('button', {}); + + <> + + + ` + ); + + assert.equal(report, { + Button: { + instances: [ + { + props: {}, + propsSpread: false, + location: { + file: "add-styled-from-attribute.js", + start: { + line: 7, + column: 7, + }, + }, + }, + ], + styledFrom: "button", + }, + }); +}); + +Scan("styled component with custom getComponentName", ({ getReport }) => { + const report = getReport( + "styled-custom-get-component-name.js", + ` + import MyBox from "@my/design-system/Box"; + import { ImportedText as LocalText } from "@my/design-system/Text"; + import StyledButton from "@my/design-system/StyledButton"; + + + const styled = (element: El, {children, ...argOpts}) => {children}; + + const Button = styled(StyledButton, {}); + + <> + + + ); } +const Button = styled("button", {}); + export default Home;