let a condition contain an object literal

review found two more, both an object literal inside the condition. a vue
binding calling isActive({ x: 1 }) and a jsx expression starting with an
inline object

an object group is now allowed as a unit the same way a parenthesised group
already was, so the braces inside it do not read as the end of the value
This commit is contained in:
Huzaifa Iftikhar 2026-08-28 05:20:48 +05:00 • committed by HuzaifaChaudary
parent f2bc150471
commit 467f9fc976
2 changed files with 12 additions and 3 deletions

View file

@ -128,6 +128,8 @@ describe('is-safe-migration', async () => {
[`<div :variant="active ? 'ghost' : 'outline'"></div>`, 'outline'],
[`Button({ variant: (value ?? "outline") })`, 'outline'],
[`Button({ variant: theme === "dark" ? "outline" : "ghost" })`, 'outline'],
[`<div :variant="isActive({ x: 1 }) ? 'ghost' : 'outline'"></div>`, 'outline'],
[`<Button variant={{ tone: "dark" }.tone === "dark" ? "outline" : "ghost"} />`, 'outline'],
[`<Button variant={cond ? ("outline") : "ghost"} />`, 'outline'],
[`<Button variant={cond ? "ghost" : ("outline")} />`, 'outline'],
])('does not replace classes in invalid positions #%#', async (example, candidate) => {

View file

@ -8,6 +8,9 @@ const LOGICAL_OPERATORS = ['&&', '||', '?', '===', '==', '!=', '!==', '>', '>=',
// A parenthesised group with one level of nesting, so a call in a condition keeps
// its own commas instead of ending the match early.
const PAREN_GROUP = String.raw`\((?:[^()]|\([^()]*\))*\)`
// An object literal with one level of nesting, so a condition may contain one
// without the surrounding value looking like it ended there.
const OBJECT_GROUP = String.raw`\{(?:[^{}]|\{[^{}]*\})*\}`
// A ternary or nullish operator, then whatever sits before the string literal.
const CONDITION_TAIL = String.raw`(?:\?\?|\?|:)\s*\(*\s*['"\`]$`
@ -34,15 +37,19 @@ const CONDITIONAL_TEMPLATE_SYNTAX = [
// `variant={cond ? "outline" : "ghost"}` — a brace ends the value, and a
// quoted branch inside it is ordinary
new RegExp(String.raw`variant\s*[:=]\s*\{(?:[^{}]|${PAREN_GROUP})*?${CONDITION_TAIL}`),
new RegExp(
String.raw`variant\s*[:=]\s*\{(?:[^{}]|${PAREN_GROUP}|${OBJECT_GROUP})*?${CONDITION_TAIL}`,
),
// `:variant="active ? 'outline' : 'ghost'"` — the opening quote ends the value,
// so the match cannot run on into a neighbouring attribute such as `:class`
new RegExp(String.raw`variant\s*=\s*(["'])(?:(?!\1)[^{}])*?${CONDITION_TAIL}`),
new RegExp(String.raw`variant\s*=\s*(["'])(?:(?!\1)[^{}]|${OBJECT_GROUP})*?${CONDITION_TAIL}`),
// `{ variant: theme === "dark" ? "outline" : "ghost" }` — a comma or brace ends
// the value, quotes do not
new RegExp(String.raw`variant\s*:\s*(?:[^{},]|${PAREN_GROUP})*?${CONDITION_TAIL}`),
new RegExp(
String.raw`variant\s*:\s*(?:[^{},]|${PAREN_GROUP}|${OBJECT_GROUP})*?${CONDITION_TAIL}`,
),
]
const NEXT_PLACEHOLDER_PROP = /placeholder=\{?['"`]$/
const VUE_3_EMIT = /\b\$?emit\(['"`]$/