import { camelCaseToDashCase, removeIllegalCharacters, dashCaseToUpperCamelCase, } from './string-utils' import { ComponentUIDL, UIDLStyleDefinitions, UIDLConditionalNode, UIDLElement, UIDLNode, UIDLStaticValue, UIDLAttributeValue, UIDLDynamicReference, UIDLRepeatContent, UIDLRepeatMeta, UIDLElementNode, UIDLDependency, UIDLStyleValue, UIDLStyleSheetContent, UIDLComponentStyleReference, UIDLRootComponent, UIDLResourceItem, GeneratorOptions, } from '@teleporthq/teleport-types' import { basename } from 'path' import { StringUtils } from '..' export const extractRoutes = (rootComponent: UIDLRootComponent) => { // Assuming root element starts with a UIDLElementNode const rootElement = rootComponent.node.content as UIDLElement // Look for conditional nodes in the first level children of the root element return rootElement.children.filter( (child) => child.type === 'conditional' && child.content.reference.type === 'dynamic' && child.content.reference.content.id === 'route' ) as UIDLConditionalNode[] } export const createWebComponentFriendlyName = (componentName: string) => { const dashCaseName = camelCaseToDashCase(componentName) if (dashCaseName.includes('-')) { return dashCaseName } return `app-${dashCaseName}` } export const setFriendlyOutputOptions = (uidl: ComponentUIDL) => { uidl.outputOptions = uidl.outputOptions || {} const defaultComponentName = 'AppComponent' const friendlyName = removeIllegalCharacters(uidl.name) || defaultComponentName if (!uidl.outputOptions.fileName) { uidl.outputOptions.fileName = camelCaseToDashCase(friendlyName) } if (!uidl.outputOptions.componentClassName) { uidl.outputOptions.componentClassName = dashCaseToUpperCamelCase(friendlyName) } // failsafe for invalid UIDL samples with illegal characters as element names // when used in projects, resolveLocalDependencies should handle this traverseElements(uidl.node, (element) => { if (element.dependency) { element.semanticType = dashCaseToUpperCamelCase( removeIllegalCharacters(element.semanticType) || defaultComponentName ) } else { element.semanticType = removeIllegalCharacters(element.semanticType) } }) } export const getComponentFileName = (component: ComponentUIDL) => { return component.outputOptions && component.outputOptions.fileName ? component.outputOptions.fileName : camelCaseToDashCase(getComponentClassName(component)) } export const getStyleFileName = (component: ComponentUIDL) => { const componentFileName = getComponentFileName(component) // If component meta style file name is not set, we default to the component file name return component.outputOptions && component.outputOptions.styleFileName ? component.outputOptions.styleFileName : componentFileName } export const getTemplateFileName = (component: ComponentUIDL) => { const componentFileName = getComponentFileName(component) // If component meta style file name is not set, we default to the component file name return component.outputOptions && component.outputOptions.templateFileName ? component.outputOptions.templateFileName : componentFileName } export const getComponentFolderPath = (component: ComponentUIDL) => component.outputOptions && component.outputOptions.folderPath ? component.outputOptions.folderPath : [] export const getComponentClassName = (component: ComponentUIDL) => { const componentName = component.outputOptions && component.outputOptions.componentClassName ? component.outputOptions.componentClassName : component.name // Failsafe for angular modules and other places where component names are computed without passing through mapping // "Component" will not exist when generating a component because the resolver checks for illegal class names if (componentName === 'Component') { return 'AppComponent' } return componentName } export const getRepeatIteratorNameAndKey = (meta: UIDLRepeatMeta = {}) => { const iteratorName = meta.iteratorName || 'item' const iteratorKey = meta.iteratorKey || (meta.useIndex ? 'index' : iteratorName) return { iteratorKey, iteratorName, } } export const prefixAssetsPath = ( originalString: string | undefined, assets: GeneratorOptions['assets'] = {} ) => { if (!originalString) { return originalString } if (typeof originalString !== 'string') { return originalString } if (!originalString?.startsWith('/')) { return originalString } const { prefix, mappings = {}, identifier } = assets const assetName = basename(originalString) const decodedAssetName = decodeURIComponent(assetName) // Extract the full path from the original string (normalize to remove leading slash) const fullPath = originalString.startsWith('/') ? originalString.slice(1) : originalString const decodedFullPath = decodeURIComponent(fullPath) /* If the value is missing from the mapping, it means - asset is missing in the project packer - It's not a asset and so we don't need to provide any mapping for it Note: We need to check for decoded asset name as well as for some special characters such as katakana / kanjis / hiraganas the src / url leading to the asset can be encoded and we need to check the decoded version against the asset mapping Try full-path lookup first (to handle duplicate filenames in different folders), then fall back to basename-only (for backward compatibility) */ if ( !(typeof mappings[fullPath] === 'string') && !(typeof mappings[decodedFullPath] === 'string') && !(typeof mappings[assetName] === 'string') && !(typeof mappings[decodedAssetName] === 'string') ) { return originalString } /* need to use either the original or decoded assetName to retrieve its mapping if there is one Priority: fullPath > decodedFullPath > assetName > decodedAssetName */ const assetNameUsedForMapping = typeof mappings[fullPath] === 'string' ? fullPath : typeof mappings[decodedFullPath] === 'string' ? decodedFullPath : typeof mappings[assetName] === 'string' ? assetName : decodedAssetName /* If the value from the mapping is an empty string we need to not join it in the return path as it would append a wrong / */ if (!mappings[assetNameUsedForMapping]) { if (!identifier) { return [prefix, assetName].join('/') } return [prefix, identifier, assetName].join('/') } if (!identifier) { return [prefix, mappings[assetNameUsedForMapping], assetName].join('/') } return [prefix, identifier, mappings[assetNameUsedForMapping], assetName].join('/') } // Clones existing objects while keeping the type cast export const cloneObject = (node: T): T => JSON.parse(JSON.stringify(node)) // This function parses all the UIDLNodes in a tree structure // enabling a function to be applied to each individual node export const traverseNodes = ( node: UIDLNode | UIDLComponentStyleReference, fn: (node: UIDLNode | UIDLComponentStyleReference, parentNode: UIDLNode) => void, parent: UIDLNode | null = null ) => { if (!node.type) { return } fn(node, parent) switch (node.type) { case 'element': // Check if this element node is wrapping a data-source node // This happens when node.content has a 'type' property that is a data-source type if ( node.content && typeof node.content === 'object' && 'type' in node.content && (node.content.type === 'data-source-item' || node.content.type === 'data-source-list') ) { // Treat the entire node.content as a data-source node traverseNodes(node.content as any, fn, node) break } const { attrs, children, style, abilities, referencedStyles } = node.content if (attrs) { Object.keys(attrs).forEach((attrKey) => { const attr = attrs[attrKey] if (attr.type === 'object') { return } traverseNodes(attr, fn, node) }) } if (referencedStyles && Object.keys(referencedStyles).length > 0) { Object.values(referencedStyles).forEach((styleRef) => { if (styleRef.content.mapType === 'inlined') { traverseStyleObject(styleRef.content.styles) } }) } if (style) { traverseStyleObject(style) } if (abilities?.link) { if (abilities.link.type === 'url') { traverseNodes(abilities.link.content?.url, fn, node) } else if (abilities.link.type === 'dynamic') { traverseNodes(abilities.link as UIDLNode, fn, node) } } if (children) { children.forEach((child) => { traverseNodes(child, fn, node) }) } break case 'cms-list': traverseNodes(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseNodes(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseNodes(node.content.nodes.loading, fn) } break case 'cms-list-repeater': traverseNodes(node.content.nodes.list, fn) if (node.content.nodes.empty) { traverseNodes(node.content.nodes.empty, fn) } if (node.content.nodes.loading) { traverseNodes(node.content.nodes.loading, fn) } break case 'cms-item': traverseNodes(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseNodes(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseNodes(node.content.nodes.loading, fn) } break case 'cms-mixed-type': if (node.content.nodes?.fallback) { traverseNodes(node.content.nodes.fallback, fn) } if (node.content.nodes?.error) { traverseNodes(node.content.nodes.error, fn) } Object.keys(node.content?.mappings || {}).forEach((key) => { traverseNodes(node.content.mappings[key], fn) }) break case 'data-source-item': if (node.content.nodes?.success) { traverseNodes(node.content.nodes.success, fn) } if (node.content.nodes?.error) { traverseNodes(node.content.nodes.error, fn) } if (node.content.nodes?.loading) { traverseNodes(node.content.nodes.loading, fn) } if (node.content.children) { node.content.children.forEach((child) => { traverseNodes(child, fn, node) }) } break case 'data-source-list': if (node.content.nodes?.success) { traverseNodes(node.content.nodes.success, fn) } if (node.content.nodes?.error) { traverseNodes(node.content.nodes.error, fn) } if (node.content.nodes?.loading) { traverseNodes(node.content.nodes.loading, fn) } if (node.content.children) { node.content.children.forEach((child) => { traverseNodes(child, fn, node) }) } break case 'repeat': traverseNodes(node.content.node, fn, node) traverseNodes(node.content.dataSource, fn, node) break case 'conditional': traverseNodes(node.content.node, fn, node) traverseNodes(node.content.reference, fn, node) break case 'slot': if (node.content.fallback) { traverseNodes(node.content.fallback, fn, node) } break case 'static': case 'comp-style': case 'dynamic': case 'import': case 'expr': case 'raw': case 'inject': break default: throw new Error( `traverseNodes was given an unsupported node type: ${JSON.stringify(node, null, 2)}` ) } } export const traverseResources = ( node: UIDLNode, fn: (node: UIDLResourceItem, parentNode: UIDLNode) => void ) => { switch (node.type) { case 'element': // Check if this element node is wrapping a data-source node if ( node.content && typeof node.content === 'object' && 'type' in node.content && (node.content.type === 'data-source-item' || node.content.type === 'data-source-list') ) { // Treat the entire node.content as a data-source node traverseResources(node.content as any, fn) break } const { children } = node.content if (children) { children.forEach((child) => { traverseResources(child, fn) }) } break case 'cms-list': traverseResources(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseResources(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseResources(node.content.nodes.loading, fn) } break case 'cms-list-repeater': traverseResources(node.content.nodes.list, fn) if (node.content.nodes.empty) { traverseResources(node.content.nodes.empty, fn) } if (node.content.nodes.loading) { traverseResources(node.content.nodes.loading, fn) } break case 'cms-item': traverseResources(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseResources(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseResources(node.content.nodes.loading, fn) } break case 'cms-mixed-type': if (node.content.nodes?.fallback) { traverseResources(node.content.nodes.fallback, fn) } if (node.content.nodes?.error) { traverseResources(node.content.nodes.error, fn) } Object.keys(node.content?.mappings || {}).forEach((key) => { traverseResources(node.content.mappings[key], fn) }) break case 'data-source-item': if (node.content.nodes?.success) { traverseResources(node.content.nodes.success, fn) } if (node.content.nodes?.error) { traverseResources(node.content.nodes.error, fn) } if (node.content.nodes?.loading) { traverseResources(node.content.nodes.loading, fn) } break case 'data-source-list': if (node.content.nodes?.success) { traverseResources(node.content.nodes.success, fn) } if (node.content.nodes?.error) { traverseResources(node.content.nodes.error, fn) } if (node.content.nodes?.loading) { traverseResources(node.content.nodes.loading, fn) } break case 'repeat': traverseResources(node.content.node, fn) break case 'conditional': traverseResources(node.content.node, fn) break case 'slot': if (node.content.fallback) { traverseResources(node.content.fallback, fn) } break case 'static': case 'dynamic': case 'import': case 'raw': break default: throw new Error( `traverseResources was given an unsupported node type: ${JSON.stringify(node, null, 2)}` ) } } const traverseStyleObject = (style: UIDLStyleDefinitions) => { Object.keys(style).forEach((styleKey) => { const styleValue = style[styleKey] if (styleValue.type !== 'static' && styleValue.type !== 'dynamic') { throw new Error(`We support only 'static' and 'dynamic' content for styles \n Received \n ${JSON.stringify(styleValue, null, 2)}`) } }) } // Parses a node structure recursively and applies a function to each UIDLElement instance export const traverseElements = (node: UIDLNode, fn: (element: UIDLElement) => void) => { if (!node || !node.type) { return } switch (node.type) { case 'element': // Check if this element node is wrapping a data-source node if ( node.content && typeof node.content === 'object' && 'type' in node.content && (node.content.type === 'data-source-item' || node.content.type === 'data-source-list') ) { // Treat the entire node.content as a data-source node traverseElements(node.content as any, fn) break } fn(node.content) if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseElements(attrValue, fn) } } } if (node.content.children) { node.content.children.forEach((child) => { traverseElements(child, fn) }) } break case 'cms-list': traverseElements(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseElements(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseElements(node.content.nodes.loading, fn) } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseElements(attrValue, fn) } } } break case 'cms-list-repeater': traverseElements(node.content.nodes.list, fn) if (node.content.nodes.empty) { traverseElements(node.content.nodes.empty, fn) } if (node.content.nodes.loading) { traverseElements(node.content.nodes.loading, fn) } break case 'cms-item': traverseElements(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseElements(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseElements(node.content.nodes.loading, fn) } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseElements(attrValue, fn) } } } break case 'cms-mixed-type': if (node.content.nodes?.fallback) { traverseElements(node.content.nodes.fallback, fn) } if (node.content.nodes?.error) { traverseElements(node.content.nodes.error, fn) } Object.keys(node.content?.mappings || {}).forEach((key) => { traverseElements(node.content.mappings[key], fn) }) if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseElements(attrValue, fn) } } } break case 'data-source-item': if (node.content.nodes?.success) { traverseElements(node.content.nodes.success, fn) } if (node.content.nodes?.error) { traverseElements(node.content.nodes.error, fn) } if (node.content.nodes?.loading) { traverseElements(node.content.nodes.loading, fn) } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseElements(attrValue, fn) } } } break case 'data-source-list': if (node.content.nodes?.success) { traverseElements(node.content.nodes.success, fn) } if (node.content.nodes?.error) { traverseElements(node.content.nodes.error, fn) } if (node.content.nodes?.loading) { traverseElements(node.content.nodes.loading, fn) } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseElements(attrValue, fn) } } } break case 'repeat': traverseElements(node.content.node, fn) break case 'conditional': traverseElements(node.content.node, fn) break case 'slot': if (node.content.fallback) { traverseElements(node.content.fallback, fn) } break case 'static': case 'dynamic': case 'inject': case 'raw': case 'expr': break default: throw new Error( `traverseElements was given an unsupported node type : \n ${JSON.stringify(node, null, 2)}` ) } } export const traverseRepeats = (node: UIDLNode, fn: (element: UIDLRepeatContent) => void) => { if (!node || !node.type) { return } switch (node.type) { case 'element': // Check if this element node is wrapping a data-source node if ( node.content && typeof node.content === 'object' && 'type' in node.content && (node.content.type === 'data-source-item' || node.content.type === 'data-source-list') ) { // Treat the entire node.content as a data-source node traverseRepeats(node.content as any, fn) break } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseRepeats(attrValue, fn) } } } if (node.content.children) { node.content.children.forEach((child) => { traverseRepeats(child, fn) }) } break case 'cms-list': traverseRepeats(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseRepeats(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseRepeats(node.content.nodes.loading, fn) } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseRepeats(attrValue, fn) } } } break case 'cms-list-repeater': traverseRepeats(node.content.nodes.list, fn) if (node.content.nodes.empty) { traverseRepeats(node.content.nodes.empty, fn) } if (node.content.nodes.loading) { traverseRepeats(node.content.nodes.loading, fn) } break case 'cms-item': traverseRepeats(node.content.nodes.success, fn) if (node.content.nodes.error) { traverseRepeats(node.content.nodes.error, fn) } if (node.content.nodes.loading) { traverseRepeats(node.content.nodes.loading, fn) } if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseRepeats(attrValue, fn) } } } break case 'cms-mixed-type': if (node.content.nodes?.fallback) { traverseRepeats(node.content.nodes.fallback, fn) } if (node.content.nodes?.error) { traverseRepeats(node.content.nodes.error, fn) } Object.keys(node.content?.mappings || {}).forEach((key) => { traverseRepeats(node.content.mappings[key], fn) }) if (node.content.attrs) { for (const attrKey of Object.keys(node.content.attrs)) { const attrValue = node.content.attrs[attrKey] if (attrValue.type === 'element') { traverseRepeats(attrValue, fn) } } } break case 'repeat': fn(node.content) traverseRepeats(node.content.node, fn) break case 'conditional': traverseRepeats(node.content.node, fn) break case 'slot': if (node.content.fallback) { traverseRepeats(node.content.fallback, fn) } break case 'data-source-item': if ((node as any).content.nodes?.success) { traverseRepeats((node as any).content.nodes.success, fn) } if ((node as any).content.nodes?.error) { traverseRepeats((node as any).content.nodes.error, fn) } if ((node as any).content.nodes?.loading) { traverseRepeats((node as any).content.nodes.loading, fn) } break case 'data-source-list': if ((node as any).content.nodes?.success) { traverseRepeats((node as any).content.nodes.success, fn) } if ((node as any).content.nodes?.error) { traverseRepeats((node as any).content.nodes.error, fn) } if ((node as any).content.nodes?.loading) { traverseRepeats((node as any).content.nodes.loading, fn) } break case 'static': case 'dynamic': case 'raw': case 'expr': case 'inject': break default: throw new Error( `traverseRepeats was given an unsupported node type: ${node.type} - ${JSON.stringify( node, null, 2 )}` ) } } interface SplitResponse { staticStyles: UIDLStyleDefinitions dynamicStyles: UIDLStyleDefinitions tokenStyles: UIDLStyleDefinitions } export const splitDynamicAndStaticStyles = ( style: UIDLStyleDefinitions | Record ): SplitResponse => { const responsePayload: SplitResponse = { staticStyles: {}, dynamicStyles: {}, tokenStyles: {} } Object.keys(style).reduce((acc: SplitResponse, styleKey) => { const styleValue = style[styleKey] const { staticStyles, dynamicStyles, tokenStyles } = acc switch (styleValue.type) { case 'dynamic': if (styleValue.content.referenceType === 'token') { tokenStyles[styleKey] = styleValue return acc } dynamicStyles[styleKey] = styleValue return acc case 'static': staticStyles[styleKey] = styleValue return acc default: throw new Error( `splitDynamicAndStaticStyles encountered an unknown style definition ${JSON.stringify( styleValue, null, 2 )}` ) } }, responsePayload) return responsePayload } // removes all the dynamic styles from the style object, including the nested structures export const cleanupDynamicStyles = (style: UIDLStyleDefinitions): UIDLStyleDefinitions => { return Object.keys(style).reduce((resultedStyles: UIDLStyleDefinitions, styleKey: string) => { const styleValue = style[styleKey] switch (styleValue.type) { case 'dynamic': return resultedStyles case 'static': resultedStyles[styleKey] = styleValue return resultedStyles default: throw new Error( `cleanupDynamicStyles encountered an unknown style definition ${JSON.stringify( styleValue, null, 2 )}` ) } }, {}) } // Traverses the style object and applies the convert funtion to all the dynamic styles export const transformDynamicStyles = ( style: UIDLStyleDefinitions, // tslint:disable-next-line no-any transform: (value: UIDLDynamicReference, key?: string) => any // tslint:disable-next-line no-any ): Record => { return Object.keys(style).reduce((resultedStyles: Record, styleKey) => { const styleValue = style[styleKey] switch (styleValue.type) { case 'dynamic': resultedStyles[styleKey] = transform(styleValue, styleKey) return resultedStyles case 'static': resultedStyles[styleKey] = styleValue.content return resultedStyles default: throw new Error( `transformDynamicStyles encountered an unknown style definition ${JSON.stringify( styleValue, null, 2 )}` ) } }, {}) } /** * Transform properties like * $props.something * $local.something * $state.something * * Into their json alternative which is used in beta release/0.6 and * later. */ export const transformStringAssignmentToJson = ( declaration: string | number ): UIDLStaticValue | UIDLStyleValue => { if (typeof declaration === 'number') { return { type: 'static', content: declaration, } } const parts = declaration.split('.') const prefix = parts[0] const path = parts.slice(1).join('.') if (['$props', '$state', '$local'].indexOf(prefix) !== -1) { let referenceType: 'prop' | 'state' | 'local' = 'prop' if (prefix !== '$props') { referenceType = prefix.replace('$', '') as 'state' | 'local' } return { type: 'dynamic', content: { referenceType, id: StringUtils.createStateOrPropStoringValue(path), }, } } return { type: 'static', content: declaration, } } export const transformStylesAssignmentsToJson = ( styleObject: Record ): UIDLStyleDefinitions => { const newStyleObject: UIDLStyleDefinitions = {} Object.keys(styleObject).reduce((acc, key) => { const styleContentAtKey = styleObject[key] const entityType = typeof styleContentAtKey if (['string', 'number'].indexOf(entityType) !== -1) { acc[key] = transformStringAssignmentToJson(styleContentAtKey as string | number) return acc } if (!Array.isArray(styleContentAtKey) && entityType === 'object') { // if this value is already properly declared, make sure it is not const { type, content } = styleContentAtKey as UIDLStaticValue | UIDLDynamicReference if (type === 'static') { acc[key] = styleContentAtKey as UIDLStaticValue return acc } if (type === 'dynamic' && content.referenceType !== 'global') { if (['state', 'prop'].includes(content?.referenceType)) { acc[key] = { type, content: { ...content, id: generateIdWithRefPath(content.id, content.refPath), }, } } else { acc[key] = styleContentAtKey as UIDLDynamicReference } } return acc } throw new Error( `transformStylesAssignmentsToJson encountered a style value that is not supported ${JSON.stringify( styleContentAtKey, null, 2 )}` ) }, newStyleObject) return newStyleObject } export const generateIdWithRefPath = (contentId: string, refPath?: string[]) => { let processedId = contentId if (refPath) { const processedRefPath = refPath.reduce((acc, path) => { return `${acc}?.['${path}']` }, '') // This is a bit ugly, but in some cases props are parsed twice or already generated. // To avoid possible bugs, this should be a good safety measure if (!processedId.includes(processedRefPath)) { processedId = `${processedId}${processedRefPath}` } } return StringUtils.createStateOrPropStoringValue(processedId) } /* All the props passed to the components are transformed to a unique case to minimize the collision of using cameCalse in one place and dashCase in another place. So, all the attrs that are being passed to the local compoenents need to be transformed since these are basically props. */ export const transformAttributesAssignmentsToJson = ( attributesObject: Record, isLocalComponent = false ): Record => { const newAttrObject: Record = {} Object.keys(attributesObject).reduce((acc, key) => { const attributeContent = attributesObject[key] const entityType = typeof attributeContent if (['string', 'number'].indexOf(entityType) !== -1) { const propKey = isLocalComponent ? StringUtils.createStateOrPropStoringValue(key) : key acc[propKey] = transformStringAssignmentToJson( attributeContent as string | number ) as UIDLAttributeValue return acc } if (!Array.isArray(attributeContent) && entityType === 'object') { const { type } = attributeContent as UIDLAttributeValue switch (type) { case 'import': case 'static': case 'raw': case 'expr': { const propKey = isLocalComponent ? StringUtils.createStateOrPropStoringValue(key) : key acc[propKey] = attributeContent as UIDLAttributeValue return acc } case 'comp-style': { acc[key] = { type: 'comp-style', content: StringUtils.createStateOrPropStoringValue( (attributeContent as UIDLComponentStyleReference).content ), } return acc } case 'dynamic': { const { content } = attributeContent as UIDLDynamicReference // global id's dont need to be transformed. As they are constants and each one is handled by the generator // depending on the context and the framework. if ( ['state', 'prop'].includes(content?.referenceType) && content.referenceType !== 'global' ) { acc[key] = { type, content: { ...content, id: generateIdWithRefPath(content.id, content.refPath), }, } } else { acc[key] = attributeContent as UIDLAttributeValue } return acc } case 'object': case 'element': acc[key] = attributeContent as UIDLAttributeValue return acc default: { throw new Error( `transformAttributesAssignmentsToJson encountered a style value that is not supported ${JSON.stringify( attributeContent, null, 2 )}` ) } } } }, newAttrObject) return newAttrObject } export const findFirstElementNode = (node: UIDLNode): UIDLElementNode => { switch (node.type) { case 'element': return node case 'static': case 'dynamic': case 'slot': throw new Error('UIDL does not have any element node') case 'conditional': case 'repeat': const childNode = node.content.node return findFirstElementNode(childNode) default: throw new Error(`Invalid node type '${node}'`) } } export const removeChildNodes = ( node: UIDLNode, criteria: (element: UIDLNode) => boolean ): void => { switch (node.type) { case 'element': if (node.content.children) { // filter this level children node.content.children = node.content.children.filter((child) => !criteria(child)) // call function recursively for remaining children node.content.children.forEach((child) => removeChildNodes(child, criteria)) } break case 'repeat': removeChildNodes(node.content.node, criteria) break case 'cms-list': removeChildNodes(node.content.nodes.success, criteria) if (node.content.nodes.error) { removeChildNodes(node.content.nodes.error, criteria) } if (node.content.nodes.loading) { removeChildNodes(node.content.nodes.loading, criteria) } break case 'cms-list-repeater': removeChildNodes(node.content.nodes.list, criteria) if (node.content.nodes.empty) { removeChildNodes(node.content.nodes.empty, criteria) } if (node.content.nodes.loading) { removeChildNodes(node.content.nodes.loading, criteria) } break case 'cms-item': removeChildNodes(node.content.nodes.success, criteria) if (node.content.nodes.error) { removeChildNodes(node.content.nodes.error, criteria) } if (node.content.nodes.loading) { removeChildNodes(node.content.nodes.loading, criteria) } break case 'cms-mixed-type': if (node.content.nodes?.fallback) { removeChildNodes(node.content.nodes.fallback, criteria) } if (node.content.nodes?.error) { removeChildNodes(node.content.nodes.error, criteria) } Object.keys(node.content?.mappings || {}).forEach((key) => { removeChildNodes(node.content.mappings[key], criteria) }) break case 'conditional': removeChildNodes(node.content.node, criteria) break case 'slot': if (node.content.fallback) { removeChildNodes(node.content.fallback, criteria) } break case 'static': case 'dynamic': case 'raw': case 'expr': case 'inject': break default: throw new Error( `removeChildNodes was given an unsupported node type ${JSON.stringify(node, null, 2)}` ) } } export const extractExternalDependencies = (dependencies: Record) => { return Object.keys(dependencies) .filter((key) => { return dependencies[key].type === 'package' }) .reduce((acc: Record, key) => { const depInfo = dependencies[key] if (depInfo.path && depInfo.type === 'package') { acc[depInfo.path] = depInfo.version } return acc }, {}) }