Skip to content

Commit

Permalink
fix(babel): usage of +exp in interpolations (#1292)
Browse files Browse the repository at this point in the history
* Ignore babel erroneously thinking identifiers in unary expressions are bindings

* fix: add exception for delete operator

* fix tests

---------

Co-authored-by: Anton Evzhakov <anton@evz.name>
  • Loading branch information
GabbeV and Anber committed Jul 22, 2023
1 parent ba8ab67 commit dca076e
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/tame-impalas-happen.md
@@ -0,0 +1,5 @@
---
'@linaria/utils': patch
---

All references in unary operators should be treated as references, not as bindings. That fixes usages of `+exp` in interpolations.
16 changes: 16 additions & 0 deletions packages/testkit/src/__snapshots__/babel.test.ts.snap
Expand Up @@ -2265,6 +2265,22 @@ CSS:
Dependencies: NA
`;
exports[`strategy shaker should process unary expressions in interpolation 1`] = `
"export const class1 = \\"class1_c13jq05\\";
export const class2 = \\"class2_c1vhermz\\";"
`;
exports[`strategy shaker should process unary expressions in interpolation 2`] = `
CSS:
.class1_c13jq05 {width:1337px;}
.class2_c1vhermz {width:1337px;}
Dependencies: NA
`;
Expand Down
18 changes: 18 additions & 0 deletions packages/testkit/src/babel.test.ts
Expand Up @@ -2579,6 +2579,24 @@ describe('strategy shaker', () => {
expect(metadata).toMatchSnapshot();
});

it('should process unary expressions in interpolation', async () => {
const { code, metadata } = await transform(
dedent`
import { css } from "@linaria/core";
let size = 1337;
size += 0;
export const class1 = css\`width:${'${+size}'}px;\`;
export const class2 = css\`width:${'${size}'}px;\`;
`,
[evaluator]
);

expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});

it('should interpolate imported components', async () => {
const { code, metadata } = await transform(
dedent`
Expand Down
26 changes: 20 additions & 6 deletions packages/utils/src/findIdentifiers.ts
@@ -1,25 +1,39 @@
import type { NodePath } from '@babel/traverse';
import type { Node, Identifier, JSXIdentifier } from '@babel/types';
import type {
Node,
Identifier,
JSXIdentifier,
UnaryExpression,
} from '@babel/types';

import { getScope } from './getScope';

type FindType = 'binding' | 'both' | 'referenced';

function isInVoid(path: NodePath): boolean {
return path.parentPath?.isUnaryExpression({ operator: 'void' }) ?? false;
function isInUnary<T extends NodePath>(
path: T
): path is T & { parentPath: NodePath<UnaryExpression> } {
return path.parentPath?.isUnaryExpression() ?? false;
}

// It's possible for non-strict mode code to have variable deletions.
function isInDelete(path: { parentPath: NodePath<UnaryExpression> }): boolean {
return path.parentPath.node.operator === 'delete';
}

function isBindingIdentifier(path: NodePath): path is NodePath<Identifier> {
return path.isBindingIdentifier() && !isInVoid(path);
return path.isBindingIdentifier() && (!isInUnary(path) || isInDelete(path));
}

function isReferencedIdentifier(
path: NodePath
): path is NodePath<Identifier | JSXIdentifier> {
return path.isReferencedIdentifier() || isInVoid(path);
return (
path.isReferencedIdentifier() || (isInUnary(path) && !isInDelete(path))
);
}

// For some reasons, `isBindingIdentifier` returns true for identifiers inside `void` expressions.
// For some reasons, `isBindingIdentifier` returns true for identifiers inside unary expressions.
const checkers: Record<FindType, (ex: NodePath) => boolean> = {
binding: (ex) => isBindingIdentifier(ex),
both: (ex) => isBindingIdentifier(ex) || isReferencedIdentifier(ex),
Expand Down

0 comments on commit dca076e

Please sign in to comment.