Skip to content

Commit 40f4077

Browse files
authored
fix: Should be able to parse to extract values template string (#77)
* fix: Should be able to parse to extract values template string * fix: updated unit test code * fix: updated unit test code
1 parent 6560cfb commit 40f4077

File tree

13 files changed

+2285
-2427
lines changed

13 files changed

+2285
-2427
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"extends": "@baiwusanyu",
33
"rules": {
4-
"no-console": ["warn", { "allow": ["log"] }]
4+
"no-console": ["warn", { "allow": ["log"] }],
5+
"no-template-curly-in-string": "off"
56
}
67
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
},
5757
"scripts": {
5858
"init": "pnpm i",
59-
"lint:fix": "eslint --fix ./ --ext .vue,.js,.ts,.jsx,.tsx,.json ",
59+
"lint:fix": "eslint --cache --fix ./ --ext .vue,.js,.ts,.jsx,.tsx,.json ",
6060
"dev": "pnpm run --filter @unplugin-vue-cssvars/build dev",
6161
"build": "pnpm run clean && pnpm run --filter @unplugin-vue-cssvars/build build",
6262
"play:vite:server": "pnpm run --filter @unplugin-vue-cssvars/play-vite server",

packages/core/inject/inject-cssvars.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import hash from 'hash-sum'
32
import { type MagicStringBase } from 'magic-string-ast'
43
import type { IParseSFCRes, TMatchVariable } from '../parser'

packages/core/parser/__test__/parser-compiled-sfc.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import { beforeEach, describe, expect, test } from 'vitest'
32
import { parse } from '@babel/parser'
43
import {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { parseCssVars } from '../parser-vbind-m'
3+
4+
describe('analyze css vbind', () => {
5+
test('Should be able to parse to extract the v-bind-m value', () => {
6+
const source = `
7+
.test {
8+
color: v-bind-m(color);
9+
}
10+
`
11+
const expected = ['color']
12+
expect(parseCssVars([source])).toMatchObject(expected)
13+
})
14+
15+
test('Should be able to parse single quoted values', () => {
16+
const source = `
17+
.test {
18+
color: v-bind-m('color');
19+
}
20+
`
21+
const expected = ['color']
22+
expect(parseCssVars([source])).toMatchObject(expected)
23+
})
24+
25+
test('Should be able to parse double quoted values', () => {
26+
const source = `
27+
.test {
28+
color: v-bind-m("color");
29+
}
30+
`
31+
const expected = ['color']
32+
expect(parseCssVars([source])).toMatchObject(expected)
33+
})
34+
35+
test('Should be able to parse the value of the template string', () => {
36+
const source = `
37+
.test {
38+
color: v-bind-m(\`\${v}\`);
39+
background-image: v-bind-m('\`url('\${bgUrl}')\`');
40+
}
41+
`
42+
43+
const expected = ['`${v}`', '`url(\'${bgUrl}\')`']
44+
expect(parseCssVars([source])).toMatchObject(expected)
45+
})
46+
47+
test('Should be able to parse extract v-bind-m values in nested', () => {
48+
const source = `
49+
.parent {
50+
.child {
51+
color: v-bind-m(color);
52+
}
53+
}
54+
`
55+
const expected = ['color']
56+
expect(parseCssVars([source])).toMatchObject(expected)
57+
})
58+
59+
test('Should be able to parse extract v-bind-m values when ignoring single line comments', () => {
60+
const source = `
61+
.test {
62+
color: v-bind-m(color); // this is a comment
63+
}
64+
`
65+
const expected = ['color']
66+
expect(parseCssVars([source])).toMatchObject(expected)
67+
})
68+
69+
test('Should be able to parse extract v-bind-m values when ignoring multi-line comments', () => {
70+
const source = `
71+
.test {
72+
color: v-bind-m(color); /* this is a
73+
multi-line
74+
comment */
75+
}
76+
`
77+
const expected = ['color']
78+
expect(parseCssVars([source])).toMatchObject(expected)
79+
})
80+
81+
test('Should be able to extract multiple v-bind-m values in analysis', () => {
82+
const source = `
83+
.test {
84+
color: v-bind-m(color1);
85+
background-color: v-bind-m(color2);
86+
}
87+
`
88+
const expected = ['color1', 'color2']
89+
expect(parseCssVars([source])).toMatchObject(expected)
90+
})
91+
92+
test('Should only analyze to extract unique values', () => {
93+
const source = `
94+
.test {
95+
color: v-bind-m(color1);
96+
background-color: v-bind-m(color1);
97+
}
98+
`
99+
const expected = ['color1']
100+
expect(parseCssVars([source])).toMatchObject(expected)
101+
})
102+
103+
test('Should be able to parse to extract values inside nested parentheses', () => {
104+
const source = `
105+
.test {
106+
color: v-bind-m(((color1)));
107+
}
108+
`
109+
const expected = ['((color1))']
110+
expect(parseCssVars([source])).toMatchObject(expected)
111+
})
112+
113+
test('Should be able to parse to extract values template string', () => {
114+
const source = '.test{ color: v-bind-m(`${v}`);\n background-image: v-bind-m("`url(\'${bgUrl}\')`");}'
115+
const expected = ['`${v}`', "`url('${bgUrl}')`"]
116+
expect(parseCssVars([source])).toMatchObject(expected)
117+
})
118+
119+
test('the right parenthesis is missing', () => {
120+
const source = `
121+
.test {
122+
v-bind-m(color1;
123+
}
124+
`
125+
expect(parseCssVars([source])).toMatchObject([])
126+
})
127+
128+
test('the left parenthesis is missing', () => {
129+
const source = `
130+
.test {
131+
v-bind-m color1);
132+
}
133+
`
134+
expect(parseCssVars([source])).toMatchObject([])
135+
})
136+
137+
test('should be able to parse incomplete expressions', () => {
138+
const source = `
139+
.test {
140+
font-weight: v-bind-m("count.toString(");
141+
font-weight: v-bind-m(xxx);
142+
}
143+
`
144+
expect(parseCssVars([source])).toMatchObject(['count.toString(', 'xxx'])
145+
})
146+
})

packages/core/parser/__test__/parser-vbindm.spec.ts

Lines changed: 0 additions & 88 deletions
This file was deleted.

packages/core/parser/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export * from './parser-variable'
22
export * from './parser-import'
3-
export * from './parser-vbindm'
3+
export * from './parser-vbind-m'
44
export { parserCompiledSfc } from './parser-compiled-sfc'
55
export type { IParseSFCRes } from './parser-compiled-sfc'
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Implementation from vue
3+
* https://github.com/vuejs/core/blob/main/packages/compiler-sfc/src/style/cssVars.ts
4+
*/
5+
const enum LexerState {
6+
inParens,
7+
inSingleQuoteString,
8+
inDoubleQuoteString,
9+
}
10+
11+
function lexBinding(content: string, start: number): number | null {
12+
let state: LexerState = LexerState.inParens
13+
let parenDepth = 0
14+
15+
for (let i = start; i < content.length; i++) {
16+
const char = content.charAt(i)
17+
switch (state) {
18+
case LexerState.inParens:
19+
if (char === '\'') {
20+
state = LexerState.inSingleQuoteString
21+
} else if (char === '"') {
22+
state = LexerState.inDoubleQuoteString
23+
} else if (char === '(') {
24+
parenDepth++
25+
} else if (char === ')') {
26+
if (parenDepth > 0)
27+
parenDepth--
28+
else
29+
return i
30+
}
31+
break
32+
case LexerState.inSingleQuoteString:
33+
if (char === '\'')
34+
state = LexerState.inParens
35+
36+
break
37+
case LexerState.inDoubleQuoteString:
38+
if (char === '"')
39+
state = LexerState.inParens
40+
41+
break
42+
}
43+
}
44+
return null
45+
}
46+
47+
function normalizeExpression(exp: string) {
48+
exp = exp.trim()
49+
if (
50+
(exp[0] === '\'' && exp[exp.length - 1] === '\'')
51+
|| (exp[0] === '"' && exp[exp.length - 1] === '"')
52+
)
53+
return exp.slice(1, -1)
54+
55+
return exp
56+
}
57+
58+
const vBindRE = /v-bind-m\s*\(/g
59+
60+
export function parseCssVars(
61+
styles: string[],
62+
hook?: {
63+
getIndex(start: number, end: number): void
64+
},
65+
): string[] {
66+
const vars: string[] = []
67+
styles.forEach((style) => {
68+
let match: RegExpExecArray | null = null
69+
// ignore v-bind() in comments /* ... */
70+
const content = style.replace(/\/\*([\s\S]*?)\*\//g, '')
71+
72+
while ((match = vBindRE.exec(content))) {
73+
const start = match.index + match[0].length
74+
const end = lexBinding(content, start)
75+
if (end !== null) {
76+
hook && hook.getIndex(start, end)
77+
const variable = normalizeExpression(content.slice(start, end))
78+
if (!vars.includes(variable))
79+
vars.push(variable)
80+
}
81+
}
82+
})
83+
return vars
84+
}

0 commit comments

Comments
 (0)