Skip to content

Commit

Permalink
refactor some stuff around (#35)
Browse files Browse the repository at this point in the history
Did not change any logic, just moved/renamed functions out of parsePageFile.ts
  • Loading branch information
oshi97 committed Sep 7, 2022
1 parent fc0a639 commit e91c511
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 95 deletions.
19 changes: 19 additions & 0 deletions studio/studio-plugin/ts-morph/getComponentModuleName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PossibleModuleNames } from '../../shared/models'

export default function getComponentModuleName(
name: string,
imports: Record<string, string[]>,
isLayout: boolean
): PossibleModuleNames {
let moduleName = Object.keys(imports).find(importIdentifier => {
const importedNames = imports[importIdentifier]
return importedNames.includes(name)
})
if (!moduleName) {
throw new Error(`Could not find import path/module for component "${name}"`)
}
if (moduleName.startsWith('.')) {
moduleName = isLayout ? 'localLayouts' : 'localComponents'
}
return moduleName as PossibleModuleNames
}
30 changes: 30 additions & 0 deletions studio/studio-plugin/ts-morph/parseJsxAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { JsxOpeningElement, JsxSelfClosingElement, ts, JsxAttribute } from 'ts-morph'
import { ComponentMetadata, PropState } from '../../shared/models'
import { getPropName, getJsxAttributeValue, validatePropState } from '../common'

export default function parseJsxAttributes(
n: JsxOpeningElement | JsxSelfClosingElement,
componentMetaData: ComponentMetadata
): PropState {
const props = {}
n.getDescendantsOfKind(ts.SyntaxKind.JsxAttribute).forEach((jsxAttribute: JsxAttribute) => {
const propName = getPropName(jsxAttribute)
if (!propName) {
throw new Error('Could not parse jsx attribute prop name: ' + jsxAttribute.getFullText())
}
const propType = componentMetaData.propShape?.[propName]?.type
if (!propType) {
throw new Error('Could not find prop type for: ' + jsxAttribute.getFullText())
}
const propValue = getJsxAttributeValue(jsxAttribute)
const propState = {
type: propType,
value: propValue
}
if (!validatePropState(propState)) {
throw new Error(`Could not validate propState ${JSON.stringify(propState, null, 2)}`)
}
props[propName] = propState
})
return props
}
51 changes: 51 additions & 0 deletions studio/studio-plugin/ts-morph/parseLayoutState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ComponentState } from 'react'
import { SourceFile, JsxElement, JsxFragment, ts } from 'ts-morph'
import { v1 } from 'uuid'
import { getDefaultExport, getComponentName } from '../common'
import getComponentModuleName from './getComponentModuleName'

export default function parseLayoutState(
sourceFile: SourceFile,
imports: Record<string, string[]>
): { layoutState: ComponentState, layoutNode: JsxElement | JsxFragment } {
const defaultExport = getDefaultExport(sourceFile)
const returnStatement = defaultExport.getFirstDescendantByKind(ts.SyntaxKind.ReturnStatement)
if (!returnStatement) {
throw new Error('No return statement found for page')
}
const JsxNodeWrapper = returnStatement.getFirstChildByKind(ts.SyntaxKind.ParenthesizedExpression)
?? returnStatement
const topLevelJsxNode = JsxNodeWrapper.getChildren()
.find(n => n.getKind() === ts.SyntaxKind.JsxElement || n.getKind() === ts.SyntaxKind.JsxFragment)
if (!topLevelJsxNode) {
throw new Error('Unable to find top level JSX element or JsxFragment type from file.')
}

let layoutState: ComponentState
if (topLevelJsxNode.getKind() === ts.SyntaxKind.JsxElement) {
const name = getComponentName((topLevelJsxNode as JsxElement).getOpeningElement())
layoutState = {
name,
props: {},
uuid: v1(),
moduleName: 'builtIn'
}
const isBuiltinJsxElement = name.charAt(0) === name.charAt(0).toLowerCase()
if (!isBuiltinJsxElement && !['Fragment', 'React.Fragment'].includes(name)) {
layoutState.moduleName = getComponentModuleName(name, imports, true)
}
} else {
// This handles the React.Fragment shorthand <></>
layoutState = {
name: '',
props: {},
uuid: v1(),
moduleName: 'builtIn'
}
}

return {
layoutState,
layoutNode: topLevelJsxNode as JsxElement | JsxFragment
}
}
102 changes: 7 additions & 95 deletions studio/studio-plugin/ts-morph/parsePageFile.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { JsxAttribute, ts, SourceFile, JsxElement, JsxFragment, JsxOpeningElement, JsxSelfClosingElement } from 'ts-morph'
import { ComponentState, PossibleModuleNames, PageState, ComponentMetadata, PropState } from '../../shared/models'
import { getComponentName, getComponentNodes, getDefaultExport, getJsxAttributeValue, getPropName, validatePropState, getSourceFile } from '../common'
import { ts, JsxElement, JsxFragment } from 'ts-morph'
import { ComponentState, PageState } from '../../shared/models'
import { getComponentName, getComponentNodes, getSourceFile } from '../common'
import { v1 } from 'uuid'
import parseImports from './parseImports'
import { moduleNameToComponentMetadata } from '../componentMetadata'
import getComponentModuleName from './getComponentModuleName'
import parseLayoutState from './parseLayoutState'
import parseJsxAttributes from './parseJsxAttributes'

export default function parsePageFile(filePath: string): PageState {
const sourceFile = getSourceFile(filePath)
Expand Down Expand Up @@ -32,7 +35,7 @@ export default function parsePageFile(filePath: string): PageState {
const componentMetaData = moduleNameToComponentMetadata[moduleName][name]
componentData.props = componentMetaData.global
? componentMetaData.globalProps ?? {}
: parseComponentJsxAttributes(n, componentMetaData)
: parseJsxAttributes(n, componentMetaData)
componentsState.push(componentData)
})

Expand All @@ -41,94 +44,3 @@ export default function parsePageFile(filePath: string): PageState {
componentsState
}
}

function parseComponentJsxAttributes(
n: JsxOpeningElement | JsxSelfClosingElement,
componentMetaData: ComponentMetadata
): PropState {
const props = {}
n.getDescendantsOfKind(ts.SyntaxKind.JsxAttribute).forEach((jsxAttribute: JsxAttribute) => {
const propName = getPropName(jsxAttribute)
if (!propName) {
throw new Error('Could not parse jsx attribute prop name: ' + jsxAttribute.getFullText())
}
const propType = componentMetaData.propShape?.[propName]?.type
if (!propType) {
throw new Error('Could not find prop type for: ' + jsxAttribute.getFullText())
}
const propValue = getJsxAttributeValue(jsxAttribute)
const propState = {
type: propType,
value: propValue
}
if (!validatePropState(propState)) {
throw new Error(`Could not validate propState ${JSON.stringify(propState, null, 2)}`)
}
props[propName] = propState
})
return props
}

function parseLayoutState(
sourceFile: SourceFile,
imports: Record<string, string[]>
): { layoutState: ComponentState, layoutNode: JsxElement | JsxFragment } {
const defaultExport = getDefaultExport(sourceFile)
const returnStatement = defaultExport.getFirstDescendantByKind(ts.SyntaxKind.ReturnStatement)
if (!returnStatement) {
throw new Error('No return statement found for page')
}
const JsxNodeWrapper = returnStatement.getFirstChildByKind(ts.SyntaxKind.ParenthesizedExpression)
?? returnStatement
const topLevelJsxNode = JsxNodeWrapper.getChildren()
.find(n => n.getKind() === ts.SyntaxKind.JsxElement || n.getKind() === ts.SyntaxKind.JsxFragment)
if (!topLevelJsxNode) {
throw new Error('Unable to find top level JSX element or JsxFragment type from file.')
}

let layoutState: ComponentState
if (topLevelJsxNode.getKind() === ts.SyntaxKind.JsxElement) {
const name = getComponentName((topLevelJsxNode as JsxElement).getOpeningElement())
layoutState = {
name,
props: {},
uuid: v1(),
moduleName: 'builtIn'
}
const isBuiltinJsxElement = name.charAt(0) === name.charAt(0).toLowerCase()
if (!isBuiltinJsxElement && !['Fragment', 'React.Fragment'].includes(name)) {
layoutState.moduleName = getComponentModuleName(name, imports, true)
}
} else {
// This handles the React.Fragment shorthand <></>
layoutState = {
name: '',
props: {},
uuid: v1(),
moduleName: 'builtIn'
}
}

return {
layoutState,
layoutNode: topLevelJsxNode as JsxElement | JsxFragment
}
}

function getComponentModuleName(
name: string,
imports: Record<string, string[]>,
isLayout: boolean
): PossibleModuleNames {
let moduleName = Object.keys(imports).find(importIdentifier => {
const importedNames = imports[importIdentifier]
return importedNames.includes(name)
})
if (!moduleName) {
throw new Error(`Could not find import path/module for component "${name}"`)
}
if (moduleName.startsWith('.')) {
moduleName = isLayout ? 'localLayouts' : 'localComponents'
}
return moduleName as PossibleModuleNames
}

0 comments on commit e91c511

Please sign in to comment.