Skip to content

Commit

Permalink
move logic to spreadSxProp util
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp committed Apr 26, 2024
1 parent c1ec425 commit 49fab1b
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 103 deletions.
107 changes: 4 additions & 103 deletions packages/pigment-css-react/src/processors/sx.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import type { NodePath } from '@babel/core';
import type {
CallExpression,
Expression,
JSXAttribute,
JSXOpeningElement,
ObjectExpression,
ObjectProperty,
} from '@babel/types';
import type { CallExpression, Expression } from '@babel/types';
import {
validateParams,
type Params,
Expand All @@ -18,6 +11,7 @@ import type { IOptions } from './styled';
import { processCssObject } from '../utils/processCssObject';
import { cssFnValueToVariable } from '../utils/cssFnValueToVariable';
import BaseProcessor from './base-processor';
import spreadSxProp from '../utils/spreadSxProp';

export class SxProcessor extends BaseProcessor {
sxArguments: ExpressionValue[] = [];
Expand Down Expand Up @@ -153,103 +147,10 @@ export class SxProcessor extends BaseProcessor {
*/
this.replacer((_tagPath) => {
const tagPath = _tagPath as NodePath<CallExpression>;
const target = tagPath.findParent(
(p) => p.isJSXOpeningElement() || p.isObjectExpression(),
) as NodePath<JSXOpeningElement> | NodePath<ObjectExpression> | null;

if (target?.isJSXOpeningElement()) {
const attributes: any[] = [];
let sxAttribute: undefined | NodePath<JSXAttribute>;
(target.get('attributes') as NodePath[]).forEach((attr) => {
if (attr.isJSXAttribute() && attr.node.name.name === 'sx') {
sxAttribute = attr;
} else if (attr.isJSXSpreadAttribute()) {
let containRuntimeSx = false;
attr.traverse({
CallExpression(path) {
const callee = path.get('callee');
if (callee.isIdentifier() && callee.node.name.startsWith('_sx')) {
containRuntimeSx = true;
}
},
});
if (!containRuntimeSx) {
attributes.push(t.spreadElement(attr.node.argument));
}
} else if (
attr.isJSXAttribute() &&
(attr.node.name.name === 'className' || attr.node.name.name === 'style')
) {
const value = attr.get('value');
if (value.isJSXExpressionContainer()) {
attributes.push(
t.objectProperty(
t.identifier(attr.node.name.name),
value.get('expression').node as any,
),
);
} else {
attributes.push(
t.objectProperty(t.identifier(attr.node.name.name), attr.node.value as any),
);
}
}
});
if (sxAttribute) {
const expContainer = sxAttribute.get('value');
if (expContainer.isJSXExpressionContainer()) {
target.node.attributes.push(
t.jsxSpreadAttribute(expContainer.node.expression as Expression),
);
}
sxAttribute.remove();
}
tagPath.node.arguments.push(t.objectExpression(attributes));
return tagPath.node;
}
spreadSxProp(tagPath);

if (target?.isObjectExpression()) {
const properties: any[] = [];
let sxProperty: undefined | NodePath<ObjectProperty>;
(target.get('properties') as NodePath[]).forEach((prop) => {
if (
prop.isObjectProperty() &&
prop.node.key.type === 'Identifier' &&
prop.node.key.name === 'sx'
) {
sxProperty = prop;
} else if (prop.isSpreadElement()) {
let containRuntimeSx = false;
prop.traverse({
CallExpression(path) {
const callee = path.get('callee');
if (callee.isIdentifier() && callee.node.name.startsWith('_sx')) {
containRuntimeSx = true;
}
},
});
if (!containRuntimeSx) {
properties.push(t.spreadElement(prop.node.argument));
}
} else if (
prop.isObjectProperty() &&
prop.node.key.type === 'Identifier' &&
(prop.node.key.name === 'className' || prop.node.key.name === 'style')
) {
properties.push(
t.objectProperty(t.identifier(prop.node.key.name), prop.node.value as any),
);
}
});
if (sxProperty) {
const expression = sxProperty.get('value');
target.node.properties.push(t.spreadElement(expression.node as CallExpression));
sxProperty.remove();
}
tagPath.node.arguments.push(t.objectExpression(properties));
return tagPath.node;
}
throw tagPath.buildCodeFrameError('`sx` prop must be used with a JSX element or an object.');
return tagPath.node;
}, false);
}

Expand Down
130 changes: 130 additions & 0 deletions packages/pigment-css-react/src/utils/spreadSxProp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { NodePath, types as astService } from '@babel/core';
import {
CallExpression,
Expression,
JSXAttribute,
JSXOpeningElement,
JSXSpreadAttribute,
ObjectExpression,
ObjectMethod,
ObjectProperty,
SpreadElement,
} from '@babel/types';

function isSxProp(path: NodePath) {
if (path.isJSXAttribute() && path.node.name.name === 'sx') {
return true;
}
if (
path.isObjectProperty() &&
path.node.key.type === 'Identifier' &&
path.node.key.name === 'sx'
) {
return true;
}
return false;
}

function isSpreadExpression(path: NodePath) {
return path.isSpreadElement() || path.isJSXSpreadAttribute();
}

function getProps(paths: NodePath[]) {
const props: Array<SpreadElement | ObjectProperty> = [];
let sxPath: undefined | NodePath<JSXAttribute> | NodePath<ObjectProperty>;
paths.forEach((attr) => {
if (isSxProp(attr)) {
sxPath = attr as NodePath<JSXAttribute> | NodePath<ObjectProperty>;
} else if (isSpreadExpression(attr)) {
let containRuntimeSx = false;
attr.traverse({
CallExpression(path) {
const callee = path.get('callee');
if (callee.isIdentifier() && callee.node.name.startsWith('_sx')) {
containRuntimeSx = true;
}
},
});
if (!containRuntimeSx) {
props.push(
astService.spreadElement(
(attr as NodePath<JSXSpreadAttribute | SpreadElement>).node.argument,
),
);
}
} else if (
attr.isJSXAttribute() &&
(attr.node.name.name === 'className' || attr.node.name.name === 'style')
) {
const value = attr.get('value');
if (value.isJSXExpressionContainer()) {
props.push(
astService.objectProperty(
astService.identifier(attr.node.name.name),
value.get('expression').node as any,
),
);
} else {
props.push(
astService.objectProperty(
astService.identifier(attr.node.name.name),
attr.node.value as any,
),
);
}
} else if (
attr.isObjectProperty() &&
attr.node.key.type === 'Identifier' &&
(attr.node.key.name === 'className' || attr.node.key.name === 'style')
) {
props.push(
astService.objectProperty(
astService.identifier(attr.node.key.name),
attr.node.value as any,
),
);
}
});
return { props, sxPath };
}

/**
* Convert the sx prop that contains the sx() call to runtime {...sx()} spread.
*
* It will try to find the sibling `className` and `style` props and put them in the second argument of
* the runtime sx call.
*/
export default function spreadSxProp(tagPath: NodePath<CallExpression>) {
const target = tagPath.findParent((p) => p.isJSXOpeningElement() || p.isObjectExpression()) as
| NodePath<JSXOpeningElement>
| NodePath<ObjectExpression>
| null;
if (!target) {
return;
}
let paths:
| NodePath<JSXAttribute | JSXSpreadAttribute>[]
| NodePath<ObjectProperty | SpreadElement | ObjectMethod>[] = [];
if (target.isJSXOpeningElement()) {
paths = target.get('attributes');
}
if (target.isObjectExpression()) {
paths = target.get('properties');
}
const { props, sxPath } = getProps(paths);
if (sxPath) {
const expression = sxPath.get('value');
if ('node' in expression) {
if (target.isObjectExpression()) {
target.node.properties.push(astService.spreadElement(expression.node as Expression));
}
if (target.isJSXOpeningElement() && expression.isJSXExpressionContainer()) {
target.node.attributes.push(
astService.jsxSpreadAttribute(expression.node.expression as Expression),
);
}
}
sxPath.remove();
}
tagPath.node.arguments.push(astService.objectExpression(props));
}

0 comments on commit 49fab1b

Please sign in to comment.