Comparing version
278
index.js
@@ -0,3 +1,4 @@ | ||
var destructureDeclaration = require('./lib/destructureDeclaration.js'); | ||
var css = require('css'); | ||
var _ = require('underscore'); | ||
var _ = require('lodash'); | ||
var Immutable = require('immutable'); | ||
@@ -9,168 +10,32 @@ | ||
var CssValue = function(regex, options) { | ||
_.extend(this, options); | ||
var regexSource = regex.source; | ||
this.getAllRegex = this.getAllRegex || new RegExp('(?:^|\s+)('+regexSource+')(?:$|\s+)', 'g'); | ||
this.getOneRegex = this.getOneRegex || new RegExp('(?:^|\s+)('+regexSource+')(?:$|\s+)', ''); | ||
this.getNextRegex = this.getNextRegex || new RegExp('^(?:\s*)('+regexSource+')(?:$|\s+)', ''); | ||
}; | ||
CssValue.prototype.getAll = function(str, cb) { | ||
var matches = []; | ||
var result = str.replace(this.getAllRegex, function() { | ||
matches.push(arguments[1]); | ||
return ''; | ||
function parseValidSelector(selector) { | ||
var selectorParts = selector.split(/(?!^)(?=[.:])/g); | ||
var selectorClasses = []; | ||
var selectorPseudoClasses = []; | ||
selectorParts.forEach(function(part) { | ||
if(part.substr(0,1) == '.') { | ||
selectorClasses.push(part.substr(1)); | ||
} else { | ||
selectorPseudoClasses.push(part.substr(1)); | ||
} | ||
}); | ||
cb(matches); | ||
return result; | ||
}; | ||
return { | ||
classes: selectorClasses, | ||
pseudoClasses: selectorPseudoClasses, | ||
}; | ||
} | ||
//XXX Currently accepts all alphabetical strings as units | ||
//XXX Currently accepts unitless numbers that are not zero | ||
CssValue.lengthValue = new CssValue(/^[+-]?(\d+|\d*\.\d+)([a-z]+)?$/); | ||
CssValue.borderStyle = new CssValue(/^(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/); | ||
//XXX Currently accepts anything wrapped in rgb(), rgba(), hsl() and hsla() as color | ||
//XXX Currently accepts hexstring of any length as color | ||
//XXX Currently accepts all alpabetical strings as colors | ||
CssValue.color = new CssValue(/^((rgba?|hsla?)\(.*?\)|#[0-9a-fA-F]+|[a-zA-Z]+)$/); | ||
function destructureDeclaration(declaration) { | ||
var propertyMatches, destructuredDeclarations, values; | ||
// border-top, border-right, border-bottom, border-left | ||
if((propertyMatches = declaration.property.match(/^border-(top|right|bottom|left)$/))) { | ||
destructuredDeclarations = {}; | ||
declaration.value = CssValue.lengthValue.getAll(declaration.value, function(values) { | ||
if(values.length) { | ||
destructuredDeclarations[propertyMatches[0]+'-width'] = values[values.length-1]; | ||
} else { | ||
destructuredDeclarations[propertyMatches[0]+'-width'] = 'initial'; | ||
} | ||
}); | ||
declaration.value = CssValue.borderStyle.getAll(declaration.value, function(values) { | ||
if(values.length) { | ||
destructuredDeclarations[propertyMatches[0]+'-style'] = values[values.length-1]; | ||
} else { | ||
destructuredDeclarations[propertyMatches[0]+'-style'] = 'initial'; | ||
} | ||
}); | ||
declaration.value = CssValue.color.getAll(declaration.value, function(values) { | ||
if(values.length) { | ||
destructuredDeclarations[propertyMatches[0]+'-color'] = values[values.length-1]; | ||
} else { | ||
destructuredDeclarations[propertyMatches[0]+'-color'] = 'initial'; | ||
} | ||
}); | ||
return _.map(destructuredDeclarations, function(value, property) { | ||
return { | ||
property: property, | ||
value: value | ||
}; | ||
}); | ||
} | ||
// border | ||
else if((propertyMatches = declaration.property.match(/^border$/))) { | ||
destructuredDeclarations = {}; | ||
declaration.value = CssValue.lengthValue.getAll(declaration.value, function(values) { | ||
if(values.length) { | ||
destructuredDeclarations['border-top-width'] = values[values.length-1]; | ||
destructuredDeclarations['border-right-width'] = values[values.length-1]; | ||
destructuredDeclarations['border-bottom-width'] = values[values.length-1]; | ||
destructuredDeclarations['border-left-width'] = values[values.length-1]; | ||
} else { | ||
destructuredDeclarations['border-top-width'] = 'initial'; | ||
destructuredDeclarations['border-right-width'] = 'initial'; | ||
destructuredDeclarations['border-bottom-width'] = 'initial'; | ||
destructuredDeclarations['border-left-width'] = 'initial'; | ||
} | ||
}); | ||
declaration.value = CssValue.borderStyle.getAll(declaration.value, function(values) { | ||
if(values.length) { | ||
destructuredDeclarations['border-top-style'] = values[values.length-1]; | ||
destructuredDeclarations['border-right-style'] = values[values.length-1]; | ||
destructuredDeclarations['border-bottom-style'] = values[values.length-1]; | ||
destructuredDeclarations['border-left-style'] = values[values.length-1]; | ||
} else { | ||
destructuredDeclarations['border-top-style'] = 'initial'; | ||
destructuredDeclarations['border-right-style'] = 'initial'; | ||
destructuredDeclarations['border-bottom-style'] = 'initial'; | ||
destructuredDeclarations['border-left-style'] = 'initial'; | ||
} | ||
}); | ||
declaration.value = CssValue.color.getAll(declaration.value, function(values) { | ||
if(values.length) { | ||
destructuredDeclarations['border-top-color'] = values[values.length-1]; | ||
destructuredDeclarations['border-right-color'] = values[values.length-1]; | ||
destructuredDeclarations['border-bottom-color'] = values[values.length-1]; | ||
destructuredDeclarations['border-left-color'] = values[values.length-1]; | ||
} else { | ||
destructuredDeclarations['border-top-color'] = 'initial'; | ||
destructuredDeclarations['border-right-color'] = 'initial'; | ||
destructuredDeclarations['border-bottom-color'] = 'initial'; | ||
destructuredDeclarations['border-left-color'] = 'initial'; | ||
} | ||
}); | ||
return _.map(destructuredDeclarations, function(value, property) { | ||
return { | ||
property: property, | ||
value: value | ||
}; | ||
}); | ||
} | ||
// border-style, border-width, border-color | ||
else if((propertyMatches = declaration.property.match(/^border-(style|width|color)$/))) { | ||
values = declaration.value.split(/\s+/g); | ||
CssValue[ | ||
{ | ||
style: 'borderStyle', | ||
width: 'lengthValue', | ||
color: 'color', | ||
}[propertyMatches[1]] | ||
].getAll(declaration.value, function(matchedValues) { | ||
values = matchedValues; | ||
}); | ||
destructuredDeclarations = {}; | ||
switch(values.length) { | ||
case 1: | ||
destructuredDeclarations['border-top-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-right-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-bottom-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-left-'+propertyMatches[1]] = values[0]; | ||
break; | ||
case 2: | ||
destructuredDeclarations['border-top-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-right-'+propertyMatches[1]] = values[1]; | ||
destructuredDeclarations['border-bottom-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-left-'+propertyMatches[1]] = values[1]; | ||
break; | ||
case 3: | ||
destructuredDeclarations['border-top-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-right-'+propertyMatches[1]] = values[1]; | ||
destructuredDeclarations['border-bottom-'+propertyMatches[1]] = values[2]; | ||
destructuredDeclarations['border-left-'+propertyMatches[1]] = values[1]; | ||
break; | ||
case 4: | ||
destructuredDeclarations['border-top-'+propertyMatches[1]] = values[0]; | ||
destructuredDeclarations['border-right-'+propertyMatches[1]] = values[1]; | ||
destructuredDeclarations['border-bottom-'+propertyMatches[1]] = values[2]; | ||
destructuredDeclarations['border-left-'+propertyMatches[1]] = values[3]; | ||
break; | ||
} | ||
return _.map(destructuredDeclarations, function(value, property) { | ||
return { | ||
property: property, | ||
value: value | ||
}; | ||
}); | ||
function addValidRule(classesByName, context, rule, selectorParts) { | ||
var className = selectorParts.classes[0]; | ||
classesByName[className] = classesByName[className] || {}; | ||
classesByName[className].declarations = (classesByName[className].declarations || []).concat(rule.get('declarations').toJS()); | ||
if(selectorParts.pseudoClasses.length) { | ||
classesByName[className].states = selectorParts.pseudoClasses; | ||
} else { | ||
return [{ | ||
property: declaration.property, | ||
value: declaration.value, | ||
}]; | ||
classesByName[className].states = []; | ||
} | ||
classesByName[className].medias = context.get('medias').toArray(); | ||
} | ||
function parseRule(classesByName, context, rule) { | ||
function parseRule(classesByName, discardedClassNames, context, rule) { | ||
context = (context || new Immutable.Map({ | ||
@@ -180,45 +45,63 @@ medias: new Immutable.Set() | ||
if(rule.type === 'rule') { | ||
var extendee; | ||
rule.selectors.forEach(function(selector) { | ||
// Skip selectors that are comprised of anything more than classes and pseudo-classes. | ||
if(!/^(\.[\w-]+|:[\w-]+)+$/.test(selector)) return; | ||
var selectorParts = selector.split(/(?!^)(?=[.:])/g); | ||
var selectorClasses = []; | ||
var selectorPseudoClasses = []; | ||
selectorParts.forEach(function(part) { | ||
if(part.substr(0,1) == '.') { | ||
selectorClasses.push(part.substr(1)); | ||
var invalidSelectors = []; | ||
var validSelectors = []; | ||
rule = Immutable.fromJS(rule); | ||
rule.get('selectors').forEach(function(selector) { | ||
var className, selectorParts; | ||
if(!/^(\.[\w-]+|:[\w-]+)+$/.test(selector)) { | ||
// Skip selectors that are comprised of anything more than classes and pseudo-classes. | ||
invalidSelectors.push(selector); | ||
} else { | ||
selectorParts = parseValidSelector(selector); | ||
if(selectorParts.classes.length == 1) { | ||
className = selectorParts.classes[0]; | ||
if(_.includes(discardedClassNames, className)) { | ||
// Skip selector if class is in `discardedClassNames` | ||
invalidSelectors.push(selector); | ||
} else { | ||
if(classesByName[className] && _.xor(classesByName[className].states, selectorParts.pseudoClasses).length !== 0) { | ||
// Skip selector if pseudo-classes doesn't match a previous selector with same class | ||
invalidSelectors.push(selector); | ||
discardedClassNames.push(className); | ||
if(classesByName[className]) { | ||
delete classesByName[className]; | ||
} | ||
} else { | ||
if(classesByName[className] && _.xor(classesByName[className].medias, context.get('medias').toArray()).length !== 0) { | ||
// Skip selector if media queries doesn't match a previous selector with same class | ||
invalidSelectors.push(selector); | ||
discardedClassNames.push(className); | ||
if(classesByName[className]) { | ||
delete classesByName[className]; | ||
} | ||
} else { | ||
// Selector is valid! | ||
addValidRule(classesByName, context, rule, selectorParts); | ||
} | ||
} | ||
} | ||
} else { | ||
selectorPseudoClasses.push(part.substr(1)); | ||
// Skip selectors with more than one class | ||
invalidSelectors.push(selector); | ||
} | ||
}); | ||
if(selectorClasses.length == 1) { | ||
var className = selectorClasses[0]; | ||
classesByName[className] = classesByName[className] || {}; | ||
classesByName[className].declarations = (classesByName[className].declarations || []).concat(rule.declarations); | ||
if(selectorPseudoClasses.length) { | ||
classesByName[className].states = selectorPseudoClasses; | ||
} else { | ||
classesByName[className].states = []; | ||
} | ||
if(extendee) { | ||
classesByName[className].extendees = classesByName[className].extendees || []; | ||
classesByName[className].extendees.push(extendee); | ||
} else if(!selectorPseudoClasses.length) { | ||
extendee = className; | ||
} | ||
classesByName[className].medias = context.get('medias').toArray(); | ||
} | ||
}); | ||
return false; | ||
if(invalidSelectors.length) { | ||
return [rule.set('selectors', invalidSelectors)]; | ||
} else { | ||
return []; | ||
} | ||
} else if(rule.type === 'media') { | ||
rule.rules = rule.rules.filter(_.partial(parseRule, classesByName, context.update('medias', function(medias) { | ||
rule.rules = rule.rules.filter(_.partial(parseRule, classesByName, discardedClassNames, context.update('medias', function(medias) { | ||
return medias.add(rule.media); | ||
}))); | ||
return rule.rules.length > 1; | ||
if(rule.rules.length > 1) { | ||
return [rule]; | ||
} | ||
} | ||
return true; | ||
// return [rule]; | ||
} | ||
module.exports = { | ||
_parseRule: parseRule, | ||
'parse': function(input, options) { | ||
@@ -246,4 +129,5 @@ options = _.extend({ | ||
var rules = file.stylesheet.rules; | ||
var discardedClassNames = []; | ||
var classesByName = {}; | ||
rules = rules.filter(_.partial(parseRule, classesByName, null)); | ||
rules = _.flatMap(rules, _.partial(parseRule, classesByName, discardedClassNames, null)); | ||
var classes = _.values(classesByName); | ||
@@ -293,3 +177,3 @@ Object.keys(classesByName).forEach(function(className) { | ||
classes.forEach(function(classObj) { | ||
classObj.declarationsString = _.chain(classObj.declarationsMap).pairs().sortBy('0').map(joiner).value().join(';'); | ||
classObj.declarationsString = _.chain(classObj.declarationsMap).toPairs().sortBy('0').map(joiner).value().join(';'); | ||
}); | ||
@@ -300,3 +184,3 @@ } | ||
classes.forEach(function(classObj) { | ||
classObj.values = _.chain(classObj.declarationsMap).pairs().pluck('1').uniq().value(); | ||
classObj.values = _.chain(classObj.declarationsMap).values().uniq().value(); | ||
}); | ||
@@ -313,3 +197,3 @@ } | ||
classes.forEach(function(classObj) { | ||
classObj.properties = _.chain(classObj.declarationsMap).pairs().pluck('0').uniq().value(); | ||
classObj.properties = _.chain(classObj.declarationsMap).keys().uniq().value(); | ||
}); | ||
@@ -316,0 +200,0 @@ } |
{ | ||
"name": "css-info", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "", | ||
@@ -23,3 +23,3 @@ "main": "index.js", | ||
"immutable": "^3.7.6", | ||
"underscore": "^1.8.3" | ||
"lodash": "^4.0.0" | ||
}, | ||
@@ -26,0 +26,0 @@ "devDependencies": { |
126
test/test.js
var cssInfo = require('../index.js'); | ||
var expect = require('chai').expect; | ||
describe('parseRule(…)', function() { | ||
it('handles parsed css rule', function() { | ||
var classesByName = {}; | ||
var discardedClassNames = []; | ||
var context = null; | ||
var rule = { | ||
type: 'rule', | ||
selectors: [ | ||
'.yellow' | ||
], | ||
declarations: [ | ||
{ | ||
'property': 'color', | ||
'value': 'yellow', | ||
} | ||
] | ||
}; | ||
var result = cssInfo._parseRule(classesByName, discardedClassNames, context, rule); | ||
expect(result).to.deep.equal([]); | ||
expect(classesByName).to.deep.equal({ | ||
'yellow': { | ||
declarations: [ | ||
{ | ||
'property': 'color', | ||
'value': 'yellow', | ||
} | ||
], | ||
'medias': [], | ||
'states': [] | ||
} | ||
}); | ||
}); | ||
}); | ||
describe('cssInfo.parse(…)', function() { | ||
@@ -10,2 +44,94 @@ it('returns an object', function() { | ||
}); | ||
it('returns an object with classes array', function() { | ||
var input = '.yellow { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.be.an('array'); | ||
}); | ||
it('handles class declaration containing one rule', function() { | ||
var input = '.yellow { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(1); | ||
expect(result.classes[0]).to.have.property('className', 'yellow'); | ||
expect(result.classes[0]).to.have.property('properties').that.deep.equals(['color']); | ||
expect(result.classes[0]).to.have.property('values').that.deep.equals(['yellow']); | ||
}); | ||
it('handles class declaration containing two rules', function() { | ||
var input = '.phm { padding-left: 1rem; padding-right: 1rem; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(1); | ||
expect(result.classes[0]).to.have.property('className', 'phm'); | ||
expect(result.classes[0]).to.have.property('propertiesString').that.equals('padding-left,padding-right'); | ||
expect(result.classes[0]).to.have.property('valuesString').that.equals('1rem'); | ||
}); | ||
it('combines rules with same selectors', function() { | ||
var input = '.phm { padding-left: 1rem; } .phm { padding-right: 1rem; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(1); | ||
expect(result.classes[0]).to.have.property('className', 'phm'); | ||
expect(result.classes[0]).to.have.property('propertiesString').that.equals('padding-left,padding-right'); | ||
expect(result.classes[0]).to.have.property('valuesString').that.equals('1rem'); | ||
}); | ||
it('handles selectors with pseudo-class/state', function() { | ||
var input = '.hover-yellow:hover { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(1); | ||
expect(result.classes[0]).to.have.property('className', 'hover-yellow'); | ||
expect(result.classes[0]).to.have.property('propertiesString').that.equals('color'); | ||
expect(result.classes[0]).to.have.property('valuesString').that.equals('yellow'); | ||
expect(result.classes[0]).to.have.property('statesString').that.equals('hover'); | ||
}); | ||
it('handles selectors with multiple pseudo-classes/states', function() { | ||
var input = '.visited-hover-yellow:hover:visited { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(1); | ||
expect(result.classes[0]).to.have.property('className', 'visited-hover-yellow'); | ||
expect(result.classes[0]).to.have.property('propertiesString').that.equals('color'); | ||
expect(result.classes[0]).to.have.property('valuesString').that.equals('yellow'); | ||
expect(result.classes[0]).to.have.property('statesString').that.equals('hover,visited'); | ||
}); | ||
it('discards selectors without classes', function() { | ||
var input = 'a { color: yellow; } :hover { color: red; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(0); | ||
}); | ||
it('discards selectors with more than one class', function() { | ||
var input = '.color.yellow { color: yellow; } .color.red { color: red; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(0); | ||
}); | ||
it('discards selectors with elements', function() { | ||
var input = 'a.yellow { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(0); | ||
}); | ||
it('discards classes with dissimilar pseudo-classes/states', function() { | ||
var input = '.btn { color: yellow; } .btn:hover { color: red; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(0); | ||
}); | ||
it('handles rules with multiple valid selectors', function() { | ||
var input = '.active-yellow:active, .focus-yellow:focus, .yellow, .hover-yellow:hover { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(4); | ||
}); | ||
it('handles rules with both valid and invalid selectors', function() { | ||
var input = '.active-yellow:active, .focus-yellow:focus, .yellow, .yellow:hover { color: yellow; }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(2); | ||
}); | ||
it('handles selectors with media queries', function() { | ||
var input = '@media (min-width: 40em) { .hover-yellow:hover { color: yellow; } }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(1); | ||
expect(result.classes[0]).to.have.property('className', 'hover-yellow'); | ||
expect(result.classes[0]).to.have.property('propertiesString').that.equals('color'); | ||
expect(result.classes[0]).to.have.property('valuesString').that.equals('yellow'); | ||
expect(result.classes[0]).to.have.property('statesString').that.equals('hover'); | ||
expect(result.classes[0]).to.have.property('mediasString').that.equals('(min-width: 40em)'); | ||
}); | ||
it('discards classes with dissimilar media queries', function() { | ||
var input = '.hover-yellow:hover { color: yellow; } @media (min-width: 40em) { .hover-yellow:hover { color: yellow; } }'; | ||
var result = cssInfo.parse(input); | ||
expect(result.classes).to.have.length(0); | ||
}); | ||
}); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
21113
54.64%7
40%521
51.01%1
Infinity%+ Added
+ Added
- Removed
- Removed