From 15854f671a1943388b7273f832743629617743e5 Mon Sep 17 00:00:00 2001 From: Jacob Parker Date: Mon, 15 May 2017 19:42:42 +0100 Subject: [PATCH 1/3] Fix parsing various shorthands when a value is zero (fixes #38) --- src/TokenStream.js | 10 ++++++++-- src/index.js | 4 ++-- src/index.test.js | 9 +++++++++ src/tokenTypes.js | 6 ++++-- src/transforms/flex.js | 17 ++++++++++------- src/transforms/font.js | 12 ++++++------ src/transforms/fontFamily.js | 2 +- src/transforms/index.js | 6 +++--- src/transforms/util.js | 4 ++-- 9 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/TokenStream.js b/src/TokenStream.js index bcbb70f..3650f2b 100644 --- a/src/TokenStream.js +++ b/src/TokenStream.js @@ -1,3 +1,5 @@ +const SYMBOL_MATCH = 'SYMBOL_MATCH'; + module.exports = class TokenStream { constructor(nodes, parent) { this.nodes = nodes; @@ -18,7 +20,7 @@ module.exports = class TokenStream { return new TokenStream(this.nodes.slice(1), this.parent); } - match(...tokenDescriptors) { + [SYMBOL_MATCH](...tokenDescriptors) { const node = this.node; if (!node) return null; @@ -38,8 +40,12 @@ module.exports = class TokenStream { return null; } + matches(...tokenDescriptors) { + return this[SYMBOL_MATCH](...tokenDescriptors) !== null; + } + expect(...tokenDescriptors) { - const value = this.match(...tokenDescriptors); + const value = this[SYMBOL_MATCH](...tokenDescriptors); if (value !== null) return value; return this.throw(); } diff --git a/src/index.js b/src/index.js index 6347137..3f25730 100644 --- a/src/index.js +++ b/src/index.js @@ -13,10 +13,10 @@ export const transformRawValue = (input) => { const value = input.trim(); const numberMatch = value.match(numberOrLengthRe); - if (numberMatch) return Number(numberMatch[1]); + if (numberMatch !== null) return Number(numberMatch[1]); const boolMatch = input.match(boolRe); - if (boolMatch) return boolMatch[0].toLowerCase() === 'true'; + if (boolMatch !== null) return boolMatch[0].toLowerCase() === 'true'; return value; }; diff --git a/src/index.test.js b/src/index.test.js index 363e2b8..0b5b7ed 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -240,6 +240,15 @@ it('transforms flex shorthand with 1 values', () => runTest([ ['flex', '1'], ], { flexGrow: 1, flexShrink: 1, flexBasis: 0 })); +/* +A unitless zero that is not already preceded by two flex factors must be interpreted as a flex +factor. To avoid misinterpretation or invalid declarations, authors must specify a zero +<‘flex-basis’> component with a unit or precede it by two flex factors. +*/ +it('transforms flex shorthand with flex-grow/shrink taking priority over basis', () => runTest([ + ['flex', '0 1 0'], +], { flexGrow: 0, flexShrink: 1, flexBasis: 0 })); + it('transforms flexFlow shorthand with two values', () => runTest([ ['flex-flow', 'column wrap'], ], { flexDirection: 'column', flexWrap: 'wrap' })); diff --git a/src/tokenTypes.js b/src/tokenTypes.js index 056616a..0d1216e 100644 --- a/src/tokenTypes.js +++ b/src/tokenTypes.js @@ -22,7 +22,8 @@ const matchColor = (node) => { return null; }; -const noneRe = /^(none)$/; +const noneRe = /^(none)$/i; +const autoRe = /^(auto)$/i; const identRe = /(^-?[_a-z][_a-z0-9-]*$)/i; // Note if these are wrong, you'll need to change index.js too const numberRe = /^([+-]?(?:\d*\.)?\d+(?:[Ee][+-]?\d+)?)$/; @@ -39,7 +40,7 @@ const regExpToken = (regExp, transform = String) => (node) => { if (node.type !== 'word') return null; const match = node.value.match(regExp); - if (!match) return null; + if (match === null) return null; const value = transform(match[1]); @@ -54,6 +55,7 @@ module.exports.tokens = { COMMA: noopToken(node => node.type === 'div' && node.value === ','), WORD: valueForTypeToken('word'), NONE: regExpToken(noneRe), + AUTO: regExpToken(autoRe), NUMBER: regExpToken(numberRe, Number), LENGTH: regExpToken(lengthRe, Number), ANGLE: regExpToken(angleRe), diff --git a/src/transforms/flex.js b/src/transforms/flex.js index d694492..6458870 100644 --- a/src/transforms/flex.js +++ b/src/transforms/flex.js @@ -1,6 +1,6 @@ const { tokens } = require('../tokenTypes'); -const { NONE, NUMBER, LENGTH, SPACE } = tokens; +const { NONE, AUTO, NUMBER, LENGTH, SPACE } = tokens; const defaultFlexGrow = 1; const defaultFlexShrink = 1; @@ -11,23 +11,26 @@ module.exports = (tokenStream) => { let flexShrink; let flexBasis; - if (tokenStream.match(NONE)) { + if (tokenStream.matches(NONE)) { tokenStream.expectEmpty(); return { $merge: { flexGrow: 0, flexShrink: 0 } }; + } else if (tokenStream.matches(AUTO)) { + tokenStream.expectEmpty(); + return { $merge: { flexGrow: 1, flexShrink: 1 } }; } let partsParsed = 0; while (partsParsed < 2 && tokenStream.hasTokens()) { - if (partsParsed) tokenStream.expect(SPACE); + if (partsParsed !== 0) tokenStream.expect(SPACE); - if (flexGrow === undefined && tokenStream.match(NUMBER)) { + if (flexGrow === undefined && tokenStream.matches(NUMBER)) { flexGrow = tokenStream.lastValue; - if (tokenStream.lookahead().match(NUMBER)) { + if (tokenStream.lookahead().matches(NUMBER)) { tokenStream.expect(SPACE); - flexShrink = tokenStream.match(NUMBER); + flexShrink = tokenStream.expect(NUMBER); } - } else if (flexBasis === undefined && tokenStream.match(LENGTH)) { + } else if (flexBasis === undefined && tokenStream.matches(LENGTH)) { flexBasis = tokenStream.lastValue; } else { tokenStream.throw(); diff --git a/src/transforms/font.js b/src/transforms/font.js index 4586c35..c7b02e6 100644 --- a/src/transforms/font.js +++ b/src/transforms/font.js @@ -21,13 +21,13 @@ module.exports = (tokenStream) => { let numStyleWeightVariantMatched = 0; while (numStyleWeightVariantMatched < 3 && tokenStream.hasTokens()) { - if (tokenStream.match(NORMAL)) { + if (tokenStream.matches(NORMAL)) { /* pass */ - } else if (fontStyle === undefined && tokenStream.match(STYLE)) { + } else if (fontStyle === undefined && tokenStream.matches(STYLE)) { fontStyle = tokenStream.lastValue; - } else if (fontWeight === undefined && tokenStream.match(WEIGHT)) { + } else if (fontWeight === undefined && tokenStream.matches(WEIGHT)) { fontWeight = tokenStream.lastValue; - } else if (fontVariant === undefined && tokenStream.match(VARIANT)) { + } else if (fontVariant === undefined && tokenStream.matches(VARIANT)) { fontVariant = [tokenStream.lastValue]; } else { break; @@ -39,8 +39,8 @@ module.exports = (tokenStream) => { const fontSize = tokenStream.expect(LENGTH); - if (tokenStream.match(SLASH)) { - if (tokenStream.match(NUMBER)) { + if (tokenStream.matches(SLASH)) { + if (tokenStream.matches(NUMBER)) { lineHeight = fontSize * tokenStream.lastValue; } else { lineHeight = tokenStream.expect(LENGTH); diff --git a/src/transforms/fontFamily.js b/src/transforms/fontFamily.js index 59e1cfc..3b3f368 100644 --- a/src/transforms/fontFamily.js +++ b/src/transforms/fontFamily.js @@ -5,7 +5,7 @@ const { SPACE, IDENT, STRING } = tokens; module.exports = (tokenStream) => { let fontFamily; - if (tokenStream.match(STRING)) { + if (tokenStream.matches(STRING)) { fontFamily = tokenStream.lastValue; } else { fontFamily = tokenStream.expect(IDENT); diff --git a/src/transforms/index.js b/src/transforms/index.js index 13a7c4b..64c3667 100644 --- a/src/transforms/index.js +++ b/src/transforms/index.js @@ -7,7 +7,7 @@ const { directionFactory, anyOrderFactory, shadowOffsetFactory } = require('./ut const { IDENT, WORD, COLOR } = tokens; -const background = tokenStream => ({ $merge: { backgroundColor: tokenStream.match(COLOR) } }); +const background = tokenStream => ({ $merge: { backgroundColor: tokenStream.expect(COLOR) } }); const border = anyOrderFactory({ borderWidth: { token: tokens.LENGTH, @@ -45,8 +45,8 @@ const flexFlow = anyOrderFactory({ default: 'row', }, }); -const fontVariant = tokenStream => [tokenStream.match(IDENT)]; -const fontWeight = tokenStream => tokenStream.match(WORD); // Also match numbers as strings +const fontVariant = tokenStream => [tokenStream.expect(IDENT)]; +const fontWeight = tokenStream => tokenStream.expect(WORD); // Also match numbers as strings const shadowOffset = shadowOffsetFactory(); const textShadowOffset = shadowOffsetFactory(); diff --git a/src/transforms/util.js b/src/transforms/util.js index 2a0304c..03bdcf1 100644 --- a/src/transforms/util.js +++ b/src/transforms/util.js @@ -46,7 +46,7 @@ module.exports.anyOrderFactory = (properties, delim = SPACE) => (tokenStream) => if (numParsed) tokenStream.expect(delim); const matchedPropertyName = propertyNames.find(propertyName => ( - values[propertyName] === undefined && tokenStream.match(properties[propertyName].token) + values[propertyName] === undefined && tokenStream.matches(properties[propertyName].token) )); if (!matchedPropertyName) { @@ -69,7 +69,7 @@ module.exports.anyOrderFactory = (properties, delim = SPACE) => (tokenStream) => module.exports.shadowOffsetFactory = () => (tokenStream) => { const width = tokenStream.expect(LENGTH); - const height = tokenStream.match(SPACE) + const height = tokenStream.matches(SPACE) ? tokenStream.expect(LENGTH) : width; tokenStream.expectEmpty(); From b901f2df25e72a27ace453d2d59c07f9f34fd9e9 Mon Sep 17 00:00:00 2001 From: Jacob Parker Date: Mon, 15 May 2017 20:05:17 +0100 Subject: [PATCH 2/3] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b2a9d61..5273050 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-to-react-native", - "version": "2.0.3", + "version": "2.0.4", "description": "Convert CSS text to a React Native stylesheet object", "main": "dist/index.js", "scripts": { From ba3f1e58405e83544cc1c785ff5e7045042576b3 Mon Sep 17 00:00:00 2001 From: Jacob Parker Date: Fri, 19 May 2017 15:09:06 +0100 Subject: [PATCH 3/3] Improve flex tests --- src/index.test.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/index.test.js b/src/index.test.js index 0b5b7ed..436dd8d 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -232,13 +232,25 @@ it('transforms flex shorthand with 3 values in reverse order', () => runTest([ ['flex', '3px 1 2'], ], { flexGrow: 1, flexShrink: 2, flexBasis: 3 })); -it('transforms flex shorthand with 2 values', () => runTest([ +it('transforms flex shorthand with 2 values of flex-grow and flex-shrink', () => runTest([ ['flex', '1 2'], ], { flexGrow: 1, flexShrink: 2, flexBasis: 0 })); -it('transforms flex shorthand with 1 values', () => runTest([ - ['flex', '1'], -], { flexGrow: 1, flexShrink: 1, flexBasis: 0 })); +it('transforms flex shorthand with 2 values of flex-grow and flex-basis', () => runTest([ + ['flex', '2 2px'], +], { flexGrow: 2, flexShrink: 1, flexBasis: 2 })); + +it('transforms flex shorthand with 2 values of flex-grow and flex-basis (reversed)', () => runTest([ + ['flex', '2px 2'], +], { flexGrow: 2, flexShrink: 1, flexBasis: 2 })); + +it('transforms flex shorthand with 1 value of flex-grow', () => runTest([ + ['flex', '2'], +], { flexGrow: 2, flexShrink: 1, flexBasis: 0 })); + +it('transforms flex shorthand with 1 value of flex-basis', () => runTest([ + ['flex', '10px'], +], { flexGrow: 1, flexShrink: 1, flexBasis: 10 })); /* A unitless zero that is not already preceded by two flex factors must be interpreted as a flex @@ -410,6 +422,10 @@ it('does not transform invalid unquoted font-family', () => { expect(() => transformCss([['font-family', 'Goudy Bookletter 1911']])).toThrow(); }); +it('does not transform invalid flex', () => { + expect(() => transformCss([['flex', '1 2px 3']])).toThrow(); +}); + it('allows blacklisting shorthands', () => { const actualStyles = transformCss([['border-radius', '50']], ['borderRadius']); expect(actualStyles).toEqual({ borderRadius: 50 });