diff --git a/packages/compiler-sfc/__tests__/cssVars.spec.ts b/packages/compiler-sfc/__tests__/cssVars.spec.ts
index d9912f44b51..9d3df069e76 100644
--- a/packages/compiler-sfc/__tests__/cssVars.spec.ts
+++ b/packages/compiler-sfc/__tests__/cssVars.spec.ts
@@ -1,4 +1,4 @@
-import { compileStyle } from '../src'
+import { compileStyle, parse } from '../src'
import { mockId, compileSFCScript, assertCode } from './utils'
describe('CSS vars injection', () => {
@@ -231,5 +231,21 @@ describe('CSS vars injection', () => {
})`)
assertCode(content)
})
+
+ // #6022
+ test('should be able to parse incomplete expressions', () => {
+ const {
+ descriptor: { cssVars }
+ } = parse(
+ `
+ `
+ )
+ expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
+ })
})
})
diff --git a/packages/compiler-sfc/src/cssVars.ts b/packages/compiler-sfc/src/cssVars.ts
index a922e2de7cf..10f9bb480f1 100644
--- a/packages/compiler-sfc/src/cssVars.ts
+++ b/packages/compiler-sfc/src/cssVars.ts
@@ -12,8 +12,6 @@ import { PluginCreator } from 'postcss'
import hash from 'hash-sum'
export const CSS_VARS_HELPER = `useCssVars`
-// match v-bind() with max 2-levels of nested parens.
-const cssVarRE = /v-bind\s*\(((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/g
export function genCssVarsFromList(
vars: string[],
@@ -47,22 +45,71 @@ function normalizeExpression(exp: string) {
return exp
}
+const vBindRE = /v-bind\s*\(/g
+
export function parseCssVars(sfc: SFCDescriptor): string[] {
const vars: string[] = []
sfc.styles.forEach(style => {
let match
// ignore v-bind() in comments /* ... */
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
- while ((match = cssVarRE.exec(content))) {
- const variable = normalizeExpression(match[1])
- if (!vars.includes(variable)) {
- vars.push(variable)
+ while ((match = vBindRE.exec(content))) {
+ const start = match.index + match[0].length
+ const end = lexBinding(content, start)
+ if (end !== null) {
+ const variable = normalizeExpression(content.slice(start, end))
+ if (!vars.includes(variable)) {
+ vars.push(variable)
+ }
}
}
})
return vars
}
+const enum LexerState {
+ inParens,
+ inSingleQuoteString,
+ inDoubleQuoteString
+}
+
+function lexBinding(content: string, start: number): number | null {
+ let state: LexerState = LexerState.inParens
+ let parenDepth = 0
+
+ for (let i = start; i < content.length; i++) {
+ const char = content.charAt(i)
+ switch (state) {
+ case LexerState.inParens:
+ if (char === `'`) {
+ state = LexerState.inSingleQuoteString
+ } else if (char === `"`) {
+ state = LexerState.inDoubleQuoteString
+ } else if (char === `(`) {
+ parenDepth++
+ } else if (char === `)`) {
+ if (parenDepth > 0) {
+ parenDepth--
+ } else {
+ return i
+ }
+ }
+ break
+ case LexerState.inSingleQuoteString:
+ if (char === `'`) {
+ state = LexerState.inParens
+ }
+ break
+ case LexerState.inDoubleQuoteString:
+ if (char === `"`) {
+ state = LexerState.inParens
+ }
+ break
+ }
+ }
+ return null
+}
+
// for compileStyle
export interface CssVarsPluginOptions {
id: string
@@ -75,10 +122,24 @@ export const cssVarsPlugin: PluginCreator = opts => {
postcssPlugin: 'vue-sfc-vars',
Declaration(decl) {
// rewrite CSS variables
- if (cssVarRE.test(decl.value)) {
- decl.value = decl.value.replace(cssVarRE, (_, $1) => {
- return `var(--${genVarName(id, normalizeExpression($1), isProd)})`
- })
+ const value = decl.value
+ if (vBindRE.test(value)) {
+ vBindRE.lastIndex = 0
+ let transformed = ''
+ let lastIndex = 0
+ let match
+ while ((match = vBindRE.exec(value))) {
+ const start = match.index + match[0].length
+ const end = lexBinding(value, start)
+ if (end !== null) {
+ const variable = normalizeExpression(value.slice(start, end))
+ transformed +=
+ value.slice(lastIndex, match.index) +
+ `var(--${genVarName(id, variable, isProd)})`
+ lastIndex = end + 1
+ }
+ }
+ decl.value = transformed + value.slice(lastIndex)
}
}
}