Skip to content

Improve tokenstream performance, implement prettier #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module.exports = {
extends: 'airbnb-base',
};
extends: ['airbnb-base', 'prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 2,
},
}
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

language: node_js
dist: trusty
node_js:
Expand All @@ -8,6 +7,7 @@ script:
- node --version
- yarn --version
- yarn run test
- yarn run lint
notifications:
email:
on_failure: change
Expand Down
19 changes: 16 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"name": "css-to-react-native",
"version": "2.1.0",
"version": "2.1.1",
"description": "Convert CSS text to a React Native stylesheet object",
"main": "dist/index.js",
"scripts": {
"build": "babel src --ignore test.js --out-dir dist",
"test": "jest",
"test:watch": "jest --watch",
"prepublish": "npm run build"
"lint": "eslint src",
"prepublish": "npm run build",
"precommit": "lint-staged",
"lint-staged": "lint-staged"
},
"files": [
"dist",
Expand Down Expand Up @@ -35,12 +38,22 @@
"babel-preset-es2015": "^6.18.0",
"eslint": "^3.9.1",
"eslint-config-airbnb-base": "^10.0.1",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.2.0",
"jest": "^17.0.0"
"eslint-plugin-prettier": "^2.6.0",
"jest": "^17.0.0",
"lint-staged": "^6.1.0",
"prettier": "^1.10.2"
},
"dependencies": {
"css-color-keywords": "^1.0.0",
"fbjs": "^0.8.5",
"postcss-value-parser": "^3.3.0"
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
}
}
94 changes: 42 additions & 52 deletions src/TokenStream.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,74 @@
const SYMBOL_BASE_MATCH = 'SYMBOL_BASE_MATCH';
const SYMBOL_MATCH = 'SYMBOL_MATCH';
const SYMBOL_MATCH = 'SYMBOL_MATCH'

module.exports = class TokenStream {
constructor(nodes, parent) {
this.nodes = nodes;
this.parent = parent;
this.lastFunction = null;
this.lastValue = null;
}

get node() {
return this.nodes[0];
this.index = 0
this.nodes = nodes
this.functionName = parent != null ? parent.value : null
this.lastValue = null
this.rewindIndex = -1
}

hasTokens() {
return this.nodes.length > 0;
}

lookAhead() {
return new TokenStream(this.nodes.slice(1), this.parent);
return this.index <= this.nodes.length - 1
}

[SYMBOL_BASE_MATCH](...tokenDescriptors) {
const node = this.node;
[SYMBOL_MATCH](...tokenDescriptors) {
if (!this.hasTokens()) return null

if (!node) return null;
const node = this.nodes[this.index]

for (let i = 0; i < tokenDescriptors.length; i += 1) {
const tokenDescriptor = tokenDescriptors[i];
const value = tokenDescriptor(node);
if (value !== null) return value;
const tokenDescriptor = tokenDescriptors[i]
const value = tokenDescriptor(node)
if (value !== null) {
this.index += 1
this.lastValue = value
return value
}
}

return null;
}

[SYMBOL_MATCH](...tokenDescriptors) {
const value = this[SYMBOL_BASE_MATCH](...tokenDescriptors);
if (value === null) return null;
this.nodes = this.nodes.slice(1);
this.lastFunction = null;
this.lastValue = value;
return value;
}

test(...tokenDescriptors) {
return this[SYMBOL_BASE_MATCH](...tokenDescriptors) !== null;
return null
}

matches(...tokenDescriptors) {
return this[SYMBOL_MATCH](...tokenDescriptors) !== null;
return this[SYMBOL_MATCH](...tokenDescriptors) !== null
}

expect(...tokenDescriptors) {
const value = this[SYMBOL_MATCH](...tokenDescriptors);
if (value !== null) return value;
return this.throw();
const value = this[SYMBOL_MATCH](...tokenDescriptors)
return value !== null ? value : this.throw()
}

matchesFunction() {
const node = this.node;
if (node.type !== 'function') return null;
const value = new TokenStream(node.nodes, node);
this.nodes = this.nodes.slice(1);
this.lastFunction = value;
this.lastValue = null;
return value;
const node = this.nodes[this.index]
if (node.type !== 'function') return null
const value = new TokenStream(node.nodes, node)
this.index += 1
this.lastValue = null
return value
}

expectFunction() {
const value = this.matchesFunction();
if (value !== null) return value;
return this.throw();
const value = this.matchesFunction()
return value !== null ? value : this.throw()
}

expectEmpty() {
if (this.hasTokens()) this.throw();
if (this.hasTokens()) this.throw()
}

throw() {
throw new Error(`Unexpected token type: ${this.node.type}`);
throw new Error(`Unexpected token type: ${this.nodes[this.index].type}`)
}

saveRewindPoint() {
this.rewindIndex = this.index
}

rewind() {
if (this.rewindIndex === -1) throw new Error('Internal error')
this.index = this.rewindIndex
this.lastValue = null
}
};
}
97 changes: 52 additions & 45 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,73 @@
/* eslint-disable no-param-reassign */
const parse = require('postcss-value-parser');
const camelizeStyleName = require('fbjs/lib/camelizeStyleName');
const transforms = require('./transforms');
const TokenStream = require('./TokenStream');
const parse = require('postcss-value-parser')
const camelizeStyleName = require('fbjs/lib/camelizeStyleName')
const transforms = require('./transforms')
const TokenStream = require('./TokenStream')

// Note if this is wrong, you'll need to change tokenTypes.js too
const numberOrLengthRe = /^([+-]?(?:\d*\.)?\d+(?:[Ee][+-]?\d+)?)(?:px)?$/i;
const boolRe = /^true|false$/i;
const nullRe = /^null$/i;
const undefinedRe = /^undefined$/i;
const numberOrLengthRe = /^([+-]?(?:\d*\.)?\d+(?:[Ee][+-]?\d+)?)(?:px)?$/i
const boolRe = /^true|false$/i
const nullRe = /^null$/i
const undefinedRe = /^undefined$/i

// Undocumented export
export const transformRawValue = (input) => {
const value = input.trim();
export const transformRawValue = input => {
const value = input.trim()

const numberMatch = value.match(numberOrLengthRe);
if (numberMatch !== null) return Number(numberMatch[1]);
const numberMatch = value.match(numberOrLengthRe)
if (numberMatch !== null) return Number(numberMatch[1])

const boolMatch = input.match(boolRe);
if (boolMatch !== null) return boolMatch[0].toLowerCase() === 'true';
const boolMatch = input.match(boolRe)
if (boolMatch !== null) return boolMatch[0].toLowerCase() === 'true'

const nullMatch = input.match(nullRe);
if (nullMatch !== null) return null;
const nullMatch = input.match(nullRe)
if (nullMatch !== null) return null

const undefinedMatch = input.match(undefinedRe);
if (undefinedMatch !== null) return undefined;
const undefinedMatch = input.match(undefinedRe)
if (undefinedMatch !== null) return undefined

return value;
};
return value
}

const baseTransformShorthandValue = (propName, inputValue) => {
const ast = parse(inputValue.trim());
const tokenStream = new TokenStream(ast.nodes);
return transforms[propName](tokenStream);
};
const ast = parse(inputValue.trim())
const tokenStream = new TokenStream(ast.nodes)
return transforms[propName](tokenStream)
}

const transformShorthandValue = (process.env.NODE_ENV === 'production')
? baseTransformShorthandValue
: (propName, inputValue) => {
try {
return baseTransformShorthandValue(propName, inputValue);
} catch (e) {
throw new Error(`Failed to parse declaration "${propName}: ${inputValue}"`);
}
};
const transformShorthandValue =
process.env.NODE_ENV === 'production'
? baseTransformShorthandValue
: (propName, inputValue) => {
try {
return baseTransformShorthandValue(propName, inputValue)
} catch (e) {
throw new Error(
`Failed to parse declaration "${propName}: ${inputValue}"`
)
}
}

export const getStylesForProperty = (propName, inputValue, allowShorthand) => {
const isRawValue = (allowShorthand === false) || !(propName in transforms);
const isRawValue = allowShorthand === false || !(propName in transforms)
const propValue = isRawValue
? transformRawValue(inputValue)
: transformShorthandValue(propName, inputValue.trim());
: transformShorthandValue(propName, inputValue.trim())

return (propValue && propValue.$merge)
return propValue && propValue.$merge
? propValue.$merge
: { [propName]: propValue };
};
: { [propName]: propValue }
}

export const getPropertyName = camelizeStyleName;
export const getPropertyName = camelizeStyleName

export default (rules, shorthandBlacklist = []) => rules.reduce((accum, rule) => {
const propertyName = getPropertyName(rule[0]);
const value = rule[1];
const allowShorthand = shorthandBlacklist.indexOf(propertyName) === -1;
return Object.assign(accum, getStylesForProperty(propertyName, value, allowShorthand));
}, {});
export default (rules, shorthandBlacklist = []) =>
rules.reduce((accum, rule) => {
const propertyName = getPropertyName(rule[0])
const value = rule[1]
const allowShorthand = shorthandBlacklist.indexOf(propertyName) === -1
return Object.assign(
accum,
getStylesForProperty(propertyName, value, allowShorthand)
)
}, {})
Loading