You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

css-info

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

css-info - npm Package Compare versions

Comparing version

to
0.3.0

lib/CssValue.js

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": {

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);
});
});