Skip to content

Commit

Permalink
Add support for as expressions in TypeScript (#1248)
Browse files Browse the repository at this point in the history
* Add support for as expressions in TypeScript

* Use ternary for expression
  • Loading branch information
JakeLane committed Jul 10, 2022
1 parent d41813e commit 6c1ea7f
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-poets-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@compiled/babel-plugin': patch
---

Support Compiled in files with TypeScript as expressions
41 changes: 41 additions & 0 deletions packages/babel-plugin/src/__tests__/expression-evaluation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,5 +454,46 @@ describe('import specifiers', () => {
'ax(["_19pk19vg"])',
]);
});

it('statically evaluates a TS const expression', () => {
const actual = transform(
`
import '@compiled/react';
const styles = { color: 'red' } as const;
<div css={{ ...styles }} />;
`,
{
parserBabelPlugins: ['typescript', 'jsx'],
}
);

expect(actual).toIncludeMultiple(['._syaz5scu{color:red}', 'ax(["_syaz5scu"])']);
});

it('statically evaluates a TS const expression in a resolved binding', () => {
const actual = transform(
`
import { styled } from "@compiled/react";
const style = {
backgroundColor: 'red'
} as const;
const Component = styled.div({
"input": style,
});
`,
{
parserBabelPlugins: ['typescript', 'jsx'],
}
);

expect(actual).toIncludeMultiple([
'._1rwq5scu input{background-color:red}',
'ax(["_1rwq5scu", props.className]',
]);
});
});
});
5 changes: 4 additions & 1 deletion packages/babel-plugin/src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const transform = (code: string, options: TransformOptions = {}): string
pluginOptions.importReact === false
? [['@babel/preset-react', { runtime: 'automatic' }]]
: [],
parserOpts: {
plugins: pluginOptions.parserBabelPlugins,
},
});

if (!fileResult || !fileResult.code) {
Expand All @@ -52,5 +55,5 @@ export const transform = (code: string, options: TransformOptions = {}): string
codeSnippet = babelCode;
}

return pretty ? format(codeSnippet, { parser: 'babel' }) : codeSnippet;
return pretty ? format(codeSnippet, { parser: 'babel-ts' }) : codeSnippet;
};
4 changes: 4 additions & 0 deletions packages/babel-plugin/src/utils/css-builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,10 @@ export const buildCss = (node: t.Expression | t.Expression[], meta: Metadata): C
return { css: [{ type: 'unconditional', css: node.value }], variables: [] };
}

if (t.isTSAsExpression(node)) {
return buildCss(node.expression, meta);
}

if (t.isTemplateLiteral(node)) {
return extractTemplateLiteral(node, meta);
}
Expand Down
35 changes: 21 additions & 14 deletions packages/babel-plugin/src/utils/evaluate-expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,41 +128,48 @@ export const evaluateExpression = (
let value: t.Node | undefined | null = undefined;
let updatedMeta: Metadata = meta;

// TypeScript AST nodes can be skipped as we don't care about types
const targetExpression = t.isTSAsExpression(expression) ? expression.expression : expression;

// --------------
// NOTE: We are recursively calling evaluateExpression() which is then going to try and evaluate it
// multiple times. This may or may not be a performance problem - when looking for quick wins perhaps
// there is something we could do better here.
// --------------

if (t.isIdentifier(expression)) {
if (t.isIdentifier(targetExpression)) {
({ value, meta: updatedMeta } = traverseIdentifier(
expression,
targetExpression,
updatedMeta,
evaluateExpression
));
} else if (t.isMemberExpression(expression)) {
} else if (t.isMemberExpression(targetExpression)) {
({ value, meta: updatedMeta } = traverseMemberExpression(
expression,
targetExpression,
updatedMeta,
evaluateExpression
));
} else if (t.isFunction(targetExpression)) {
({ value, meta: updatedMeta } = traverseFunction(
targetExpression,
updatedMeta,
evaluateExpression
));
} else if (t.isFunction(expression)) {
({ value, meta: updatedMeta } = traverseFunction(expression, updatedMeta, evaluateExpression));
} else if (t.isCallExpression(expression)) {
} else if (t.isCallExpression(targetExpression)) {
({ value, meta: updatedMeta } = traverseCallExpression(
expression,
targetExpression,
updatedMeta,
evaluateExpression
));
} else if (t.isBinaryExpression(expression)) {
} else if (t.isBinaryExpression(targetExpression)) {
({ value, meta: updatedMeta } = traverseBinaryExpression(
expression,
targetExpression,
updatedMeta,
evaluateExpression
));
} else if (t.isUnaryExpression(expression)) {
} else if (t.isUnaryExpression(targetExpression)) {
({ value, meta: updatedMeta } = traverseUnaryExpression(
expression,
targetExpression,
updatedMeta,
evaluateExpression
));
Expand All @@ -184,10 +191,10 @@ export const evaluateExpression = (
// It's preferable to use the identifier than its result if it can't be statically evaluated.
// E.g. say we got the result of an identifier `foo` as `bar()` -- its more preferable to return
// `foo` instead of `bar()` for a single source of truth.
const babelEvaluatedNode = babelEvaluateExpression(value, updatedMeta, expression);
const babelEvaluatedNode = babelEvaluateExpression(value, updatedMeta, targetExpression);
return createResultPair(babelEvaluatedNode, updatedMeta);
}

const babelEvaluatedNode = babelEvaluateExpression(expression, updatedMeta);
const babelEvaluatedNode = babelEvaluateExpression(targetExpression, updatedMeta);
return createResultPair(babelEvaluatedNode, updatedMeta);
};

0 comments on commit 6c1ea7f

Please sign in to comment.