import generate from '@babel/generator' ; import template from '@babel/template' ; import type { NodePath , Visitor } from '@babel/traverse' ; import traverse from '@babel/traverse' ; import * as t from '@babel/types' ; import { transformCss } from '@compiled/css' ; import { unique } from '@compiled/utils' ; import isPropValid from '@emotion/is-prop-valid' ; import { PROPS_IDENTIFIER_NAME } from '../constants' ; import type { Metadata , Tag } from '../types' ; import type { CSSOutput , CssItem } from '../utils/types' ; import { pickFunctionBody } from './ast' ; import { buildCssVariables } from './build-css-variables' ; import { getItemCss } from './css-builders' ; import { hoistSheet } from './hoist-sheet' ; import { resolveIdentifierComingFromDestructuring } from './resolve-binding' ; import { transformCssItems } from './transform-css-items' ; export interface StyledTemplateOpts { /**
     * Class to be used for the CSS selector.
     */ classNames : t . Expression [ ] ; /**
     * Tag for the Styled Component, for example "div" or user defined component.
     */ tag : Tag ; /**
     * CSS variables to be passed to the `style` prop.
     */ variables : CSSOutput [ 'variables' ] ; /**
     * CSS sheets to be passed to the `CS` component.
     */ sheets : string [ ] ; } param variables param transform const styledStyleProp = ( variables , transform ? ) => { const props = [ t . spreadElement ( t . identifier ( 'style' ) ) ] ; return t . objectExpression ( props . concat ( buildCssVariables ( variables , transform ) ) ) ; } ; param tag const buildComponentTag = ( { name , type } ) => { return type === 'InBuiltComponent' ? `"${ name }"` : name ; } ; param node param nestedVisitor const traverseStyledArrowFunctionExpression = ( node , nestedVisitor ) => { traverse ( node , nestedVisitor ) ; return pickFunctionBody ( node ) ; } ; param node param nestedVisitor const traverseStyledBinaryExpression = ( node , nestedVisitor ) => { traverse ( node , { noScope : true , ArrowFunctionExpression ( path ) { path . traverse ( nestedVisitor ) ; path . replaceWith ( pickFunctionBody ( path . node ) ) ; path . stop ( ) ; } , } ) ; return node ; } ; param path const handleMemberExpressionInStyledInterpolation = ( path ) => { const memberExpressionKey = path . node . object ; const propsToDestructure = [ ] ; if ( t . isIdentifier ( memberExpressionKey ) ) { const traversedUpFunctionPath = path . find ( ( parentPath ) => parentPath . isFunction ( ) ) ; const memberExpressionKeyName = memberExpressionKey . name ; const isMemberExpressionNameTheSameAsFunctionFirstParam = traversedUpFunctionPath && t . isFunction ( traversedUpFunctionPath . node ) && t . isIdentifier ( traversedUpFunctionPath . node . params [ 0 ] ) && traversedUpFunctionPath . node . params [ 0 ] . name === memberExpressionKeyName ; if ( isMemberExpressionNameTheSameAsFunctionFirstParam ) { const memberExpressionValue = path . node . property ; if ( t . isIdentifier ( memberExpressionValue ) ) { const memberExpressionValueName = memberExpressionValue . name ; if ( isPropValid ( memberExpressionValueName ) ) { if ( memberExpressionKeyName !== PROPS_IDENTIFIER_NAME ) { path . replaceWith ( t . memberExpression ( t . identifier ( PROPS_IDENTIFIER_NAME ) , t . identifier ( memberExpressionValueName ) ) ) ; } } else { propsToDestructure . push ( memberExpressionValueName ) ; path . replaceWith ( memberExpressionValue ) ; } } } } return propsToDestructure ; } ; param path const handleDestructuringInStyledInterpolation = ( path ) => { const propsToDestructure = [ ] ; if ( path . parentPath && ! t . isObjectProperty ( path . parentPath . node ) ) { const traversedUpFunctionPath = path . find ( ( parentPath ) => parentPath . isFunction ( ) ) ; const firstFunctionParam = traversedUpFunctionPath && t . isFunction ( traversedUpFunctionPath . node ) && traversedUpFunctionPath . node . params [ 0 ] ; const resolvedDestructuringIdentifier = resolveIdentifierComingFromDestructuring ( { name : path . node . name , node : firstFunctionParam as t . Expression , resolveFor : 'value' , } ) ; if ( resolvedDestructuringIdentifier && t . isIdentifier ( resolvedDestructuringIdentifier . key ) ) { const resolvedDestructuringIdentifierKey = resolvedDestructuringIdentifier . key ; const resolvedDestructuringIdentifierKeyName = resolvedDestructuringIdentifierKey . name ; propsToDestructure . push ( resolvedDestructuringIdentifierKeyName ) ; if ( resolvedDestructuringIdentifierKeyName !== path . node . name ) { path . replaceWith ( t . identifier ( resolvedDestructuringIdentifierKeyName ) ) ; } } } return propsToDestructure ; } ; param opts { StyledTemplateOpts } param meta { Metadata } const styledTemplate = ( opts , meta ) : t . Node => { const nonceAttribute = meta . state . opts . nonce ? `nonce={${ meta . state . opts . nonce }}` : '' ; const propsToDestructure = [ ] ; const styleProp = opts . variables . length ? styledStyleProp ( opts . variables , ( node ) => { const nestedArrowFunctionExpressionVisitor = { noScope : true , MemberExpression ( path ) { const propsToDestructureFromMemberExpression = handleMemberExpressionInStyledInterpolation ( path ) ; propsToDestructure . push ( ... propsToDestructureFromMemberExpression ) ; } , Identifier ( path ) { const propsToDestructureFromIdentifier = handleDestructuringInStyledInterpolation ( path ) ; propsToDestructure . push ( ... propsToDestructureFromIdentifier ) ; } , } ; if ( t . isArrowFunctionExpression ( node ) ) { return traverseStyledArrowFunctionExpression ( node , nestedArrowFunctionExpressionVisitor ) ; } if ( t . isBinaryExpression ( node ) ) { return traverseStyledBinaryExpression ( node , nestedArrowFunctionExpressionVisitor ) ; } return node ; } ) : t . identifier ( 'style' ) ; let unconditionalClassNames = '' , conditionalClassNames = '' ; opts . classNames . forEach ( ( item ) => { if ( t . isStringLiteral ( item ) ) { unconditionalClassNames += `${ item . value } ` ; } else if ( t . isLogicalExpression ( item ) || t . isConditionalExpression ( item ) ) { conditionalClassNames += `${ generate ( item ) . code }, ` ; } } ) ; const classNames = `"${ unconditionalClassNames . trim ( ) }", ${ conditionalClassNames }` ; return template ( `
  forwardRef(({
    as: C = ${ buildComponentTag ( opts . tag ) },
    style,
    ${ unique ( propsToDestructure ) . map ( ( prop ) => prop + ',' ) . join ( '' ) }
    ...${ PROPS_IDENTIFIER_NAME }
  }, ref) => (
    <CC>
      <CS ${ nonceAttribute }>{%%cssNode%%}</CS>
      <C
        {...${ PROPS_IDENTIFIER_NAME }}
        style={%%styleProp%%}
        ref={ref}
        className={ax([${ classNames } ${ PROPS_IDENTIFIER_NAME }.className])}
      />
    </CC>
  ));
` , { plugins : [ 'jsx' ] , } ) ( { styleProp , cssNode : t . arrayExpression ( unique ( opts . sheets ) . map ( ( sheet ) => hoistSheet ( sheet , meta ) ) ) , } ) as t . Node ; } ; param tag { Tag } param cssOutput { CSSOutput } param meta { Metadata } export const buildStyledComponent = ( tag , cssOutput , meta ) : t . Node => { const unconditionalCss = [ ] ; const conditionalCss = [ ] ; cssOutput . css . forEach ( ( item ) => { if ( item . type === 'logical' || item . type === 'conditional' ) { conditionalCss . push ( item ) ; } else { unconditionalCss . push ( getItemCss ( item ) ) ; } } ) ; const uniqueUnconditionalCssOutput = transformCss ( unconditionalCss . join ( '' ) ) ; const conditionalCssOutput = transformCssItems ( conditionalCss ) ; const sheets = [ ... uniqueUnconditionalCssOutput . sheets , ... conditionalCssOutput . sheets ] ; const classNames = [ ... [ t . stringLiteral ( uniqueUnconditionalCssOutput . classNames . join ( ' ' ) ) ] , ... conditionalCssOutput . classNames , ] ; return styledTemplate ( { classNames , tag , sheets , variables : cssOutput . variables , } , meta ) ; } ;