Skip to content

Commit de29e05

Browse files
committed
Respect reachable local style placement
1 parent f2fca0a commit de29e05

3 files changed

Lines changed: 204 additions & 14 deletions

File tree

packages/babel-plugin-rn-stylename-inline/__tests__/__snapshots__/index.spec.js.snap

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,126 @@ export default function Card({ ready, pad }) {
10801080
}
10811081
10821082
1083+
`;
1084+
1085+
exports[`@cssxjs/babel-plugin-rn-stylename-inline Reachable local css interpolation after early return. Should error: Reachable local css interpolation after early return. Should error 1`] = `
1086+
1087+
import React from 'react'
1088+
import { css } from 'cssxjs'
1089+
import { View } from 'react-native'
1090+
import { useThemeColor } from './theme'
1091+
1092+
export default function Card ({ ready }) {
1093+
if (!ready) return <View styleName='loader' />
1094+
const color = useThemeColor('primary')
1095+
1096+
css\`
1097+
.root {
1098+
color: \${color};
1099+
}
1100+
\`
1101+
1102+
return <View styleName='root' />
1103+
}
1104+
1105+
↓ ↓ ↓ ↓ ↓ ↓
1106+
1107+
SyntaxError: unknown file: [@cssxjs/babel-plugin-rn-stylename-inline] Local css/styl templates must be declared before the first return, unless they are trailing CSSX style blocks at the end of the component.
1108+
Move this template before the first return, or place it after all returns as the final component statement.
1109+
8 | const color = useThemeColor('primary')
1110+
9 |
1111+
> 10 | css\`
1112+
| ^
1113+
11 | .root {
1114+
12 | color: \${color};
1115+
13 | }
1116+
1117+
`;
1118+
1119+
exports[`@cssxjs/babel-plugin-rn-stylename-inline Reachable local css interpolation before return: Reachable local css interpolation before return 1`] = `
1120+
1121+
import React from 'react'
1122+
import { css } from 'cssxjs'
1123+
import { View } from 'react-native'
1124+
import { useThemeColor } from './theme'
1125+
1126+
export default function Card ({ pad }) {
1127+
const color = useThemeColor('primary')
1128+
1129+
css\`
1130+
.root {
1131+
color: \${color};
1132+
padding: \${pad} 2u;
1133+
}
1134+
\`
1135+
1136+
return <View styleName='root' />
1137+
}
1138+
1139+
↓ ↓ ↓ ↓ ↓ ↓
1140+
1141+
import React from "react";
1142+
import { css } from "cssxjs";
1143+
import { View } from "react-native";
1144+
import { useThemeColor } from "./theme";
1145+
const _localCssInstance = {
1146+
version: 1,
1147+
id: "cssx_fjh55d",
1148+
contentHash: "cssx_4m17p8",
1149+
rules: [
1150+
{
1151+
selector: ".root",
1152+
classes: ["root"],
1153+
part: null,
1154+
specificity: 1,
1155+
order: 0,
1156+
media: null,
1157+
declarations: [
1158+
{
1159+
property: "color",
1160+
value: "var(--__cssx_dynamic_0)",
1161+
raw: "color: var(--__cssx_dynamic_0)",
1162+
order: 0,
1163+
dynamicSlots: [0],
1164+
line: 3,
1165+
column: 7,
1166+
},
1167+
{
1168+
property: "padding",
1169+
value: "var(--__cssx_dynamic_1) 2u",
1170+
raw: "padding: var(--__cssx_dynamic_1) 2u",
1171+
order: 1,
1172+
dynamicSlots: [1],
1173+
line: 4,
1174+
column: 7,
1175+
},
1176+
],
1177+
},
1178+
],
1179+
keyframes: {},
1180+
metadata: {
1181+
hasVars: true,
1182+
vars: ["--__cssx_dynamic_0", "--__cssx_dynamic_1"],
1183+
hasMedia: false,
1184+
hasViewportUnits: false,
1185+
hasInterpolations: true,
1186+
hasDynamicRuntimeDependencies: true,
1187+
hasAnimations: false,
1188+
hasTransitions: false,
1189+
},
1190+
diagnostics: [],
1191+
__hash__: -1763352586,
1192+
};
1193+
export default function Card({ pad }) {
1194+
const color = useThemeColor("primary");
1195+
const __CSS_LOCAL__ = {
1196+
sheet: _localCssInstance,
1197+
values: [color, pad],
1198+
};
1199+
return <View styleName="root" />;
1200+
}
1201+
1202+
10831203
`;
10841204
10851205
exports[`@cssxjs/babel-plugin-rn-stylename-inline Should remove css and styl from cssxjs import: Should remove css and styl from cssxjs import 1`] = `

packages/babel-plugin-rn-stylename-inline/__tests__/index.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,47 @@ pluginTester({
243243
\`
244244
}
245245
`
246+
},
247+
'Reachable local css interpolation before return': /* js */`
248+
import React from 'react'
249+
import { css } from 'cssxjs'
250+
import { View } from 'react-native'
251+
import { useThemeColor } from './theme'
252+
253+
export default function Card ({ pad }) {
254+
const color = useThemeColor('primary')
255+
256+
css\`
257+
.root {
258+
color: \${color};
259+
padding: \${pad} 2u;
260+
}
261+
\`
262+
263+
return <View styleName='root' />
264+
}
265+
`,
266+
'Reachable local css interpolation after early return. Should error': {
267+
error: /Local css\/styl templates must be declared before the first return/,
268+
code: /* js */`
269+
import React from 'react'
270+
import { css } from 'cssxjs'
271+
import { View } from 'react-native'
272+
import { useThemeColor } from './theme'
273+
274+
export default function Card ({ ready }) {
275+
if (!ready) return <View styleName='loader' />
276+
const color = useThemeColor('primary')
277+
278+
css\`
279+
.root {
280+
color: \${color};
281+
}
282+
\`
283+
284+
return <View styleName='root' />
285+
}
286+
`
246287
}
247288
}
248289
})

packages/babel-plugin-rn-stylename-inline/index.js

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const getVisitor = ({ $program, usedCompilers }) => ({
7272
insertLocalCss($function, $this, buildConst({
7373
variable: t.identifier(LOCAL_NAME),
7474
value: localValue
75-
}), expressions)
75+
}), expressions, usedCompilers)
7676

7777
// IV. GLOBAL. if parent is program -- handle global
7878
} else {
@@ -105,7 +105,7 @@ function insertAfterImports ($program, expressionStatement) {
105105
}
106106
}
107107

108-
function insertLocalCss ($function, $template, statement, expressions) {
108+
function insertLocalCss ($function, $template, statement, expressions, usedCompilers) {
109109
const $body = $function.get('body')
110110
if (!$body.isBlockStatement()) {
111111
$body.replaceWith(t.blockStatement([
@@ -115,23 +115,52 @@ function insertLocalCss ($function, $template, statement, expressions) {
115115

116116
const $statement = $template.getStatementParent()
117117
const $functionBody = $function.get('body')
118-
// CSSX tracking hooks must run before any render return. Insert the local
119-
// layer before the first return, while keeping it after user setup code.
120-
const $target = findFirstReturnStatement($functionBody) ||
121-
(
122-
$statement?.parentPath === $functionBody
123-
? $statement
124-
: undefined
125-
)
118+
const $firstReturn = findFirstReturnStatement($functionBody)
119+
120+
if ($statement?.parentPath !== $functionBody) {
121+
$functionBody.unshiftContainer('body', statement)
122+
return
123+
}
124+
125+
const $target = isTrailingStyleTemplateStatement($statement, usedCompilers)
126+
? ($firstReturn || $statement)
127+
: $statement
128+
129+
validateLocalCssPosition($functionBody, $firstReturn, $target, $template)
126130

127131
validateInterpolationBindings($function, $functionBody, $target, expressions, $template)
128132

129-
if ($target) {
130-
$target.insertBefore(statement)
131-
return
133+
$target.insertBefore(statement)
134+
}
135+
136+
function validateLocalCssPosition ($functionBody, $firstReturn, $target, $template) {
137+
if (!$firstReturn || $target.node !== $template.getStatementParent()?.node) return
138+
139+
const statements = $functionBody.get('body')
140+
const returnIndex = statements.findIndex($statement => $statement.node === $firstReturn.node)
141+
const targetIndex = statements.findIndex($statement => $statement.node === $target.node)
142+
if (returnIndex < 0 || targetIndex < 0 || targetIndex < returnIndex) return
143+
144+
throw $template.buildCodeFrameError([
145+
'[@cssxjs/babel-plugin-rn-stylename-inline] Local css/styl templates must be declared before the first return, unless they are trailing CSSX style blocks at the end of the component.',
146+
'Move this template before the first return, or place it after all returns as the final component statement.'
147+
].join('\n'))
148+
}
149+
150+
function isTrailingStyleTemplateStatement ($statement, usedCompilers) {
151+
let $current = $statement
152+
while ($current?.node) {
153+
if (!isStyleTemplateStatement($current, usedCompilers)) return false
154+
$current = $current.getNextSibling()
132155
}
156+
return true
157+
}
133158

134-
$functionBody.unshiftContainer('body', statement)
159+
function isStyleTemplateStatement ($statement, usedCompilers) {
160+
if (!$statement.isExpressionStatement()) return false
161+
const expression = $statement.get('expression')
162+
if (!expression.isTaggedTemplateExpression()) return false
163+
return shouldProcess(expression, usedCompilers)
135164
}
136165

137166
function findFirstReturnStatement ($functionBody) {

0 commit comments

Comments
 (0)