Skip to content

Commit

Permalink
fix(transform): confused by comments, whitespace
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
agriffis authored and gregberge committed Mar 15, 2021
1 parent 765366b commit 93cba6a
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 18 deletions.
31 changes: 31 additions & 0 deletions packages/core/src/transform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { transform } from './transform'

const props = { theme: { colors: { black: '#000' } } }

const transformToStr = (s) =>
transform(s)
.map((x) => (typeof x === 'function' ? x(props) : x))
.join('')

describe('#transform', () => {
it('replaces name/value pair with various whitespace', () => {
expect(transformToStr('color:black;')).toBe('color:#000;')
expect(transformToStr(' color : black ; ')).toBe(' color : #000 ; ')
})

it('replaces name/value pair within rule', () => {
expect(transformToStr('.my-class { color: black; }')).toBe(
'.my-class { color: #000; }',
)
})

it('handles !important', () => {
expect(transformToStr('color: black !important;')).toBe(
'color: #000 !important;',
)
})

it("isn't confused by comments", () => {
expect(transformToStr('/* hi */color:black;')).toBe('/* hi */color:#000;')
})
})
33 changes: 15 additions & 18 deletions packages/core/src/transform.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
/* eslint-disable no-continue, no-loop-func, no-cond-assign */
import { propGetters } from './propGetters'

// prettier-ignore
const PROP_REGEXP = new RegExp(
`(\\s*)` + // leading whitespace
`([^&{}:;\\n]+)` + // property name
`:\\s*` + // colon, whitespace
`([^&{}:;\\n]+)` + // property value
`(\\s*);`, // trailing whitespace, semicolon
`g`, // flags
)
// prop name is an ident: word chars, underscore and dash.
const PROP_CHAR = `[-\\w]`

// prop value consists of non-semis unless backslash-escaped.
const VALUE_CHAR = `(?:\\\\.|[^\\\\;])`

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

export function transform(rawValue: any) {
Expand All @@ -22,17 +24,12 @@ export function transform(rawValue: any) {
let lastIndex = 0
const values = []
while ((matches = PROP_REGEXP.exec(rawValue))) {
const [, start, prop, propValue, end] = matches
const [, prop, colon, value, imp, semi] = matches
const getter = (propGetters as any)[prop]
if (getter) {
const hasImportant = IMPORTANT_REGEXP.test(propValue)
const cleanValue = propValue.replace(IMPORTANT_REGEXP, '')
values.push(rawValue.slice(lastIndex, matches.index))
values.push(
(p: object) =>
`${start}${prop}: ${getter(cleanValue)(p)}${
hasImportant ? ' !important' : ''
};${end}`,
(p: object) => `${prop}${colon}${getter(value)(p)}${imp || ''}${semi}`,
)
lastIndex = matches.index + matches[0].length
}
Expand Down

0 comments on commit 93cba6a

Please sign in to comment.