Skip to content

Commit 93cba6a

Browse files
agriffisgregberge
authored andcommitted
fix(transform): confused by comments, whitespace
- property names can't contain whitespace - there can be whitespace before the colon - comments can appear before property names - property values can contain newlines - !important must be at end of value This doesn't cover everything, because, for example, there could be comments mixed into the property value, and we don't look for them. But it's probably not necessary either. Along with the above, capture all the internal whitespace for fidelity between input and output. We are replacing values, not reformatting the CSS.
1 parent 765366b commit 93cba6a

File tree

2 files changed

+46
-18
lines changed

2 files changed

+46
-18
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { transform } from './transform'
2+
3+
const props = { theme: { colors: { black: '#000' } } }
4+
5+
const transformToStr = (s) =>
6+
transform(s)
7+
.map((x) => (typeof x === 'function' ? x(props) : x))
8+
.join('')
9+
10+
describe('#transform', () => {
11+
it('replaces name/value pair with various whitespace', () => {
12+
expect(transformToStr('color:black;')).toBe('color:#000;')
13+
expect(transformToStr(' color : black ; ')).toBe(' color : #000 ; ')
14+
})
15+
16+
it('replaces name/value pair within rule', () => {
17+
expect(transformToStr('.my-class { color: black; }')).toBe(
18+
'.my-class { color: #000; }',
19+
)
20+
})
21+
22+
it('handles !important', () => {
23+
expect(transformToStr('color: black !important;')).toBe(
24+
'color: #000 !important;',
25+
)
26+
})
27+
28+
it("isn't confused by comments", () => {
29+
expect(transformToStr('/* hi */color:black;')).toBe('/* hi */color:#000;')
30+
})
31+
})

packages/core/src/transform.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
22
import { propGetters } from './propGetters'
33

4-
// prettier-ignore
5-
const PROP_REGEXP = new RegExp(
6-
`(\\s*)` + // leading whitespace
7-
`([^&{}:;\\n]+)` + // property name
8-
`:\\s*` + // colon, whitespace
9-
`([^&{}:;\\n]+)` + // property value
10-
`(\\s*);`, // trailing whitespace, semicolon
11-
`g`, // flags
12-
)
4+
// prop name is an ident: word chars, underscore and dash.
5+
const PROP_CHAR = `[-\\w]`
6+
7+
// prop value consists of non-semis unless backslash-escaped.
8+
const VALUE_CHAR = `(?:\\\\.|[^\\\\;])`
139

1410
// prettier-ignore
15-
const IMPORTANT_REGEXP = new RegExp(
16-
`\\s*!important\\s*`, // important flag, surrounding whitespace
11+
const PROP_REGEXP = new RegExp(
12+
`(${PROP_CHAR}+)` + // capture prop name
13+
`(\\s*:\\s*)` + // colon & whitespace
14+
`(?=\\S)` + // prop value starts with non-whitespace
15+
`(${VALUE_CHAR}*?)` + // capture prop value (non-greedy)
16+
`(\\s*!important)?` + // capture !important
17+
`(\\s*;)`, // semi & whitespace
18+
`gs`, // flags
1719
)
1820

1921
export function transform(rawValue: any) {
@@ -22,17 +24,12 @@ export function transform(rawValue: any) {
2224
let lastIndex = 0
2325
const values = []
2426
while ((matches = PROP_REGEXP.exec(rawValue))) {
25-
const [, start, prop, propValue, end] = matches
27+
const [, prop, colon, value, imp, semi] = matches
2628
const getter = (propGetters as any)[prop]
2729
if (getter) {
28-
const hasImportant = IMPORTANT_REGEXP.test(propValue)
29-
const cleanValue = propValue.replace(IMPORTANT_REGEXP, '')
3030
values.push(rawValue.slice(lastIndex, matches.index))
3131
values.push(
32-
(p: object) =>
33-
`${start}${prop}: ${getter(cleanValue)(p)}${
34-
hasImportant ? ' !important' : ''
35-
};${end}`,
32+
(p: object) => `${prop}${colon}${getter(value)(p)}${imp || ''}${semi}`,
3633
)
3734
lastIndex = matches.index + matches[0].length
3835
}

0 commit comments

Comments
 (0)