Skip to content

Commit ad70461

Browse files
committed
refactor: corrected expected output
1 parent 31e219d commit ad70461

File tree

3 files changed

+184
-74
lines changed

3 files changed

+184
-74
lines changed

packages/css-if-polyfill/src/transform.js

Lines changed: 174 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,12 @@ const extractIfFunctions = (cssText) => {
184184
};
185185

186186
/**
187-
* Parse if() function content
187+
* Parse if() function content - supports both single conditions and multiple chained conditions
188188
*/
189189
const parseIfFunction = (content) => {
190-
// Find the colon that separates condition from values
191-
let colonIndex = -1;
190+
// Split content by semicolons, but respect parentheses and quotes
191+
const segments = [];
192+
let currentSegment = '';
192193
let parenDepth = 0;
193194
let inQuotes = false;
194195
let quoteChar = '';
@@ -213,44 +214,111 @@ const parseIfFunction = (content) => {
213214
parenDepth++;
214215
} else if (char === ')') {
215216
parenDepth--;
216-
} else if (char === ':' && parenDepth === 0) {
217-
colonIndex = i;
218-
break;
217+
} else if (char === ';' && parenDepth === 0) {
218+
// End of segment
219+
segments.push(currentSegment.trim());
220+
currentSegment = '';
221+
continue;
219222
}
220223
}
224+
225+
currentSegment += char;
221226
}
222227

223-
if (colonIndex === -1) {
224-
throw new Error('Invalid if() function: missing colon');
228+
// Add the last segment
229+
if (currentSegment.trim()) {
230+
segments.push(currentSegment.trim());
225231
}
226232

227-
const condition = content.slice(0, colonIndex).trim();
228-
const valuesString = content.slice(colonIndex + 1).trim();
233+
// Parse segments into conditions and values
234+
const conditions = [];
235+
let elseValue = null;
229236

230-
// Parse values (value; else: fallback)
231-
const elseMatch = valuesString.match(/^(.*?);?\s*else:\s*(.*)$/);
237+
for (const segment of segments) {
238+
// Check if this is an else clause
239+
const elseMatch = segment.match(/^else:\s*(.*)$/);
240+
if (elseMatch) {
241+
elseValue = elseMatch[1].trim();
242+
continue;
243+
}
232244

233-
if (!elseMatch) {
234-
throw new Error('Invalid if() function: missing else clause');
235-
}
245+
// Parse condition: value format
246+
let colonIndex = -1;
247+
let parenDepth = 0;
248+
let inQuotes = false;
249+
let quoteChar = '';
250+
251+
// Find the colon that's outside of parentheses and quotes
252+
for (let i = 0; i < segment.length; i++) {
253+
const char = segment[i];
254+
const previousChar = i > 0 ? segment[i - 1] : '';
255+
256+
// Handle quotes
257+
if ((char === '"' || char === "'") && previousChar !== '\\') {
258+
if (!inQuotes) {
259+
inQuotes = true;
260+
quoteChar = char;
261+
} else if (char === quoteChar) {
262+
inQuotes = false;
263+
quoteChar = '';
264+
}
265+
}
236266

237-
const trueValue = elseMatch[1].trim();
238-
const falseValue = elseMatch[2].trim();
267+
if (!inQuotes) {
268+
if (char === '(') {
269+
parenDepth++;
270+
} else if (char === ')') {
271+
parenDepth--;
272+
} else if (char === ':' && parenDepth === 0) {
273+
colonIndex = i;
274+
break;
275+
}
276+
}
277+
}
239278

240-
// Parse condition
241-
const conditionMatch = condition.match(/^(style|media|supports)\((.*)\)$/);
242-
if (!conditionMatch) {
243-
throw new Error('Invalid if() function: unknown condition type');
279+
if (colonIndex === -1) {
280+
throw new Error('Invalid if() function: missing colon in segment');
281+
}
282+
283+
const conditionPart = segment.slice(0, colonIndex).trim();
284+
const valuePart = segment.slice(colonIndex + 1).trim();
285+
286+
// Parse the condition type and expression
287+
const conditionMatch = conditionPart.match(
288+
/^(style|media|supports)\((.*)\)$/
289+
);
290+
if (!conditionMatch) {
291+
throw new Error(
292+
`Invalid if() function: unknown condition type in "${conditionPart}"`
293+
);
294+
}
295+
296+
conditions.push({
297+
conditionType: conditionMatch[1],
298+
conditionExpression: conditionMatch[2],
299+
value: valuePart
300+
});
244301
}
245302

246-
const conditionType = conditionMatch[1];
247-
const conditionExpression = conditionMatch[2];
303+
if (!elseValue) {
304+
throw new Error('Invalid if() function: missing else clause');
305+
}
248306

307+
// For backward compatibility, if there's only one condition, return the old format
308+
if (conditions.length === 1) {
309+
return {
310+
conditionType: conditions[0].conditionType,
311+
conditionExpression: conditions[0].conditionExpression,
312+
trueValue: conditions[0].value,
313+
falseValue: elseValue
314+
};
315+
}
316+
317+
// For multiple conditions, return the new format
249318
return {
250-
conditionType,
251-
conditionExpression,
252-
trueValue,
253-
falseValue
319+
conditions,
320+
falseValue: elseValue,
321+
isMultipleConditions: true
254322
};
255323
};
256324

@@ -276,41 +344,94 @@ const transformPropertyToNative = (selector, property, value) => {
276344
try {
277345
const parsed = parseIfFunction(ifFunc.content);
278346

279-
if (parsed.conditionType === 'style') {
280-
// Style() conditions need runtime processing
281-
runtimeRules.push({
282-
selector,
283-
property,
284-
value,
285-
condition: parsed
286-
});
287-
} else {
288-
// Media() and supports() can be transformed to native CSS
289-
const nativeCondition =
290-
parsed.conditionType === 'media'
291-
? `@media (${parsed.conditionExpression})`
292-
: `@supports (${parsed.conditionExpression})`;
293-
294-
// Create conditional rule with true value
295-
const trueValue = value.replace(
296-
ifFunc.fullFunction,
297-
parsed.trueValue
347+
// Handle multiple conditions format
348+
if (parsed.isMultipleConditions) {
349+
// Check if any condition uses style() - if so, needs runtime processing
350+
const hasStyleCondition = parsed.conditions.some(
351+
(condition) => condition.conditionType === 'style'
298352
);
299-
nativeRules.push({
300-
condition: nativeCondition,
301-
rule: `${selector} { ${property}: ${trueValue}; }`
302-
});
303353

304-
// Create fallback rule with false value
305-
const falseValue = value.replace(
354+
if (hasStyleCondition) {
355+
// If any condition uses style(), fall back to runtime processing
356+
runtimeRules.push({
357+
selector,
358+
property,
359+
value,
360+
condition: parsed
361+
});
362+
continue;
363+
}
364+
365+
// All conditions are media() or supports() - can transform to native CSS
366+
// Create fallback rule first
367+
const fallbackValue = value.replace(
306368
ifFunc.fullFunction,
307369
parsed.falseValue
308370
);
309371
nativeRules.push({
310372
condition: null, // No condition = fallback
311-
rule: `${selector} { ${property}: ${falseValue}; }`
373+
rule: `${selector} { ${property}: ${fallbackValue}; }`
312374
});
375+
376+
// Create conditional rules for each condition (in reverse order for CSS cascade)
377+
const { conditions } = parsed;
378+
for (let i = conditions.length - 1; i >= 0; i--) {
379+
const condition = conditions[i];
380+
const nativeCondition =
381+
condition.conditionType === 'media'
382+
? `@media (${condition.conditionExpression})`
383+
: `@supports (${condition.conditionExpression})`;
384+
385+
const conditionalValue = value.replace(
386+
ifFunc.fullFunction,
387+
condition.value
388+
);
389+
nativeRules.push({
390+
condition: nativeCondition,
391+
rule: `${selector} { ${property}: ${conditionalValue}; }`
392+
});
393+
}
394+
395+
continue;
396+
}
397+
398+
// Handle single condition format (backward compatibility)
399+
if (parsed.conditionType === 'style') {
400+
// Style() conditions need runtime processing
401+
runtimeRules.push({
402+
selector,
403+
property,
404+
value,
405+
condition: parsed
406+
});
407+
continue;
313408
}
409+
410+
// Media() and supports() can be transformed to native CSS
411+
const nativeCondition =
412+
parsed.conditionType === 'media'
413+
? `@media (${parsed.conditionExpression})`
414+
: `@supports (${parsed.conditionExpression})`;
415+
416+
// Create conditional rule with true value
417+
const trueValue = value.replace(
418+
ifFunc.fullFunction,
419+
parsed.trueValue
420+
);
421+
nativeRules.push({
422+
condition: nativeCondition,
423+
rule: `${selector} { ${property}: ${trueValue}; }`
424+
});
425+
426+
// Create fallback rule with false value
427+
const falseValue = value.replace(
428+
ifFunc.fullFunction,
429+
parsed.falseValue
430+
);
431+
nativeRules.push({
432+
condition: null, // No condition = fallback
433+
rule: `${selector} { ${property}: ${falseValue}; }`
434+
});
314435
} catch (error) {
315436
// If parsing fails, fall back to runtime processing
316437
runtimeRules.push({

packages/postcss-if-function/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,9 @@ module.exports = {
206206
padding: 15px;
207207
}
208208

209-
@media (width >= 1200px) {
209+
@media (width >= 480px) {
210210
.responsive {
211-
padding: 40px;
211+
padding: 20px;
212212
}
213213
}
214214

@@ -218,9 +218,9 @@ module.exports = {
218218
}
219219
}
220220

221-
@media (width >= 480px) {
221+
@media (width >= 1200px) {
222222
.responsive {
223-
padding: 20px;
223+
padding: 40px;
224224
}
225225
}
226226
```

packages/postcss-if-function/test/plugin.test.js

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,26 +103,15 @@ describe('postcss-if-function plugin', () => {
103103
);
104104
}`;
105105

106-
const expected = `.responsive {
107-
padding: 15px;
108-
}
109-
110-
@media (width >= 1200px) {
111-
.responsive {
112-
padding: 40px;
113-
}
106+
const expected = `.responsive { padding: 15px; }
107+
@media (width >= 480px) {
108+
.responsive { padding: 20px; }
114109
}
115-
116110
@media (width >= 768px) {
117-
.responsive {
118-
padding: 30px;
119-
}
111+
.responsive { padding: 30px; }
120112
}
121-
122-
@media (width >= 480px) {
123-
.responsive {
124-
padding: 20px;
125-
}
113+
@media (width >= 1200px) {
114+
.responsive { padding: 40px; }
126115
}`;
127116

128117
await run(input, expected);

0 commit comments

Comments
 (0)