diff --git a/src/atomizer.js b/src/atomizer.js index 222c2518..f133c5d4 100644 --- a/src/atomizer.js +++ b/src/atomizer.js @@ -64,11 +64,11 @@ Atomizer.prototype.addRules = function(rules/*:AtomizerRules*/)/*:void*/ { Atomizer.prototype.getSyntax = function (isSimple)/*:string*/ { if (isSimple && !this.syntaxSimple) { - this.syntaxSimple = new Grammar(this.rulesMap, this.helpersMap).getSyntax(true); + this.syntaxSimple = new Grammar(this.rules).getSyntax(true); } if (!isSimple && !this.syntax) { // All Grammar and syntax parsing should be in the Grammar class - this.syntax = new Grammar(this.rulesMap, this.helpersMap).getSyntax(); + this.syntax = new Grammar(this.rules).getSyntax(); } return isSimple ? this.syntaxSimple : this.syntax; @@ -147,11 +147,22 @@ Atomizer.prototype.parseConfig = function (config/*:AtomizerConfig*/, options/*: var rgb; var values; - if (!match || !match.prop) { - return ''; + if (!match || (!match.atomicSelector && !match.selector)) { + // no match, no op + return; } - ruleIndex = this.rulesMap[match.prop] || this.helpersMap[match.prop] || this.helpersMap[match.helperProp]; + // check where this rule belongs to + if (this.rulesMap.hasOwnProperty(match.atomicSelector)) { + ruleIndex = this.rulesMap[match.atomicSelector]; + } else if (this.helpersMap.hasOwnProperty(match.atomicSelector)) { + ruleIndex = this.helpersMap[match.atomicSelector]; + } else if (this.helpersMap.hasOwnProperty(match.selector)) { + ruleIndex = this.helpersMap[match.selector]; + } else { + // not a valid class, no op + return; + } // get the rule that this class name belongs to. // this is why we created the dictionary @@ -238,7 +249,7 @@ Atomizer.prototype.parseConfig = function (config/*:AtomizerConfig*/, options/*: } // now check if named value was passed in the config else { - propAndValue = [match.prop, '(', matchVal.named, ')'].join(''); + propAndValue = [match.atomicSelector, '(', matchVal.named, ')'].join(''); // no custom, warn it if (!config.custom) { diff --git a/src/helpers.js b/src/helpers.js index 7c62545e..b894a127 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -26,6 +26,7 @@ module.exports = [ "type": "helper", "name": "Border", "matcher": "Bd", + "noParams": true, "styles": { "border-width": "1px", "border-style": "solid" @@ -36,6 +37,7 @@ module.exports = [ "type": "helper", "name": "Border X 1px solid", "matcher": "BdX", + "noParams": true, "styles": { "border-top-width": 0, "border-right-width": "1px", @@ -49,6 +51,7 @@ module.exports = [ "type": "helper", "name": "Border Y 1px solid", "matcher": "BdY", + "noParams": true, "styles": { "border-top-width": "1px", "border-right-width": 0, @@ -63,6 +66,7 @@ module.exports = [ "type": "helper", "name": "Border Top 1px solid", "matcher": "BdT", + "noParams": true, "styles": { "border-top-width": "1px", "border-right-width": 0, @@ -76,6 +80,7 @@ module.exports = [ "type": "helper", "name": "Border End 1px solid", "matcher": "BdEnd", + "noParams": true, "styles": { "border-top-width": 0, "border-__END__-width": "1px", @@ -89,6 +94,7 @@ module.exports = [ "type": "helper", "name": "Border Bottom 1px solid", "matcher": "BdB", + "noParams": true, "styles": { "border-top-width": 0, "border-right-width": 0, @@ -102,6 +108,7 @@ module.exports = [ "type": "helper", "name": "Border Start 1px solid", "matcher": "BdStart", + "noParams": true, "styles": { "border-top-width": 0, "border-__END__-width": 0, @@ -121,6 +128,7 @@ module.exports = [ "type": "helper", "name": "BfcHack", "matcher": "BfcHack", + "noParams": true, "styles": { "display": "table-cell", "width": "1600px", /* 1 */ @@ -137,6 +145,7 @@ module.exports = [ "type": "helper", "name": "Clearfix", "matcher": "Cf", + "noParams": true, "styles": { "zoom": 1 }, @@ -160,6 +169,7 @@ module.exports = [ "type": "helper", "name": "Ellipsis", "matcher": "Ell", + "noParams": true, "styles": { "max-width": "100%", "white-space": "nowrap", @@ -189,6 +199,7 @@ module.exports = [ "type": "helper", "name": "Hidden", "matcher": "Hidden", + "noParams": true, "styles": { "position": "absolute !important", "clip": "rect(1px 1px 1px 1px)", @@ -210,6 +221,7 @@ module.exports = [ "type": "helper", "name": "IbBox", "matcher": "IbBox", + "noParams": true, "styles": { "display": "inline-block", "*display": "inline", @@ -273,6 +285,7 @@ module.exports = [ "type": "helper", "name": "Row", "matcher": "Row", + "noParams": true, "styles": { "clear": "both", "display": "inline-block", @@ -294,6 +307,7 @@ module.exports = [ "type": "helper", "name": "StretchedBox", "matcher": "StretchedBox", + "noParams": true, "styles": { "position": "absolute", "top": 0, @@ -312,6 +326,7 @@ module.exports = [ "type": "helper", "name": "Zoom", "matcher": "Zoom", + "noParams": true, "styles": { "zoom": "1" } diff --git a/src/lib/grammar.js b/src/lib/grammar.js index fe359968..1de90974 100644 --- a/src/lib/grammar.js +++ b/src/lib/grammar.js @@ -51,6 +51,8 @@ var GRAMMAR = { 'BOUNDARY' : '(?:^|\\s|"|\'|\{)', 'PARENT' : '[a-zA-Z][-_a-zA-Z0-9]+?', 'PARENT_SEP' : '[>_+]', + // all characters allowed to be a prop + 'PROP' : '[A-Za-z]+', // all character allowed to be in values 'VALUES' : '[-_,.#$/%0-9a-zA-Z]+', 'FRACTION' : '(?[0-9]+)\\/(?[1-9](?:[0-9]+)?)', @@ -131,33 +133,37 @@ var VALUE_SYNTAXE = XRegExp([ * this is important so "B" doesn't match "Bgc" * e.g. Use (Bgc|B) instead of (B|Bgc) */ -function getSortedKeys(map) { - return Object.keys(map).sort(function (a, b) { +function getSortedKeys(arr) { + return arr.length > 1 ? arr.sort(function (a, b) { return a > b ? -1 : 1; - }).join('|'); + }).join('|') : arr.toString(); } -function buildRegex(map, isParamRequired) { - var keys = getSortedKeys(map); - - return keys.length && [ - // matcher - '(?', - keys, - ')', - '(?:\\(', - '(?', - GRAMMAR.VALUES, - ')', - '\\))', - isParamRequired ? '?' : '' - ].join(''); +function buildRegex(matchersParams, matchersNoParams) { + matchersParams = matchersParams ? '(?' + matchersParams + ')\\((?' + GRAMMAR.VALUES + ')\\)' : ''; + matchersNoParams = matchersNoParams ? '(?' + matchersNoParams + ')' : ''; + return '(?:' + [matchersParams, matchersNoParams].join('|') + ')'; } -function Grammar(rulesMap, helpersMap) { - this.mainSyntax = []; - this.addSyntaxRegex(buildRegex(rulesMap)); - this.addSyntaxRegex(buildRegex(helpersMap, false)); +function Grammar(rules) { + var matchersParams = []; + var matchersNoParams = []; + var matchersParamsStr; + var matchersNoParamsStr; + + rules.forEach(function (rule) { + if (rule.noParams) { + matchersNoParams.push(rule.matcher); + } else { + matchersParams.push(rule.matcher); + } + }); + + matchersParamsStr = getSortedKeys(matchersParams); + matchersNoParamsStr = getSortedKeys(matchersNoParams); + + this.simpleSyntax = buildRegex(GRAMMAR.PROP, matchersNoParamsStr); + this.complexSyntax = buildRegex(matchersParamsStr, matchersNoParamsStr); } /** @@ -171,26 +177,6 @@ Grammar.matchValue = function matchValue(value) { return XRegExp.exec(value, VALUE_SYNTAXE); }; -Grammar.prototype.addSyntaxRegex = function addRegex(regex)/*:string*/ { - regex && this.mainSyntax.push(regex); -}; - -Grammar.prototype.getMainSyntax = function getMainSyntax(isSimple)/*:string*/ { - // simple regex makes the search faster - // we don't care if the prop is valid on a simple case - // we just care that the syntax is correct and we capture each group - if (isSimple) { - return [ - '(?:', - '(?[A-Za-z]+)', - '\\((?', GRAMMAR.VALUES, ')\\)', - ')', - ].join(''); - } else { - return this.mainSyntax.length > 1 ? '(?:' + this.mainSyntax.join('|') + ')' : this.mainSyntax[0]; - } -}; - Grammar.prototype.getSyntax = function getSyntax(isSimple)/*:string*/ { var syntax = [ // word boundary @@ -199,7 +185,8 @@ Grammar.prototype.getSyntax = function getSyntax(isSimple)/*:string*/ { '(?', isSimple ? GRAMMAR.PARENT_SELECTOR_SIMPLE : GRAMMAR.PARENT_SELECTOR, ')?', - this.getMainSyntax(isSimple), + // the main syntax + isSimple ? this.simpleSyntax : this.complexSyntax, '(?', GRAMMAR.IMPORTANT, ')?', diff --git a/tests/atomizer.js b/tests/atomizer.js index 4ff4155c..21ea2a73 100644 --- a/tests/atomizer.js +++ b/tests/atomizer.js @@ -130,6 +130,16 @@ describe('Atomizer()', function () { }); expect(result).to.deep.equal(expected); }); + it('returns empty object if invalid class names have been passed', function () { + var atomizer = new Atomizer(); + var expected = {}; + var result = atomizer.parseConfig({ + classNames: [ + 'RandomInvalidClass' + ] + }); + expect(result).to.deep.equal(expected); + }); }); describe('getConfig()', function () { it ('returns a valid config object when given classes and no config', function () { @@ -387,6 +397,7 @@ describe('Atomizer()', function () { type: 'helper', name: 'Bar', matcher: 'Bar', + noParams: true, styles: { 'bar': 'foo' } @@ -396,6 +407,7 @@ describe('Atomizer()', function () { type: 'helper', name: 'Baz', matcher: 'Baz', + noParams: true, styles: { 'baz': 'foo' } @@ -518,6 +530,7 @@ describe('Atomizer()', function () { type: 'helper', name: 'Foo', matcher: 'Foo', + noParams: true, styles: { foo: 'bar' } @@ -587,6 +600,7 @@ describe('Atomizer()', function () { type: 'pattern', name: 'color', matcher: 'C', + noParams: true, styles: { 'color': '$0' } @@ -595,6 +609,7 @@ describe('Atomizer()', function () { type: 'pattern', name: 'display', matcher: 'D', + noParams: true, styles: { 'display': '$0' }, @@ -606,6 +621,7 @@ describe('Atomizer()', function () { type: 'helper', name: 'foo', matcher: 'Foo', + noParams: true, styles: { 'font-weight': 'bold' }