Socket
Socket
Sign inDemoInstall

csso

Package Overview
Dependencies
9
Maintainers
3
Versions
82
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.8.1 to 2.0.0

lib/compressor/clean/Comment.js

19

HISTORY.md

@@ -0,1 +1,20 @@

## 2.0.0 (April 5, 2016)
- No more `gonzales` AST format and related code
- `minify()` and `minifyBlock()` is always return an object as result now (i.e. `{ css: String, map: SourceMapGenerator or null }`)
- `parse()`
- Returns AST in new format (so called `internal`)
- Dynamic scanner implemented
- New AST format + dynamic scanner = performance boost and less memory consumption
- No more `context` argument, context should be specified via `options`
- Supported contexts now: `stylesheet`, `atrule`, `atruleExpression`, `ruleset`, `selector`, `simpleSelector`, `block`, `declaration` and `value`
- Drop `needPositions` option, `positions` option should be used instead
- Drop `needInfo` option, `info` object is attaching to nodes when some information is requested by `options`
- `options` should be an object, otherwise it treats as empty object
- `compress()`
- No more AST converting (performance boost and less memory consumption)
- Drop `outputAst` option
- Returns an object as result instead of AST (i.e. `{ ast: Object }`)
- Drop methods: `justDoIt()`, `stringify()`, `cleanInfo()`
## 1.8.1 (March 30, 2016)

@@ -2,0 +21,0 @@

5

lib/compressor/clean/index.js

@@ -1,2 +0,2 @@

var walk = require('../ast/walk.js').all;
var walk = require('../../utils/walk.js').all;
var handlers = {

@@ -7,3 +7,4 @@ Space: require('./Space.js'),

Declaration: require('./Declaration.js'),
Identifier: require('./Identifier.js')
Identifier: require('./Identifier.js'),
Comment: require('./Comment.js')
};

@@ -10,0 +11,0 @@

2

lib/compressor/compress/Atrule.js

@@ -1,2 +0,2 @@

var resolveKeyword = require('../ast/names.js').keyword;
var resolveKeyword = require('../../utils/names.js').keyword;
var compressKeyframes = require('./atrule/keyframes.js');

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

var walk = require('../ast/walk.js').all;
var walk = require('../../utils/walk.js').all;
var handlers = {

@@ -3,0 +3,0 @@ Atrule: require('./Atrule.js'),

@@ -1,2 +0,2 @@

var resolveName = require('../ast/names.js').property;
var resolveName = require('../../utils/names.js').property;
var handlers = {

@@ -3,0 +3,0 @@ 'font': require('./property/font.js'),

var List = require('../utils/list');
var usageUtils = require('./usage');
var convertToInternal = require('./ast/gonzalesToInternal');
var convertToGonzales = require('./ast/internalToGonzales');
var clean = require('./clean');
var compress = require('./compress');
var restructureBlock = require('./restructure');
var walkRules = require('../utils/walk').rules;
function injectInfo(token) {
for (var i = token.length - 1; i > -1; i--) {
var child = token[i];
if (Array.isArray(child)) {
injectInfo(child);
child.unshift({});
}
}
}
function readBlock(stylesheet, offset) {
var buffer = [];
function readBlock(stylesheet) {
var buffer = new List();
var nonSpaceTokenInBuffer = false;
var protectedComment;
for (var i = offset; i < stylesheet.length; i++) {
var token = stylesheet[i];
if (token[1] === 'comment' &&
token[2].charAt(0) === '!') {
stylesheet.rules.nextUntil(stylesheet.rules.head, function(node, item, list) {
if (node.type === 'Comment' && node.value.charAt(0) === '!') {
if (nonSpaceTokenInBuffer || protectedComment) {
break;
return true;
}
protectedComment = token;
continue;
list.remove(item);
protectedComment = node;
return;
}
if (token[1] !== 's') {
if (node.type !== 'Space') {
nonSpaceTokenInBuffer = true;
}
buffer.push(token);
}
buffer.insert(list.remove(item));
});
return {
comment: protectedComment,
stylesheet: [{}, 'stylesheet'].concat(buffer),
offset: i
stylesheet: {
type: 'StyleSheet',
rules: buffer
}
};

@@ -55,16 +43,20 @@ }

var internalAst = convertToInternal(ast);
logger('convertToInternal', internalAst);
var seed = 1;
ast.firstAtrulesAllowed = ast.firstAtrulesAllowed;
walkRules(ast, function() {
if (!this.stylesheet.id) {
this.stylesheet.id = seed++;
}
});
logger('init', ast);
internalAst.firstAtrulesAllowed = ast.firstAtrulesAllowed;
// remove redundant
clean(internalAst, usageData);
logger('clean', internalAst);
clean(ast, usageData);
logger('clean', ast);
// compress nodes
compress(internalAst, usageData);
logger('compress', internalAst);
compress(ast, usageData);
logger('compress', ast);
return internalAst;
return ast;
}

@@ -74,2 +66,3 @@

options = options || {};
ast = ast || { type: 'StyleSheet', rules: new List() };

@@ -82,3 +75,3 @@ var logger = typeof options.logger === 'function' ? options.logger : Function();

var result = new List();
var block = { offset: 2 };
var block;
var firstAtrulesAllowed = true;

@@ -89,18 +82,23 @@ var blockNum = 1;

var usageData = false;
var info = ast.info || null;
ast = ast || [{}, 'stylesheet'];
if (typeof ast[0] === 'string') {
injectInfo([ast]);
}
if (ast[1] !== 'stylesheet') {
if (ast.type !== 'StyleSheet') {
blockMode = true;
ast = [null, 'stylesheet',
[null, 'ruleset',
[null, 'selector',
[null, 'simpleselector', [null, 'ident', 'x']]],
ast
]
];
ast = {
type: 'StyleSheet',
rules: new List([{
type: 'Ruleset',
selector: {
type: 'Selector',
selectors: new List([{
type: 'SimpleSelector',
sequence: new List([{
type: 'Identifier',
name: 'x'
}])
}])
},
block: ast
}])
};
}

@@ -113,3 +111,4 @@

do {
block = readBlock(ast, block.offset);
block = readBlock(ast);
// console.log(JSON.stringify(block.stylesheet, null, 2));
block.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed;

@@ -134,6 +133,3 @@ block.stylesheet = compressBlock(block.stylesheet, usageData, blockNum++, logger);

result.insert(List.createItem({
type: 'Comment',
value: block.comment[2]
}));
result.insert(List.createItem(block.comment));

@@ -159,9 +155,14 @@ // add \n after comment if block is not empty

result.appendList(blockRules);
} while (block.offset < ast.length);
} while (!ast.rules.isEmpty());
if (blockMode) {
result = result.first().block;
result = !result.isEmpty() ? result.first().block : {
type: 'Block',
info: info,
declarations: new List()
};
} else {
result = {
type: 'StyleSheet',
info: info,
rules: result

@@ -171,7 +172,5 @@ };

if (!options.outputAst || options.outputAst === 'gonzales') {
return convertToGonzales(result);
}
return result;
return {
ast: result
};
};
var utils = require('./utils.js');
var internalWalkRules = require('../ast/walk.js').rules;
var walkRules = require('../../utils/walk.js').rules;

@@ -27,3 +27,3 @@ function processRuleset(node, item, list) {

// try to join by declarations
if (utils.isEqualLists(declarations, prevDeclarations)) {
if (utils.isEqualDeclarations(declarations, prevDeclarations)) {
utils.addSelectors(prevSelectors, selectors);

@@ -44,3 +44,3 @@ list.remove(item);

module.exports = function initialMergeRuleset(ast) {
internalWalkRules(ast, function(node, item, list) {
walkRules(ast, function(node, item, list) {
if (node.type === 'Ruleset') {

@@ -47,0 +47,0 @@ processRuleset(node, item, list);

@@ -1,2 +0,2 @@

var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var walkRulesRight = require('../../utils/walk.js').rulesRight;

@@ -30,3 +30,3 @@ function isMediaRule(node) {

module.exports = function rejoinAtrule(ast) {
internalWalkRulesRight(ast, function(node, item, list) {
walkRulesRight(ast, function(node, item, list) {
if (node.type === 'Atrule') {

@@ -33,0 +33,0 @@ processAtrule(node, item, list);

var List = require('../../utils/list.js');
var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var walkRulesRight = require('../../utils/walk.js').rulesRight;

@@ -37,3 +37,3 @@ function processRuleset(node, item, list) {

module.exports = function disjoinRuleset(ast) {
internalWalkRulesRight(ast, function(node, item, list) {
walkRulesRight(ast, function(node, item, list) {
if (node.type === 'Ruleset') {

@@ -40,0 +40,0 @@ processRuleset(node, item, list);

var List = require('../../utils/list.js');
var translate = require('../ast/translate.js');
var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var translate = require('../../utils/translate.js');
var walkRulesRight = require('../../utils/walk.js').rulesRight;

@@ -387,3 +387,3 @@ var REPLACE = 1;

internalWalkRulesRight(ast, function(node) {
walkRulesRight(ast, function(node) {
if (node.type !== 'Ruleset') {

@@ -390,0 +390,0 @@ return;

@@ -1,5 +0,5 @@

var resolveProperty = require('../ast/names.js').property;
var resolveKeyword = require('../ast/names.js').keyword;
var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var translate = require('../ast/translate.js');
var resolveProperty = require('../../utils/names.js').property;
var resolveKeyword = require('../../utils/names.js').keyword;
var walkRulesRight = require('../../utils/walk.js').rulesRight;
var translate = require('../../utils/translate.js');
var dontRestructure = {

@@ -222,3 +222,3 @@ 'src': 1 // https://github.com/afelix/csso/issues/50

internalWalkRulesRight(ast, function(node, item, list) {
walkRulesRight(ast, function(node, item, list) {
if (node.type !== 'Ruleset') {

@@ -225,0 +225,0 @@ return;

var utils = require('./utils.js');
var internalWalkRules = require('../ast/walk.js').rules;
var walkRules = require('../../utils/walk.js').rules;

@@ -82,3 +82,3 @@ /*

module.exports = function mergeRuleset(ast) {
internalWalkRules(ast, function(node, item, list) {
walkRules(ast, function(node, item, list) {
if (node.type === 'Ruleset') {

@@ -85,0 +85,0 @@ processRuleset(node, item, list);

var List = require('../../utils/list.js');
var utils = require('./utils.js');
var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var walkRulesRight = require('../../utils/walk.js').rulesRight;

@@ -137,3 +137,3 @@ function calcSelectorLength(list) {

module.exports = function restructRuleset(ast) {
internalWalkRulesRight(ast, function(node, item, list) {
walkRulesRight(ast, function(node, item, list) {
if (node.type === 'Ruleset') {

@@ -140,0 +140,0 @@ processRuleset.call(this, node, item, list);

@@ -1,2 +0,2 @@

var translate = require('../../ast/translate.js');
var translate = require('../../../utils/translate.js');

@@ -3,0 +3,0 @@ function Index() {

@@ -1,4 +0,4 @@

var internalWalkRules = require('../../ast/walk.js').rules;
var resolveKeyword = require('../../ast/names.js').keyword;
var translate = require('../../ast/translate.js');
var resolveKeyword = require('../../../utils/names.js').keyword;
var walkRules = require('../../../utils/walk.js').rules;
var translate = require('../../../utils/translate.js');
var createDeclarationIndexer = require('./createDeclarationIndexer.js');

@@ -37,3 +37,3 @@ var processSelector = require('./processSelector.js');

internalWalkRules(ast, function(node) {
walkRules(ast, function(node) {
walk(node, markDeclaration, usageData);

@@ -40,0 +40,0 @@ });

@@ -1,2 +0,2 @@

var translate = require('../../ast/translate.js');
var translate = require('../../../utils/translate.js');
var specificity = require('./specificity.js');

@@ -3,0 +3,0 @@

@@ -7,3 +7,3 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;

while (cursor1 && cursor2 && cursor1.data.id === cursor2.data.id) {
while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) {
cursor1 = cursor1.next;

@@ -20,3 +20,3 @@ cursor2 = cursor2.next;

while (cursor1 && cursor2 && cursor1.data.id === cursor2.data.id) {
while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) {
cursor1 = cursor1.next;

@@ -23,0 +23,0 @@ cursor2 = cursor2.next;

var parse = require('./parser');
var compress = require('./compressor');
var traslateInternal = require('./compressor/ast/translate');
var traslateInternalWithSourceMap = require('./compressor/ast/translateWithSourceMap');
var internalWalkers = require('./compressor/ast/walk');
var walk = require('./utils/walker');
var translate = require('./utils/translate');
var stringify = require('./utils/stringify');
var cleanInfo = require('./utils/cleanInfo');
var translateWithSourceMap = require('./utils/translateWithSourceMap');
var walkers = require('./utils/walk');
var justDoIt = function(src, noStructureOptimizations, needInfo) {
console.warn('`csso.justDoIt()` method is deprecated, use `csso.minify()` instead');
var ast = parse(src, 'stylesheet', needInfo);
var compressed = compress(ast, {
restructure: !noStructureOptimizations,
outputAst: 'internal'
});
return traslateInternal(compressed);
};
function debugOutput(name, options, startTime, data) {

@@ -42,3 +26,3 @@ if (options.debug) {

if (level > 1 && ast) {
var css = traslateInternal(ast, true);
var css = translate(ast, true);

@@ -70,3 +54,2 @@ // when level 2, limit css to 256 symbols

options = copy(options);
options.outputAst = 'internal';

@@ -88,6 +71,6 @@ if (typeof options.logger !== 'function' && options.debug) {

var ast = debugOutput('parsing', options, Date.now(),
parse(source, context, {
parse(source, {
context: context,
filename: filename,
positions: Boolean(options.sourceMap),
needInfo: true
positions: Boolean(options.sourceMap)
})

@@ -97,3 +80,3 @@ );

// compress
var compressedAst = debugOutput('compress', options, Date.now(),
var compressResult = debugOutput('compress', options, Date.now(),
compress(ast, buildCompressOptions(options))

@@ -105,3 +88,3 @@ );

result = debugOutput('translateWithSourceMap', options, Date.now(), (function() {
var tmp = traslateInternalWithSourceMap(compressedAst);
var tmp = translateWithSourceMap(compressResult.ast);
tmp.map._file = filename; // since other tools can relay on file in source map transform chain

@@ -112,5 +95,6 @@ tmp.map.setSourceContent(filename, source);

} else {
result = debugOutput('translate', options, Date.now(),
traslateInternal(compressedAst)
);
result = debugOutput('translate', options, Date.now(), {
css: translate(compressResult.ast),
map: null
});
}

@@ -126,3 +110,3 @@

function minifyBlock(source, options) {
return minify('declarations', source, options);
return minify('block', source, options);
}

@@ -133,28 +117,16 @@

// main method
// main methods
minify: minifyStylesheet,
minifyBlock: minifyBlock,
// utils
// step by step
parse: parse,
compress: compress,
translate: translate,
translateWithSourceMap: translateWithSourceMap,
walk: walk,
stringify: stringify,
cleanInfo: cleanInfo,
// internal ast
internal: {
fromGonzales: require('./compressor/ast/gonzalesToInternal'),
toGonzales: require('./compressor/ast/internalToGonzales'),
translate: traslateInternal,
translateWithSourceMap: traslateInternalWithSourceMap,
walk: internalWalkers.all,
walkRules: internalWalkers.rules,
walkRulesRight: internalWalkers.rulesRight
},
// deprecated
justDoIt: justDoIt
// walkers
walk: walkers.all,
walkRules: walkers.rules,
walkRulesRight: walkers.rulesRight
};

@@ -47,51 +47,1 @@ exports.TokenType = {

// }
exports.NodeType = {
AtkeywordType: 'atkeyword',
AtrulebType: 'atruleb',
AtrulerqType: 'atrulerq',
AtrulersType: 'atrulers',
AtrulerType: 'atruler',
AtrulesType: 'atrules',
AttribType: 'attrib',
AttrselectorType: 'attrselector',
BlockType: 'block',
BracesType: 'braces',
ClassType: 'clazz',
CombinatorType: 'combinator',
CommentType: 'comment',
DeclarationType: 'declaration',
DecldelimType: 'decldelim',
DelimType: 'delim',
DimensionType: 'dimension',
FilterType: 'filter',
FiltervType: 'filterv',
FunctionBodyType: 'functionBody',
FunctionExpressionType: 'functionExpression',
FunctionType: 'funktion',
IdentType: 'ident',
ImportantType: 'important',
NamespaceType: 'namespace',
NthselectorType: 'nthselector',
NthType: 'nth',
NumberType: 'number',
OperatorType: 'operator',
PercentageType: 'percentage',
ProgidType: 'progid',
PropertyType: 'property',
PseudocType: 'pseudoc',
PseudoeType: 'pseudoe',
RawType: 'raw',
RulesetType: 'ruleset',
SelectorType: 'selector',
ShashType: 'shash',
SimpleselectorType: 'simpleselector',
StringType: 'string',
StylesheetType: 'stylesheet',
SType: 's',
UnaryType: 'unary',
UnknownType: 'unknown',
UriType: 'uri',
ValueType: 'value',
VhashType: 'vhash'
};
'use strict';
var TokenType = require('./const.js').TokenType;
var NodeType = require('./const.js').NodeType;
var tokenize = require('./tokenize.js');
var cleanInfo = require('../utils/cleanInfo.js');
var TokenType = require('./const').TokenType;
var Scanner = require('./scanner');
var List = require('../utils/list');
var needPositions;
var filename;
var tokens;
var pos;
var scanner;

@@ -30,51 +28,12 @@ var SCOPE_ATRULE_EXPRESSION = 1;

var rules = {
'atkeyword': getAtkeyword,
'atrule': getAtrule,
'attribute': getAttribute,
'block': getBlockWithBrackets,
'braces': getBraces,
'class': getClass,
'combinator': getCombinator,
'comment': getComment,
'declaration': getDeclaration,
'declarations': getBlock,
'dimension': getDimension,
'function': getFunction,
'ident': getIdentifier,
'important': getImportant,
'nth': getNth,
'nthselector': getNthSelector,
'number': getNumber,
'operator': getOperator,
'percentage': getPercentage,
'progid': getProgid,
'property': getProperty,
'pseudoClass': getPseudoClass,
'pseudoElement': getPseudoElement,
'ruleset': getRuleset,
'selector': getSelector,
'shash': getShash,
'simpleselector': getSimpleSelector,
'string': getString,
'stylesheet': getStylesheet,
'unary': getUnary,
'unknown': getUnknown,
'uri': getUri,
'value': getValue,
'vhash': getVhash,
// TODO: remove in 2.0
// for backward capability
'atruleb': getAtrule,
'atruler': getAtrule,
'atrules': getAtrule,
'attrib': getAttribute,
'attrselector': getAttrselector,
'clazz': getClass,
'filter': getDeclaration,
'functionExpression': getOldIEExpression,
'funktion': getFunction,
'pseudoc': getPseudoClass,
'pseudoe': getPseudoElement
var initialContext = {
stylesheet: getStylesheet,
atrule: getAtrule,
atruleExpression: getAtruleExpression,
ruleset: getRuleset,
selector: getSelector,
simpleSelector: getSimpleSelector,
block: getBlock,
declaration: getDeclaration,
value: getValue
};

@@ -93,15 +52,11 @@

if (tokens.length) {
if (pos < tokens.length) {
line = tokens[pos].line;
column = tokens[pos].column;
} else {
pos = tokens.length - 1;
lines = tokens[pos].value.trimRight().split(/\n|\r\n?|\f/);
line = tokens[pos].line + lines.length - 1;
column = lines.length > 1
? lines[lines.length - 1].length + 1
: tokens[pos].column + lines[lines.length - 1].length;
}
if (scanner.token !== null) {
line = scanner.token.line;
column = scanner.token.column;
} else if (scanner.prevToken !== null) {
lines = scanner.prevToken.value.trimRight().split(/\n|\r\n?|\f/);
line = scanner.prevToken.line + lines.length - 1;
column = lines.length > 1
? lines[lines.length - 1].length + 1
: scanner.prevToken.column + lines[lines.length - 1].length;
}

@@ -119,4 +74,4 @@

function eat(tokenType) {
if (pos < tokens.length && tokens[pos].type === tokenType) {
pos++;
if (scanner.token !== null && scanner.token.type === tokenType) {
scanner.next();
return true;

@@ -129,8 +84,7 @@ }

function expectIdentifier(name, eat) {
if (pos < tokens.length) {
var token = tokens[pos];
if (token.type === TokenType.Identifier &&
token.value.toLowerCase() === name) {
if (scanner.token !== null) {
if (scanner.token.type === TokenType.Identifier &&
scanner.token.value.toLowerCase() === name) {
if (eat) {
pos++;
scanner.next();
}

@@ -146,4 +100,4 @@

function expectAny(what) {
if (pos < tokens.length) {
for (var i = 1, type = tokens[pos].type; i < arguments.length; i++) {
if (scanner.token !== null) {
for (var i = 1, type = scanner.token.type; i < arguments.length; i++) {
if (type === arguments[i]) {

@@ -158,11 +112,9 @@ return true;

function getInfo(idx) {
if (needPositions && idx < tokens.length) {
var token = tokens[idx];
function getInfo() {
if (needPositions && scanner.token) {
return {
source: filename,
offset: token.offset,
line: token.line,
column: token.column
offset: scanner.token.offset,
line: scanner.token.line,
column: scanner.token.column
};

@@ -175,22 +127,44 @@ }

function removeTrailingSpaces(list) {
while (list.tail) {
if (list.tail.data.type === 'Space') {
list.remove(list.tail);
} else {
break;
}
}
}
function getStylesheet(nested) {
var stylesheet = [getInfo(pos), NodeType.StylesheetType];
var child = null;
var node = {
type: 'StyleSheet',
info: getInfo(),
rules: new List()
};
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.Space:
stylesheet.push(getS());
scanner.next();
child = null;
break;
case TokenType.Comment:
stylesheet.push(getComment());
// ignore comments except exclamation comments on top level
if (nested || scanner.token.value.charAt(2) !== '!') {
scanner.next();
child = null;
} else {
child = getComment();
}
break;
case TokenType.Unknown:
stylesheet.push(getUnknown());
child = getUnknown();
break;
case TokenType.CommercialAt:
stylesheet.push(getAtrule());
child = getAtrule();
break;

@@ -206,13 +180,32 @@

default:
stylesheet.push(getRuleset());
child = getRuleset();
}
if (child !== null) {
node.rules.insert(List.createItem(child));
}
}
return stylesheet;
return node;
}
function isBlockAtrule(i) {
for (i++; i < tokens.length; i++) {
var type = tokens[i].type;
// '//' ...
// TODO: remove it as wrong thing
function getUnknown() {
var info = getInfo();
var value = scanner.token.value;
eat(TokenType.Unknown);
return {
type: 'Unknown',
info: info,
value: value
};
}
function isBlockAtrule() {
for (var offset = 1, cursor; cursor = scanner.lookup(offset); offset++) {
var type = cursor.type;
if (type === TokenType.RightCurlyBracket) {

@@ -231,82 +224,117 @@ return true;

function getAtkeyword() {
eat(TokenType.CommercialAt);
function getAtruleExpression() {
var child = null;
var node = {
type: 'AtruleExpression',
info: getInfo(),
sequence: new List()
};
return [getInfo(pos - 1), NodeType.AtkeywordType, getIdentifier()];
}
function getAtrule() {
var node = [getInfo(pos), NodeType.AtrulesType, getAtkeyword(pos)];
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.Semicolon:
pos++;
break scan;
case TokenType.LeftCurlyBracket:
if (isBlockAtrule(pos)) {
node[1] = NodeType.AtrulebType;
node.push(getBlockWithBrackets());
} else {
node[1] = NodeType.AtrulerType;
node.push([
{},
NodeType.AtrulerqType
].concat(node.splice(3)));
pos++; // {
var stylesheet = getStylesheet(true);
stylesheet[1] = NodeType.AtrulersType;
node.push(stylesheet);
pos++; // }
}
break scan;
case TokenType.Space:
node.push(getS());
if (node.sequence.isEmpty()) {
scanner.next(); // ignore spaces in beginning
child = null;
} else {
child = getS();
}
break;
case TokenType.Comment:
node.push(getComment());
case TokenType.Comment: // ignore comments
scanner.next();
child = null;
break;
case TokenType.Comma:
node.push(getOperator());
child = getOperator();
break;
case TokenType.Colon:
node.push(getPseudo());
child = getPseudo();
break;
case TokenType.LeftParenthesis:
node.push(getBraces(SCOPE_ATRULE_EXPRESSION));
child = getBraces(SCOPE_ATRULE_EXPRESSION);
break;
default:
node.push(getAny(SCOPE_ATRULE_EXPRESSION));
child = getAny(SCOPE_ATRULE_EXPRESSION);
}
if (child !== null) {
node.sequence.insert(List.createItem(child));
}
}
removeTrailingSpaces(node.sequence);
return node;
}
function getAtrule() {
eat(TokenType.CommercialAt);
var node = {
type: 'Atrule',
info: getInfo(),
name: readIdent(false),
expression: getAtruleExpression(),
block: null
};
if (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.Semicolon:
scanner.next(); // {
break;
case TokenType.LeftCurlyBracket:
scanner.next(); // {
if (isBlockAtrule()) {
node.block = getBlock();
} else {
node.block = getStylesheet(true);
}
eat(TokenType.RightCurlyBracket);
break;
default:
parseError('Unexpected input');
}
}
return node;
}
function getRuleset() {
return [
getInfo(pos),
NodeType.RulesetType,
getSelector(),
getBlockWithBrackets()
];
return {
type: 'Ruleset',
info: getInfo(),
selector: getSelector(),
block: getBlockWithBrackets()
};
}
function getSelector() {
var selector = [getInfo(pos), NodeType.SelectorType];
var isBadSelector = false;
var lastComma = true;
var node = {
type: 'Selector',
info: getInfo(),
selectors: new List()
};
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.LeftCurlyBracket:

@@ -316,22 +344,48 @@ break scan;

case TokenType.Comma:
selector.push([
getInfo(pos++),
NodeType.DelimType
]);
if (lastComma) {
isBadSelector = true;
}
lastComma = true;
scanner.next();
break;
default:
selector.push(getSimpleSelector());
if (!lastComma) {
isBadSelector = true;
}
lastComma = false;
node.selectors.insert(List.createItem(getSimpleSelector()));
if (node.selectors.tail.data.sequence.isEmpty()) {
isBadSelector = true;
}
}
}
return selector;
if (lastComma) {
isBadSelector = true;
// parseError('Unexpected trailing comma');
}
if (isBadSelector) {
node.selectors = new List();
}
return node;
}
function getSimpleSelector(nested) {
var node = [getInfo(pos), NodeType.SimpleselectorType];
var child = null;
var combinator = null;
var node = {
type: 'SimpleSelector',
info: getInfo(),
sequence: new List()
};
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.Comma:

@@ -354,8 +408,14 @@ break scan;

case TokenType.Space:
node.push(getS());
case TokenType.Comment:
scanner.next();
child = null;
break;
case TokenType.Comment:
node.push(getComment());
case TokenType.Space:
child = null;
if (!combinator && node.sequence.head) {
combinator = getCombinator();
} else {
scanner.next();
}
break;

@@ -367,30 +427,35 @@

case TokenType.Solidus:
node.push(getCombinator());
if (combinator && combinator.name !== ' ') {
parseError('Unexpected combinator');
}
child = null;
combinator = getCombinator();
break;
case TokenType.FullStop:
node.push(getClass());
child = getClass();
break;
case TokenType.LeftSquareBracket:
node.push(getAttribute());
child = getAttribute();
break;
case TokenType.NumberSign:
node.push(getShash());
child = getShash();
break;
case TokenType.Colon:
node.push(getPseudo());
child = getPseudo();
break;
case TokenType.HyphenMinus:
case TokenType.LowLine:
case TokenType.Identifier:
case TokenType.Asterisk:
child = getNamespacedIdentifier(false);
break;
case TokenType.HyphenMinus:
case TokenType.DecimalNumber:
node.push(
tryGetPercentage() ||
getNamespacedIdentifier(false)
);
child = tryGetPercentage() || getNamespacedIdentifier(false);
break;

@@ -401,14 +466,16 @@

}
}
return node;
}
if (child !== null) {
if (combinator !== null) {
node.sequence.insert(List.createItem(combinator));
combinator = null;
}
function getBlockWithBrackets() {
var info = getInfo(pos);
var node;
node.sequence.insert(List.createItem(child));
}
}
eat(TokenType.LeftCurlyBracket);
node = getBlock(info);
eat(TokenType.RightCurlyBracket);
if (combinator && combinator.name !== ' ') {
parseError('Unexpected combinator');
}

@@ -418,8 +485,9 @@ return node;

function getBlock(info) {
var node = [info || getInfo(pos), NodeType.BlockType];
function getDeclarations() {
var child = null;
var declarations = new List();
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.RightCurlyBracket:

@@ -429,28 +497,51 @@ break scan;

case TokenType.Space:
node.push(getS());
break;
case TokenType.Comment:
node.push(getComment());
scanner.next();
child = null;
break;
case TokenType.Semicolon: // ;
node.push([
getInfo(pos++),
NodeType.DecldelimType
]);
scanner.next();
child = null;
break;
default:
node.push(getDeclaration());
child = getDeclaration();
}
if (child !== null) {
declarations.insert(List.createItem(child));
}
}
return declarations;
}
function getBlockWithBrackets() {
var info = getInfo();
var node;
eat(TokenType.LeftCurlyBracket);
node = {
type: 'Block',
info: info,
declarations: getDeclarations()
};
eat(TokenType.RightCurlyBracket);
return node;
}
function getBlock() {
return {
type: 'Block',
info: getInfo(),
declarations: getDeclarations()
};
}
function getDeclaration(nested) {
var startPos = pos;
var info = getInfo(pos);
var info = getInfo();
var property = getProperty();
var value;

@@ -460,30 +551,26 @@ eat(TokenType.Colon);

// check it's a filter
for (var j = startPos; j < pos; j++) {
if (tokens[j].value.toLowerCase() === 'filter') {
if (checkProgid(pos)) {
return [
info,
NodeType.FilterType,
property,
getFilterv()
];
}
break;
}
if (/filter$/.test(property.name.toLowerCase()) && checkProgid()) {
value = getFilterValue();
} else {
value = getValue(nested);
}
return [
info,
NodeType.DeclarationType,
property,
getValue(nested)
];
return {
type: 'Declaration',
info: info,
property: property,
value: value
};
}
function getProperty() {
var info = getInfo(pos);
var name = '';
var node = {
type: 'Property',
info: getInfo(),
name: null
};
while (pos < tokens.length) {
var type = tokens[pos].type;
for (; scanner.token !== null; scanner.next()) {
var type = scanner.token.type;

@@ -496,22 +583,26 @@ if (type !== TokenType.Solidus &&

name += tokens[pos++].value;
name += scanner.token.value;
}
return readSC([
info,
NodeType.PropertyType,
[
info,
NodeType.IdentType,
name + readIdent(true)
]
]);
node.name = name + readIdent(true);
readSC();
return node;
}
function getValue(nested) {
var node = [getInfo(pos), NodeType.ValueType];
var child = null;
var node = {
type: 'Value',
info: getInfo(),
important: false,
sequence: new List()
};
readSC();
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.RightCurlyBracket:

@@ -528,11 +619,12 @@ case TokenType.Semicolon:

case TokenType.Space:
node.push(getS());
child = getS();
break;
case TokenType.Comment:
node.push(getComment());
case TokenType.Comment: // ignore comments
scanner.next();
child = null;
break;
case TokenType.NumberSign:
node.push(getVhash());
child = getVhash();
break;

@@ -542,3 +634,3 @@

case TokenType.Comma:
node.push(getOperator());
child = getOperator();
break;

@@ -548,7 +640,8 @@

case TokenType.LeftSquareBracket:
node.push(getBraces(SCOPE_VALUE));
child = getBraces(SCOPE_VALUE);
break;
case TokenType.ExclamationMark:
node.push(getImportant());
node.important = getImportant();
child = null;
break;

@@ -558,14 +651,15 @@

// check for unicode range: U+0F00, U+0F00-0FFF, u+0F00??
if (tokens[pos].type === TokenType.Identifier) {
var prefix = tokens[pos].value;
if ((prefix === 'U' || prefix === 'u') &&
pos + 1 < tokens.length &&
tokens[pos + 1].type === TokenType.PlusSign) {
pos += 2;
if (scanner.token.type === TokenType.Identifier) {
var prefix = scanner.token.value;
if (prefix === 'U' || prefix === 'u') {
if (scanner.lookupType(1, TokenType.PlusSign)) {
scanner.next(); // U or u
scanner.next(); // +
node.push([
getInfo(pos),
NodeType.IdentType,
prefix + '+' + getUnicodeRange(true)
]);
child = {
type: 'Identifier',
info: getInfo(), // FIXME: wrong position
name: prefix + '+' + readUnicodeRange(true)
};
}
break;

@@ -575,6 +669,12 @@ }

node.push(getAny(SCOPE_VALUE));
child = getAny(SCOPE_VALUE);
}
if (child !== null) {
node.sequence.insert(List.createItem(child));
}
}
removeTrailingSpaces(node.sequence);
return node;

@@ -585,5 +685,3 @@ }

function getAny(scope) {
var startPos = pos;
switch (tokens[pos].type) {
switch (scanner.token.type) {
case TokenType.String:

@@ -603,7 +701,7 @@ return getString();

if (number !== null) {
if (pos < tokens.length) {
if (tokens[pos].type === TokenType.PercentSign) {
return getPercentage(startPos, number);
} else if (tokens[pos].type === TokenType.Identifier) {
return getDimension(startPos, number);
if (scanner.token !== null) {
if (scanner.token.type === TokenType.PercentSign) {
return getPercentage(number);
} else if (scanner.token.type === TokenType.Identifier) {
return getDimension(number.value);
}

@@ -615,11 +713,12 @@ }

if (tokens[pos].type === TokenType.HyphenMinus &&
pos < tokens.length &&
(tokens[pos + 1].type === TokenType.Identifier || tokens[pos + 1].type === TokenType.HyphenMinus)) {
break;
if (scanner.token.type === TokenType.HyphenMinus) {
var next = scanner.lookup(1);
if (next && (next.type === TokenType.Identifier || next.type === TokenType.HyphenMinus)) {
break;
}
}
if (tokens[pos].type === TokenType.HyphenMinus ||
tokens[pos].type === TokenType.PlusSign) {
return getUnary();
if (scanner.token.type === TokenType.HyphenMinus ||
scanner.token.type === TokenType.PlusSign) {
return getOperator();
}

@@ -633,7 +732,6 @@

var ident = getIdentifier();
var ident = getIdentifier(false);
if (pos < tokens.length && tokens[pos].type === TokenType.LeftParenthesis) {
pos = startPos;
return getFunction(scope);
if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) {
return getFunction(scope, ident);
}

@@ -644,35 +742,65 @@

function readAttrselector() {
expectAny('Attribute selector (=, ~=, ^=, $=, *=, |=)',
TokenType.EqualsSign, // =
TokenType.Tilde, // ~=
TokenType.CircumflexAccent, // ^=
TokenType.DollarSign, // $=
TokenType.Asterisk, // *=
TokenType.VerticalLine // |=
);
var name;
if (scanner.token.type === TokenType.EqualsSign) {
name = '=';
scanner.next();
} else {
name = scanner.token.value + '=';
scanner.next();
eat(TokenType.EqualsSign);
}
return name;
}
// '[' S* attrib_name ']'
// '[' S* attrib_name attrib_match [ IDENT | STRING ] S* attrib_flags? ']'
// '[' S* attrib_name S* attrib_match S* [ IDENT | STRING ] S* attrib_flags? S* ']'
function getAttribute() {
var node = [getInfo(pos), NodeType.AttribType];
var node = {
type: 'Attribute',
info: getInfo(),
name: null,
operator: null,
value: null,
flags: null
};
eat(TokenType.LeftSquareBracket);
readSC(node);
readSC();
node.push(getNamespacedIdentifier(true));
node.name = getNamespacedIdentifier(true);
readSC(node);
readSC();
if (pos < tokens.length && tokens[pos].type !== TokenType.RightSquareBracket) {
node.push(getAttrselector());
readSC(node);
if (scanner.token !== null && scanner.token.type !== TokenType.RightSquareBracket) {
node.operator = readAttrselector();
if (pos < tokens.length && tokens[pos].type === TokenType.String) {
node.push(getString());
readSC();
if (scanner.token !== null && scanner.token.type === TokenType.String) {
node.value = getString();
} else {
node.push(getIdentifier());
node.value = getIdentifier(false);
}
readSC(node);
readSC();
// attribute flags
if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) {
node.push([
getInfo(pos),
'attribFlags',
tokens[pos++].value
]);
readSC(node);
if (scanner.token !== null && scanner.token.type === TokenType.Identifier) {
node.flags = scanner.token.value;
scanner.next();
readSC();
}

@@ -686,36 +814,14 @@ }

function getAttrselector() {
expectAny('Attribute selector (=, ~=, ^=, $=, *=, |=)',
TokenType.EqualsSign, // =
TokenType.Tilde, // ~=
TokenType.CircumflexAccent, // ^=
TokenType.DollarSign, // $=
TokenType.Asterisk, // *=
TokenType.VerticalLine // |=
);
var startPos = pos;
var name;
if (tokens[pos].type === TokenType.EqualsSign) {
name = '=';
pos++;
} else {
name = tokens[pos].value + '=';
pos++;
eat(TokenType.EqualsSign);
}
return [getInfo(startPos), NodeType.AttrselectorType, name];
}
function getBraces(scope) {
expectAny('Parenthesis or square bracket',
TokenType.LeftParenthesis,
TokenType.LeftSquareBracket
);
var close;
var child = null;
var node = {
type: 'Braces',
info: getInfo(),
open: scanner.token.value,
close: null,
sequence: new List()
};
if (tokens[pos].type === TokenType.LeftParenthesis) {
if (scanner.token.type === TokenType.LeftParenthesis) {
close = TokenType.RightParenthesis;

@@ -726,29 +832,25 @@ } else {

var node = [
getInfo(pos),
NodeType.BracesType,
tokens[pos].value,
null
];
// left brace
pos++;
scanner.next();
readSC();
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case close:
node[3] = tokens[pos].value;
node.close = scanner.token.value;
break scan;
case TokenType.Space:
node.push(getS());
child = getS();
break;
case TokenType.Comment:
node.push(getComment());
scanner.next();
child = null;
break;
case TokenType.NumberSign: // ??
node.push(getVhash());
child = getVhash();
break;

@@ -758,3 +860,3 @@

case TokenType.LeftSquareBracket:
node.push(getBraces(scope));
child = getBraces(scope);
break;

@@ -766,10 +868,16 @@

case TokenType.Colon:
node.push(getOperator());
child = getOperator();
break;
default:
node.push(getAny(scope));
child = getAny(scope);
}
if (child !== null) {
node.sequence.insert(List.createItem(child));
}
}
removeTrailingSpaces(node.sequence);
// right brace

@@ -783,25 +891,24 @@ eat(close);

function getClass() {
var startPos = pos;
var info = getInfo();
eat(TokenType.FullStop);
return [
getInfo(startPos),
NodeType.ClassType,
getIdentifier()
];
return {
type: 'Class',
info: info,
name: readIdent(false)
};
}
// '#' ident
// FIXME: shash node should has structure like other ident's (['shash', ['ident', ident]])
function getShash() {
var startPos = pos;
var info = getInfo();
eat(TokenType.NumberSign);
return [
getInfo(startPos),
NodeType.ShashType,
readIdent()
];
return {
type: 'Id',
info: info,
name: readIdent(false)
};
}

@@ -811,11 +918,16 @@

function getCombinator() {
var info = getInfo(pos);
var info = getInfo();
var combinator;
switch (tokens[pos].type) {
switch (scanner.token.type) {
case TokenType.Space:
combinator = ' ';
scanner.next();
break;
case TokenType.PlusSign:
case TokenType.GreaterThanSign:
case TokenType.Tilde:
combinator = tokens[pos].value;
pos++;
combinator = scanner.token.value;
scanner.next();
break;

@@ -825,3 +937,3 @@

combinator = '/deep/';
pos++;
scanner.next();

@@ -837,3 +949,7 @@ expectIdentifier('deep', true);

return [info, NodeType.CombinatorType, combinator];
return {
type: 'Combinator',
info: info,
name: combinator
};
}

@@ -843,3 +959,4 @@

function getComment() {
var value = tokens[pos].value;
var info = getInfo();
var value = scanner.token.value;
var len = value.length;

@@ -851,3 +968,9 @@

return [getInfo(pos++), NodeType.CommentType, value.substring(2, len)];
scanner.next();
return {
type: 'Comment',
info: info,
value: value.substring(2, len)
};
}

@@ -857,4 +980,4 @@

function readUnit() {
if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) {
var unit = tokens[pos].value;
if (scanner.token !== null && scanner.token.type === TokenType.Identifier) {
var unit = scanner.token.value;
var backSlashPos = unit.indexOf('\\');

@@ -864,3 +987,3 @@

if (backSlashPos === -1) {
pos++;
scanner.next();
return unit;

@@ -870,5 +993,5 @@ }

// patch token
tokens[pos].value = unit.substr(backSlashPos);
tokens[pos].offset += backSlashPos;
tokens[pos].column += backSlashPos;
scanner.token.value = unit.substr(backSlashPos);
scanner.token.offset += backSlashPos;
scanner.token.column += backSlashPos;

@@ -883,46 +1006,40 @@ // return unit w/o backslash part

// number ident
function getDimension(startPos, number) {
return [
getInfo(startPos || pos),
NodeType.DimensionType,
number || getNumber(),
[getInfo(pos), NodeType.IdentType, readUnit()]
];
function getDimension(number) {
return {
type: 'Dimension',
info: getInfo(),
value: number || readNumber(),
unit: readUnit()
};
}
// expression '(' raw ')'
function getOldIEExpression(startPos, ident) {
var raw = '';
var balance = 0;
var startPos = pos;
var ident = getIdentifier();
// number "%"
function tryGetPercentage() {
var number = tryGetNumber();
if (ident[2].toLowerCase() !== 'expression') {
pos--;
parseError('`expression` is expected');
if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) {
return getPercentage(number);
}
eat(TokenType.LeftParenthesis);
return null;
}
while (pos < tokens.length) {
if (tokens[pos].type === TokenType.RightParenthesis) {
if (balance === 0) {
break;
}
function getPercentage(number) {
var info;
balance--;
} else if (tokens[pos].type === TokenType.LeftParenthesis) {
balance++;
}
raw += tokens[pos++].value;
if (!number) {
info = getInfo();
number = readNumber();
} else {
info = number.info;
number = number.value;
}
eat(TokenType.RightParenthesis);
eat(TokenType.PercentSign);
return [
getInfo(startPos),
NodeType.FunctionExpressionType,
raw
];
return {
type: 'Percentage',
info: info,
value: number
};
}

@@ -932,48 +1049,46 @@

// not '(' <simpleSelector>* ')'
function getFunction(scope) {
var body = getFunctionBody;
function getFunction(scope, ident) {
var defaultArguments = getFunctionArguments;
if (!ident) {
ident = getIdentifier(false);
}
// parse special functions
if (pos + 1 < tokens.length && tokens[pos].type === TokenType.Identifier) {
var name = tokens[pos].value.toLowerCase();
var name = ident.name.toLowerCase();
if (tokens[pos + 1].type === TokenType.LeftParenthesis) {
if (specialFunctions.hasOwnProperty(scope)) {
if (specialFunctions[scope].hasOwnProperty(name)) {
return specialFunctions[scope][name](scope);
}
}
if (specialFunctions.hasOwnProperty(scope)) {
if (specialFunctions[scope].hasOwnProperty(name)) {
return specialFunctions[scope][name](scope, ident);
}
}
return getFunctionInternal(body, scope);
return getFunctionInternal(defaultArguments, scope, ident);
}
function getNotFunction(scope) {
return getFunctionInternal(getNotFunctionBody, scope);
}
function getFunctionInternal(functionArgumentsReader, scope, ident) {
var args;
function getVarFunction(scope) {
return getFunctionInternal(getVarFunctionBody, scope);
}
function getFunctionInternal(functionBodyReader, scope) {
var startPos = pos;
var ident = getIdentifier();
eat(TokenType.LeftParenthesis);
var body = functionBodyReader(scope);
args = functionArgumentsReader(scope);
eat(TokenType.RightParenthesis);
return [getInfo(startPos), NodeType.FunctionType, ident, body];
return {
type: scope === SCOPE_SELECTOR ? 'FunctionalPseudo' : 'Function',
info: ident.info,
name: ident.name,
arguments: args
};
}
function getFunctionBody(scope) {
var node = [getInfo(pos), NodeType.FunctionBodyType];
function getFunctionArguments(scope) {
var args = new List();
var argument = null;
var child = null;
readSC();
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.RightParenthesis:

@@ -983,11 +1098,12 @@ break scan;

case TokenType.Space:
node.push(getS());
child = getS();
break;
case TokenType.Comment:
node.push(getComment());
case TokenType.Comment: // ignore comments
scanner.next();
child = null;
break;
case TokenType.NumberSign: // TODO: not sure it should be here
node.push(getVhash());
child = getVhash();
break;

@@ -997,28 +1113,55 @@

case TokenType.LeftSquareBracket:
node.push(getBraces(scope));
child = getBraces(scope);
break;
case TokenType.Comma:
removeTrailingSpaces(argument.sequence);
scanner.next();
readSC();
argument = null;
child = null;
break;
case TokenType.Solidus:
case TokenType.Asterisk:
case TokenType.Comma:
case TokenType.Colon:
case TokenType.EqualsSign:
node.push(getOperator());
child = getOperator();
break;
default:
node.push(getAny(scope));
child = getAny(scope);
}
if (argument === null) {
argument = {
type: 'Argument',
sequence: new List()
};
args.insert(List.createItem(argument));
}
if (child !== null) {
argument.sequence.insert(List.createItem(child));
}
}
return node;
if (argument !== null) {
removeTrailingSpaces(argument.sequence);
}
return args;
}
function getNotFunctionBody() {
var node = [getInfo(pos), NodeType.FunctionBodyType];
function getVarFunction(scope, ident) {
return getFunctionInternal(getVarFunctionArguments, scope, ident);
}
function getNotFunctionArguments() {
var args = new List();
var wasSelector = false;
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.RightParenthesis:

@@ -1037,6 +1180,3 @@ if (!wasSelector) {

wasSelector = false;
node.push([
getInfo(pos++),
NodeType.DelimType
]);
scanner.next();
break;

@@ -1046,52 +1186,74 @@

wasSelector = true;
node.push(getSimpleSelector(true));
args.insert(List.createItem(getSimpleSelector(true)));
}
}
return node;
return args;
}
function getNotFunction(scope, ident) {
var args;
eat(TokenType.LeftParenthesis);
args = getNotFunctionArguments(scope);
eat(TokenType.RightParenthesis);
return {
type: 'Negation',
info: ident.info,
// name: ident.name, // TODO: add name?
sequence: args // FIXME: -> arguments?
};
}
// var '(' ident (',' <declaration-value>)? ')'
function getVarFunctionBody() {
var node = [getInfo(pos), NodeType.FunctionBodyType];
function getVarFunctionArguments() { // TODO: special type Variable?
var args = new List();
readSC(node);
node.push(getIdentifier(true));
readSC(node);
readSC();
if (pos < tokens.length && tokens[pos].type === TokenType.Comma) {
node.push(
getOperator(),
getValue(true)
);
readSC(node);
args.insert(List.createItem({
type: 'Argument',
sequence: new List([getIdentifier(true)])
}));
readSC();
if (scanner.token !== null && scanner.token.type === TokenType.Comma) {
eat(TokenType.Comma);
readSC();
args.insert(List.createItem({
type: 'Argument',
sequence: new List([getValue(true)])
}));
readSC();
}
return node;
return args;
}
// url '(' ws* (string | raw) ws* ')'
function getUri() {
var startPos = pos;
var node = [getInfo(startPos), NodeType.UriType];
var ident = getIdentifier();
function getUri(scope, ident) {
var node = {
type: 'Url',
info: ident.info,
// name: ident.name,
value: null
};
if (ident[2].toLowerCase() !== 'url') {
pos--;
parseError('`url` is expected');
}
eat(TokenType.LeftParenthesis); // (
readSC(node);
readSC();
if (tokens[pos].type === TokenType.String) {
node.push(getString());
readSC(node);
if (scanner.token.type === TokenType.String) {
node.value = getString();
readSC();
} else {
var rawStart = pos;
var rawInfo = getInfo();
var raw = '';
while (pos < tokens.length) {
var type = tokens[pos].type;
for (; scanner.token !== null; scanner.next()) {
var type = scanner.token.type;

@@ -1104,12 +1266,12 @@ if (type === TokenType.Space ||

raw += tokens[pos++].value;
raw += scanner.token.value;
}
node.push([
getInfo(rawStart),
NodeType.RawType,
raw
]);
node.value = {
type: 'Raw',
info: rawInfo,
value: raw
};
readSC(node);
readSC();
}

@@ -1122,12 +1284,49 @@

function getUnicodeRange(tryNext) {
// expression '(' raw ')'
function getOldIEExpression(scope, ident) {
var balance = 0;
var raw = '';
eat(TokenType.LeftParenthesis);
for (; scanner.token !== null; scanner.next()) {
if (scanner.token.type === TokenType.RightParenthesis) {
if (balance === 0) {
break;
}
balance--;
} else if (scanner.token.type === TokenType.LeftParenthesis) {
balance++;
}
raw += scanner.token.value;
}
eat(TokenType.RightParenthesis);
return {
type: 'Function',
info: ident.info,
name: ident.name,
arguments: new List([{
type: 'Argument',
sequence: new List([{
type: 'Raw',
value: raw
}])
}])
};
}
function readUnicodeRange(tryNext) {
var hex = '';
for (; pos < tokens.length; pos++) {
if (tokens[pos].type !== TokenType.DecimalNumber &&
tokens[pos].type !== TokenType.Identifier) {
for (; scanner.token !== null; scanner.next()) {
if (scanner.token.type !== TokenType.DecimalNumber &&
scanner.token.type !== TokenType.Identifier) {
break;
}
hex += tokens[pos].value;
hex += scanner.token.value;
}

@@ -1141,8 +1340,8 @@

if (tryNext) {
for (; hex.length < 6 && pos < tokens.length; pos++) {
if (tokens[pos].type !== TokenType.QuestionMark) {
for (; hex.length < 6 && scanner.token !== null; scanner.next()) {
if (scanner.token.type !== TokenType.QuestionMark) {
break;
}
hex += tokens[pos].value;
hex += scanner.token.value;
tryNext = false;

@@ -1154,6 +1353,7 @@ }

if (tryNext) {
if (pos < tokens.length && tokens[pos].type === TokenType.HyphenMinus) {
pos++;
var next = getUnicodeRange(false);
if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
scanner.next();
var next = readUnicodeRange(false);
if (!next) {

@@ -1174,9 +1374,9 @@ parseError('Unexpected input');

// optional first -
if (pos < tokens.length && tokens[pos].type === TokenType.HyphenMinus) {
if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
name = '-';
pos++;
scanner.next();
if (varAllowed && pos < tokens.length && tokens[pos].type === TokenType.HyphenMinus) {
if (varAllowed && scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
name = '--';
pos++;
scanner.next();
}

@@ -1190,8 +1390,9 @@ }

if (pos < tokens.length) {
name += tokens[pos].value;
pos++;
if (scanner.token !== null) {
name += scanner.token.value;
scanner.next();
for (; pos < tokens.length; pos++) {
var type = tokens[pos].type;
for (; scanner.token !== null; scanner.next()) {
var type = scanner.token.type;
if (type !== TokenType.LowLine &&

@@ -1204,3 +1405,3 @@ type !== TokenType.Identifier &&

name += tokens[pos].value;
name += scanner.token.value;
}

@@ -1213,33 +1414,31 @@ }

function getNamespacedIdentifier(checkColon) {
if (pos >= tokens.length) {
if (scanner.token === null) {
parseError('Unexpected end of input');
}
var info = getInfo(pos);
var info = getInfo();
var name;
if (tokens[pos].type === TokenType.Asterisk) {
if (scanner.token.type === TokenType.Asterisk) {
checkColon = false;
name = '*';
pos++;
scanner.next();
} else {
name = readIdent();
name = readIdent(false);
}
if (pos < tokens.length) {
if (tokens[pos].type === TokenType.VerticalLine &&
pos + 1 < tokens.length &&
tokens[pos + 1].type !== TokenType.EqualsSign) {
if (scanner.token !== null) {
if (scanner.token.type === TokenType.VerticalLine &&
scanner.lookupType(1, TokenType.EqualsSign) === false) {
name += '|';
pos++;
if (pos < tokens.length) {
if (tokens[pos].type === TokenType.HyphenMinus ||
tokens[pos].type === TokenType.Identifier ||
tokens[pos].type === TokenType.LowLine) {
name += readIdent();
} else if (tokens[pos].type === TokenType.Asterisk) {
if (scanner.next() !== null) {
if (scanner.token.type === TokenType.HyphenMinus ||
scanner.token.type === TokenType.Identifier ||
scanner.token.type === TokenType.LowLine) {
name += readIdent(false);
} else if (scanner.token.type === TokenType.Asterisk) {
checkColon = false;
name += '*';
pos++;
scanner.next();
}

@@ -1250,27 +1449,43 @@ }

if (checkColon && pos < tokens.length && tokens[pos].type === TokenType.Colon) {
pos++;
name += ':' + readIdent();
if (checkColon && scanner.token !== null && scanner.token.type === TokenType.Colon) {
scanner.next();
name += ':' + readIdent(false);
}
return [
info,
NodeType.IdentType,
name
];
return {
type: 'Identifier',
info: info,
name: name
};
}
function getIdentifier(varAllowed) {
return [getInfo(pos), NodeType.IdentType, readIdent(varAllowed)];
return {
type: 'Identifier',
info: getInfo(),
name: readIdent(varAllowed)
};
}
// ! ws* important
function getImportant() {
function getImportant() { // TODO?
// var info = getInfo();
eat(TokenType.ExclamationMark);
var node = readSC([getInfo(pos - 1), NodeType.ImportantType]);
readSC();
expectIdentifier('important', true);
// return {
// type: 'Identifier',
// info: info,
// name: readIdent(false)
// };
return node;
expectIdentifier('important');
readIdent(false);
// should return identifier in future for original source restoring as is
// returns true for now since it's fit to optimizer purposes
return true;
}

@@ -1285,12 +1500,13 @@

var startPos = pos;
var value = tokens[pos].value;
var info = getInfo();
var value = scanner.token.value;
var cmpValue;
if (tokens[pos].type === TokenType.DecimalNumber) {
if (pos + 1 < tokens.length &&
tokens[pos + 1].type === TokenType.Identifier &&
tokens[pos + 1].value.toLowerCase() === 'n') {
value += tokens[pos + 1].value;
pos++;
if (scanner.token.type === TokenType.DecimalNumber) {
var next = scanner.lookup(1);
if (next !== null &&
next.type === TokenType.Identifier &&
next.value.toLowerCase() === 'n') {
value += next.value;
scanner.next();
}

@@ -1304,16 +1520,29 @@ } else {

pos++;
scanner.next();
return [
getInfo(startPos),
NodeType.NthType,
value
];
return {
type: 'Nth',
info: info,
value: value
};
}
function getNthSelector() {
var info = getInfo();
var sequence = new List();
var node;
var child = null;
eat(TokenType.Colon);
expectIdentifier('nth', false);
var node = [getInfo(pos - 1), NodeType.NthselectorType, getIdentifier()];
node = {
type: 'FunctionalPseudo',
info: info,
name: readIdent(false),
arguments: new List([{
type: 'Argument',
sequence: sequence
}])
};

@@ -1323,4 +1552,4 @@ eat(TokenType.LeftParenthesis);

scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.RightParenthesis:

@@ -1330,7 +1559,5 @@ break scan;

case TokenType.Space:
node.push(getS());
break;
case TokenType.Comment:
node.push(getComment());
scanner.next();
child = null;
break;

@@ -1340,8 +1567,12 @@

case TokenType.PlusSign:
node.push(getUnary());
child = getOperator();
break;
default:
node.push(getNth());
child = getNth();
}
if (child !== null) {
sequence.insert(List.createItem(child));
}
}

@@ -1354,33 +1585,35 @@

function tryGetNumber() {
var startPos = pos;
function readNumber() {
var wasDigits = false;
var number = '';
var i = pos;
var offset = 0;
if (i < tokens.length && tokens[i].type === TokenType.HyphenMinus) {
if (scanner.lookupType(offset, TokenType.HyphenMinus)) {
number = '-';
i++;
offset++;
}
if (i < tokens.length && tokens[i].type === TokenType.DecimalNumber) {
if (scanner.lookupType(offset, TokenType.DecimalNumber)) {
wasDigits = true;
number += tokens[i].value;
i++;
number += scanner.lookup(offset).value;
offset++;
}
if (i < tokens.length && tokens[i].type === TokenType.FullStop) {
if (scanner.lookupType(offset, TokenType.FullStop)) {
number += '.';
i++;
offset++;
}
if (i < tokens.length && tokens[i].type === TokenType.DecimalNumber) {
if (scanner.lookupType(offset, TokenType.DecimalNumber)) {
wasDigits = true;
number += tokens[i].value;
i++;
number += scanner.lookup(offset).value;
offset++;
}
if (wasDigits) {
pos = i;
return [getInfo(startPos), NodeType.NumberType, number];
while (offset--) {
scanner.next();
}
return number;
}

@@ -1391,68 +1624,49 @@

function getNumber() {
var number = tryGetNumber();
function tryGetNumber() {
var info = getInfo();
var number = readNumber();
if (!number) {
parseError('Wrong number');
if (number !== null) {
return {
type: 'Number',
info: info,
value: number
};
}
return number;
return null;
}
// '/' | '*' | ',' | ':' | '='
// '/' | '*' | ',' | ':' | '=' | '+' | '-'
// TODO: remove '=' since it's wrong operator, but theat as operator
// to make old things like `filter: alpha(opacity=0)` works
function getOperator() {
expectAny('Operator',
TokenType.Solidus,
TokenType.Asterisk,
TokenType.Comma,
TokenType.Colon,
TokenType.EqualsSign
);
var node = {
type: 'Operator',
info: getInfo(),
value: scanner.token.value
};
return [getInfo(pos), NodeType.OperatorType, tokens[pos++].value];
}
scanner.next();
// node: Percentage
function tryGetPercentage() {
var startPos = pos;
var number = tryGetNumber();
if (!number) {
return null;
}
if (pos >= tokens.length || tokens[pos].type !== TokenType.PercentSign) {
return null;
}
return getPercentage(startPos, number);
return node;
}
function getPercentage(startPos, number) {
if (!startPos) {
startPos = pos;
}
function getFilterValue() { // TODO
var progid;
var node = {
type: 'Value',
info: getInfo(),
important: false,
sequence: new List()
};
if (!number) {
number = getNumber();
while (progid = checkProgid()) {
node.sequence.insert(List.createItem(getProgid(progid)));
}
eat(TokenType.PercentSign);
return [getInfo(startPos), NodeType.PercentageType, number];
}
function getFilterv() {
var node = [getInfo(pos), NodeType.FiltervType];
while (checkProgid(pos)) {
node.push(getProgid());
}
readSC(node);
if (pos < tokens.length && tokens[pos].type === TokenType.ExclamationMark) {
node.push(getImportant());
if (scanner.token !== null && scanner.token.type === TokenType.ExclamationMark) {
node.important = getImportant();
}

@@ -1464,65 +1678,63 @@

// 'progid:' ws* 'DXImageTransform.Microsoft.' ident ws* '(' .* ')'
function checkSC(i) {
var start = i;
function checkProgid() {
function checkSC(offset) {
for (var cursor; cursor = scanner.lookup(offset); offset++) {
if (cursor.type !== TokenType.Space &&
cursor.type !== TokenType.Comment) {
break;
}
}
while (i < tokens.length) {
if (tokens[i].type === TokenType.Space ||
tokens[i].type === TokenType.Comment) {
i++;
} else {
break;
}
return offset;
}
return i - start;
}
var offset = checkSC(0);
function checkProgid(i) {
var start = i;
i += checkSC(i);
if (i + 1 >= tokens.length ||
tokens[i + 0].value.toLowerCase() !== 'progid' ||
tokens[i + 1].type !== TokenType.Colon) {
if (scanner.lookup(offset + 1) === null ||
scanner.lookup(offset + 0).value.toLowerCase() !== 'progid' ||
scanner.lookup(offset + 1).type !== TokenType.Colon) {
return false; // fail
}
i += 2;
i += checkSC(i);
offset += 2;
offset = checkSC(offset);
if (i + 6 >= tokens.length ||
tokens[i + 0].value.toLowerCase() !== 'dximagetransform' ||
tokens[i + 1].type !== TokenType.FullStop ||
tokens[i + 2].value.toLowerCase() !== 'microsoft' ||
tokens[i + 3].type !== TokenType.FullStop ||
tokens[i + 4].type !== TokenType.Identifier) {
if (scanner.lookup(offset + 5) === null ||
scanner.lookup(offset + 0).value.toLowerCase() !== 'dximagetransform' ||
scanner.lookup(offset + 1).type !== TokenType.FullStop ||
scanner.lookup(offset + 2).value.toLowerCase() !== 'microsoft' ||
scanner.lookup(offset + 3).type !== TokenType.FullStop ||
scanner.lookup(offset + 4).type !== TokenType.Identifier) {
return false; // fail
}
i += 5;
i += checkSC(i);
offset += 5;
offset = checkSC(offset);
if (i >= tokens.length ||
tokens[i].type !== TokenType.LeftParenthesis) {
if (scanner.lookupType(offset, TokenType.LeftParenthesis) === false) {
return false; // fail
}
while (i < tokens.length) {
if (tokens[i++].type === TokenType.RightParenthesis) {
break;
for (var cursor; cursor = scanner.lookup(offset); offset++) {
if (cursor.type === TokenType.RightParenthesis) {
return cursor;
}
}
tokens[start].progidEnd = i;
return true;
return false;
}
function getProgid() {
var node = [getInfo(pos), NodeType.ProgidType];
var progidEnd = tokens[pos].progidEnd;
function getProgid(progidEnd) {
var value = '';
var node = {
type: 'Progid',
info: getInfo(),
value: null
};
if (!progidEnd && !checkProgid(pos)) {
if (!progidEnd) {
progidEnd = checkProgid();
}
if (!progidEnd) {
parseError('progid is expected');

@@ -1533,13 +1745,16 @@ }

var rawStart = pos;
for (; pos < progidEnd; pos++) {
value += tokens[pos].value;
var rawInfo = getInfo();
for (; scanner.token && scanner.token !== progidEnd; scanner.next()) {
value += scanner.token.value;
}
node.push([
getInfo(rawStart),
NodeType.RawType,
value
]);
eat(TokenType.RightParenthesis);
value += ')';
node.value = {
type: 'Raw',
info: rawInfo,
value: value
};
readSC(node);

@@ -1552,13 +1767,9 @@

function getPseudo() {
if (pos >= tokens.length || tokens[pos].type !== TokenType.Colon) {
parseError('Colon is expected');
}
var next = scanner.lookup(1);
if (pos + 1 >= tokens.length) {
pos++;
if (next === null) {
scanner.next();
parseError('Colon or identifier is expected');
}
var next = tokens[pos + 1];
if (next.type === TokenType.Colon) {

@@ -1578,6 +1789,12 @@ return getPseudoElement();

function getPseudoElement() {
var info = getInfo();
eat(TokenType.Colon);
eat(TokenType.Colon);
return [getInfo(pos - 2), NodeType.PseudoeType, getIdentifier()];
return {
type: 'PseudoElement',
info: info,
name: readIdent(false)
};
}

@@ -1587,15 +1804,14 @@

function getPseudoClass() {
var startPos = pos;
var node = eat(TokenType.Colon) && getIdentifier();
var info = getInfo();
var ident = eat(TokenType.Colon) && getIdentifier(false);
if (pos < tokens.length && tokens[pos].type === TokenType.LeftParenthesis) {
pos = startPos + 1;
node = getFunction(SCOPE_SELECTOR);
if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) {
return getFunction(SCOPE_SELECTOR, ident);
}
return [
getInfo(startPos),
NodeType.PseudocType,
node
];
return {
type: 'PseudoClass',
info: info,
name: ident.name
};
}

@@ -1605,15 +1821,26 @@

function getS() {
return [getInfo(pos), NodeType.SType, tokens[pos++].value];
var node = {
type: 'Space'
// value: scanner.token.value
};
scanner.next();
return node;
}
function readSC(node) {
function readSC() {
// var nodes = [];
scan:
while (pos < tokens.length) {
switch (tokens[pos].type) {
while (scanner.token !== null) {
switch (scanner.token.type) {
case TokenType.Space:
node.push(getS());
scanner.next();
// nodes.push(getS());
break;
case TokenType.Comment:
node.push(getComment());
scanner.next();
// nodes.push(getComment());
break;

@@ -1626,3 +1853,5 @@

return node;
return null;
// return nodes.length ? new List(nodes) : null;
}

@@ -1632,25 +1861,18 @@

function getString() {
return [getInfo(pos), NodeType.StringType, tokens[pos++].value];
}
var node = {
type: 'String',
info: getInfo(),
value: scanner.token.value
};
// '+' | '-'
function getUnary() {
expectAny('Unary operator',
TokenType.HyphenMinus,
TokenType.PlusSign
);
scanner.next();
return [getInfo(pos), NodeType.UnaryType, tokens[pos++].value];
return node;
}
// '//' ...
// TODO: remove it as wrong thing
function getUnknown() {
eat(TokenType.Unknown);
return [getInfo(pos - 1), NodeType.UnknownType, tokens[pos - 1].value];
}
// # ident
function getVhash() {
var info = getInfo();
var value;
eat(TokenType.NumberSign);

@@ -1663,61 +1885,43 @@

var name = tokens[pos].value;
value = scanner.token.value;
if (tokens[pos++].type === TokenType.DecimalNumber) {
if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) {
name += tokens[pos++].value;
}
if (scanner.token.type === TokenType.DecimalNumber &&
scanner.lookupType(1, TokenType.Identifier)) {
scanner.next();
value += scanner.token.value;
}
return [getInfo(pos - 1), NodeType.VhashType, name];
scanner.next();
return {
type: 'Hash',
info: info,
value: value
};
}
module.exports = function parse(source, context, options) {
module.exports = function parse(source, options) {
var ast;
options = options || {};
if (options === true) {
options = {
positions: true,
needInfo: true
};
if (!options || typeof options !== 'object') {
options = {};
}
if ('positions' in options) {
needPositions = options.positions || false;
} else {
// deprecated option but using for backward capability
needPositions = options.needPositions || false;
}
var context = options.context || 'stylesheet';
needPositions = Boolean(options.positions);
filename = options.filename || '<unknown>';
context = context || 'stylesheet';
pos = 0;
tokens = tokenize(source, blockMode.hasOwnProperty(context), options.line, options.column);
if (tokens.length) {
ast = rules[context]();
if (!initialContext.hasOwnProperty(context)) {
throw new Error('Unknown context `' + context + '`');
}
tokens = null; // drop tokens
scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column);
scanner.next();
ast = initialContext[context]();
if (!ast) {
switch (context) {
case 'stylesheet':
ast = [{}, context];
break;
// case 'declarations':
// ast = [{}, 'block'];
// break;
}
}
scanner = null;
if (ast && !options.needInfo) {
ast = cleanInfo(ast);
}
// console.log(require('../utils/stringify.js')(require('../utils/cleanInfo.js')(ast), true));
// console.log(JSON.stringify(ast, null, 4));
// console.log(require('../utils/stringify.js')(ast, true));
return ast;
};

@@ -1,197 +0,168 @@

var useInfo;
var buffer;
var typeHandlers = {
unary: simple,
nth: simple,
combinator: simple,
ident: simple,
number: simple,
s: simple,
string: simple,
attrselector: simple,
operator: simple,
raw: simple,
unknown: simple,
attribFlags: simple,
function each(list) {
if (list.head === null) {
return '';
}
simpleselector: composite,
dimension: composite,
selector: composite,
property: composite,
value: composite,
filterv: composite,
progid: composite,
ruleset: composite,
atruleb: composite,
atrulerq: composite,
atrulers: composite,
stylesheet: composite,
percentage: percentage,
comment: comment,
clazz: clazz,
atkeyword: atkeyword,
shash: shash,
vhash: vhash,
attrib: attrib,
important: important,
nthselector: nthselector,
funktion: funktion,
declaration: declaration,
filter: filter,
block: block,
braces: braces,
atrules: atrules,
atruler: atruler,
pseudoe: pseudoe,
pseudoc: pseudoc,
uri: uri,
functionExpression: functionExpression,
decldelim: function() {
buffer.push(';');
},
delim: function() {
buffer.push(',');
if (list.head === list.tail) {
return translate(list.head.data);
}
};
function simple(token) {
buffer.push(token[useInfo + 1]);
return list.map(translate).join('');
}
function composite(token) {
for (var i = useInfo + 1; i < token.length; i++) {
translate(token[i]);
function eachDelim(list, delimeter) {
if (list.head === null) {
return '';
}
}
function compositeFrom(token, i) {
for (; i < token.length; i++) {
translate(token[i]);
if (list.head === list.tail) {
return translate(list.head.data);
}
}
function percentage(token) {
translate(token[useInfo + 1]);
buffer.push('%');
return list.map(translate).join(delimeter);
}
function comment(token) {
buffer.push('/*', token[useInfo + 1], '*/');
}
function translate(node) {
switch (node.type) {
case 'StyleSheet':
return each(node.rules);
function clazz(token) {
buffer.push('.');
translate(token[useInfo + 1]);
}
case 'Atrule':
var result = '@' + node.name;
function atkeyword(token) {
buffer.push('@');
translate(token[useInfo + 1]);
}
if (node.expression && !node.expression.sequence.isEmpty()) {
result += ' ' + translate(node.expression);
}
function shash(token) {
buffer.push('#', token[useInfo + 1]);
}
if (node.block) {
return result + '{' + translate(node.block) + '}';
} else {
return result + ';';
}
function vhash(token) {
buffer.push('#', token[useInfo + 1]);
}
case 'Ruleset':
return translate(node.selector) + '{' + translate(node.block) + '}';
function attrib(token) {
buffer.push('[');
composite(token);
buffer.push(']');
}
case 'Selector':
return eachDelim(node.selectors, ',');
function important(token) {
buffer.push('!');
composite(token);
buffer.push('important');
}
case 'SimpleSelector':
return node.sequence.map(function(node) {
// add extra spaces around /deep/ combinator since comment beginning/ending may to be produced
if (node.type === 'Combinator' && node.name === '/deep/') {
return ' ' + translate(node) + ' ';
}
function nthselector(token) {
buffer.push(':');
simple(token[useInfo + 1]);
buffer.push('(');
compositeFrom(token, useInfo + 2);
buffer.push(')');
}
return translate(node);
}).join('');
function funktion(token) {
simple(token[useInfo + 1]);
buffer.push('(');
composite(token[useInfo + 2]);
buffer.push(')');
}
case 'Declaration':
return translate(node.property) + ':' + translate(node.value);
function declaration(token) {
translate(token[useInfo + 1]);
buffer.push(':');
translate(token[useInfo + 2]);
}
case 'Property':
return node.name;
function filter(token) {
translate(token[useInfo + 1]);
buffer.push(':');
translate(token[useInfo + 2]);
}
case 'Value':
return node.important
? each(node.sequence) + '!important'
: each(node.sequence);
function block(token) {
buffer.push('{');
composite(token);
buffer.push('}');
}
case 'Attribute':
var result = translate(node.name);
function braces(token) {
buffer.push(token[useInfo + 1]);
compositeFrom(token, useInfo + 3);
buffer.push(token[useInfo + 2]);
}
if (node.operator !== null) {
result += node.operator;
function atrules(token) {
composite(token);
buffer.push(';');
}
if (node.value !== null) {
result += translate(node.value);
function atruler(token) {
translate(token[useInfo + 1]);
translate(token[useInfo + 2]);
buffer.push('{');
translate(token[useInfo + 3]);
buffer.push('}');
}
if (node.flags !== null) {
result += (node.value.type !== 'String' ? ' ' : '') + node.flags;
}
}
}
function pseudoe(token) {
buffer.push('::');
translate(token[useInfo + 1]);
}
return '[' + result + ']';
function pseudoc(token) {
buffer.push(':');
translate(token[useInfo + 1]);
}
case 'FunctionalPseudo':
return ':' + node.name + '(' + eachDelim(node.arguments, ',') + ')';
function uri(token) {
buffer.push('url(');
composite(token);
buffer.push(')');
}
case 'Function':
return node.name + '(' + eachDelim(node.arguments, ',') + ')';
function functionExpression(token) {
buffer.push('expression(', token[useInfo + 1], ')');
}
case 'Block':
return eachDelim(node.declarations, ';');
function translate(token) {
typeHandlers[token[useInfo]](token);
}
case 'Negation':
return ':not(' + eachDelim(node.sequence, ',') + ')';
module.exports = function(tree, hasInfo) {
useInfo = hasInfo ? 1 : 0;
buffer = [];
case 'Braces':
return node.open + each(node.sequence) + node.close;
translate(tree);
case 'Argument':
case 'AtruleExpression':
return each(node.sequence);
return buffer.join('');
};
case 'Url':
return 'url(' + translate(node.value) + ')';
case 'Progid':
return translate(node.value);
case 'Combinator':
return node.name;
case 'Identifier':
return node.name;
case 'PseudoClass':
return ':' + node.name;
case 'PseudoElement':
return '::' + node.name;
case 'Class':
return '.' + node.name;
case 'Id':
return '#' + node.name;
case 'Hash':
return '#' + node.value;
case 'Dimension':
return node.value + node.unit;
case 'Nth':
return node.value;
case 'Number':
return node.value;
case 'String':
return node.value;
case 'Operator':
return node.value;
case 'Raw':
return node.value;
case 'Unknown':
return node.value;
case 'Percentage':
return node.value + '%';
case 'Space':
return ' ';
case 'Comment':
return '/*' + node.value + '*/';
default:
throw new Error('Unknown node type: ' + node.type);
}
}
module.exports = translate;
{
"name": "csso",
"version": "1.8.1",
"version": "2.0.0",
"description": "CSSO (CSS Optimizer) is a CSS minifier with structural optimisations",

@@ -5,0 +5,0 @@ "keywords": [

@@ -49,7 +49,7 @@ [![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso)

```
> csso in.css out.css
> csso in.css
...output result in stdout...
> csso in.css --output out.css
> echo '.test { color: #ff0000; }' | csso

@@ -59,10 +59,2 @@ .test{color:red}

> cat source1.css source2.css | csso | gzip -9 -c > production.css.gz
> echo '.test { color: #ff0000 }' | csso --stat >/dev/null
File: <stdin>
Original: 25 bytes
Compressed: 16 bytes (64.00%)
Saving: 9 bytes (36.00%)
Time: 12 ms
Memory: 0.346 MB
```

@@ -115,3 +107,3 @@

> `ids` and `classes` comparison is case sensetive, `tags` – is not.
> `ids` and `classes` names are case sensitive, `tags` – is not.

@@ -188,14 +180,6 @@ Input CSS:

var compressedCss = csso.minify('.test { color: #ff0000; }');
var compressedCss = csso.minify('.test { color: #ff0000; }').css;
console.log(compressedCss);
// .test{color:red}
// there are some options you can pass
var compressedWithOptions = csso.minify('.test { color: #ff0000; }', {
restructure: false, // don't change css structure, i.e. don't merge declarations, rulesets etc
debug: true // show additional debug information:
// true or number from 1 to 3 (greater number - more details)
});
```

@@ -207,4 +191,4 @@

var ast = csso.parse('.test { color: #ff0000; }');
var compressedAst = csso.compress(ast);
var compressedCss = csso.translate(compressedAst, true);
var compressResult = csso.compress(ast);
var compressedCss = csso.translate(compressResult.ast);

@@ -231,77 +215,168 @@ console.log(compressedCss);

### Debugging
#### minify(source[, options])
Minify `source` CSS passed as `String`.
Options:
- sourceMap `Boolean` - generate source map if `true`
- filename `String` - filename of input, uses for source map
- debug `Boolean` - output debug information to `stderr`
- other options are the same as for `compress()`
Returns an object with properties:
- css `String` – resulting CSS
- map `Object` – instance of `SourceMapGenerator` or `null`
```js
var result = csso.minify('.test { color: #ff0000; }', {
restructure: false, // don't change CSS structure, i.e. don't merge declarations, rulesets etc
debug: true // show additional debug information:
// true or number from 1 to 3 (greater number - more details)
});
console.log(result.css);
// > .test{color:red}
```
> echo '.test { color: green; color: #ff0000 } .foo { color: red }' | csso --debug
## parsing done in 10 ms
Compress block #1
(0.002ms) convertToInternal
(0.000ms) clean
(0.001ms) compress
(0.002ms) prepare
(0.000ms) initialRejoinRuleset
(0.000ms) rejoinAtrule
(0.000ms) disjoin
(0.000ms) buildMaps
(0.000ms) markShorthands
(0.000ms) processShorthand
(0.001ms) restructBlock
(0.000ms) rejoinRuleset
(0.000ms) restructRuleset
## compressing done in 9 ms
#### minifyBlock(source[, options])
.foo,.test{color:red}
The same as `minify()` but for style block. Usualy it's a `style` attribute content.
```js
var result = csso.minifyBlock('color: rgba(255, 0, 0, 1); color: #ff0000').css;
console.log(result.css);
// > color:red
```
More details are provided when `--debug` flag has a number greater than `1`:
#### parse(source[, options])
Parse CSS to AST.
> NOTE: Currenly parser omit redundant separators, spaces and comments (except exclamation comments, i.e. `/*! comment */`) on AST build, since those things are removing by compressor anyway.
Options:
- context `String` – parsing context, useful when some part of CSS is parsing (see below)
- positions `Boolean` – should AST contains node position or not, store data in `info` property of nodes (`false` by default)
- filename `String` – filename of source that adds to info when `positions` is true, uses for source map generation (`<unknown>` by default)
- line `Number` – initial line number, useful when parse fragment of CSS to compute correct positions
- column `Number` – initial column number, useful when parse fragment of CSS to compute correct positions
Contexts:
- `stylesheet` (default) – regular stylesheet, should be suitable in most cases
- `atrule` – at-rule (e.g. `@media screen, print { ... }`)
- `atruleExpression` – at-rule expression (`screen, print` for example above)
- `ruleset` – rule (e.g. `.foo, .bar:hover { color: red; border: 1px solid black; }`)
- `selector` – selector group (`.foo, .bar:hover` for ruleset example)
- `simpleSelector` – selector (`.foo` or `.bar:hover` for ruleset example)
- `block` – block content w/o curly braces (`color: red; border: 1px solid black;` for ruleset example)
- `declaration` – declaration (`color: red` or `border: 1px solid black` for ruleset example)
- `value` – declaration value (`red` or `1px solid black` for ruleset example)
```js
// simple parsing with no options
var ast = csso.parse('.example { color: red }');
// parse with options
var ast = csso.parse('.foo.bar', {
context: 'simpleSelector',
positions: true
});
```
> echo '.test { color: green; color: #ff0000 } .foo { color: red }' | csso --debug 2
## parsing done in 8 ms
Compress block #1
(0.000ms) clean
.test{color:green;color:#ff0000}.foo{color:red}
#### compress(ast[, options])
(0.001ms) compress
.test{color:green;color:red}.foo{color:red}
Do the main task – compress AST.
...
Options:
(0.002ms) restructBlock
.test{color:red}.foo{color:red}
- restructure `Boolean` – do the structure optimisations or not (`true` by default)
- usage `Object` - usage data for advanced optimisations (see [Usage data](#usage-data) for details)
- logger `Function` - function to track every step of transformations
(0.001ms) rejoinRuleset
.foo,.test{color:red}
#### translate(ast)
## compressing done in 13 ms
Converts AST to string.
.foo,.test{color:red}
```js
var ast = csso.parse('.test { color: red }');
console.log(csso.translate(ast));
// > .test{color:red}
```
Using `--debug` option adds stack trace to CSS parse error output. That can help to find out problem in parser.
#### translateWithSourceMap(ast)
The same as `translate()` but also generates source map (nodes should contain positions in `info` property).
```js
var ast = csso.parse('.test { color: red }', {
filename: 'my.css',
positions: true
});
console.log(csso.translateWithSourceMap(ast));
// { css: '.test{color:red}', map: SourceMapGenerator {} }
```
> echo '.a { color }' | csso --debug
Parse error <stdin>: Colon is expected
1 |.a { color }
------------------^
2 |
#### walk(ast, handler)
/usr/local/lib/node_modules/csso/lib/cli.js:243
throw e;
^
Visit all nodes of AST and call handler for each one. `handler` receives three arguments:
Error: Colon is expected
at parseError (/usr/local/lib/node_modules/csso/lib/parser/index.js:54:17)
at eat (/usr/local/lib/node_modules/csso/lib/parser/index.js:88:5)
at getDeclaration (/usr/local/lib/node_modules/csso/lib/parser/index.js:394:5)
at getBlock (/usr/local/lib/node_modules/csso/lib/parser/index.js:380:27)
...
- node – current AST node
- item – node wrapper when node is a list member; this wrapper contains references to `prev` and `next` nodes in list
- list – reference to list when node is a list member; it's useful for operations on list like `remove()` or `insert()`
Context for handler an object, that contains references to some parent nodes:
- root – refers to `ast` or root node
- stylesheet – refers to closest `StyleSheet` node, it may be a top-level or at-rule block stylesheet
- atruleExpression – refers to `AtruleExpression` node if current node inside at-rule expression
- ruleset – refers to `Ruleset` node if current node inside a ruleset
- selector – refers to `Selector` node if current node inside a selector
- declaration – refers to `Declaration` node if current node inside a declaration
- function – refers to closest `Function` or `FunctionalPseudo` node if current node inside one of them
```js
// collect all urls in declarations
var csso = require('./lib/index.js');
var urls = [];
var ast = csso.parse(`
@import url(import.css);
.foo { background: url('foo.jpg'); }
.bar { background-image: url(bar.png); }
`);
csso.walk(ast, function(node) {
if (this.declaration !== null && node.type === 'Url') {
var value = node.value;
if (value.type === 'Raw') {
urls.push(value.value);
} else {
urls.push(value.value.substr(1, value.value.length - 2));
}
}
});
console.log(urls);
// [ 'foo.jpg', 'bar.png' ]
```
#### walkRules(ast, handler)
Same as `walk()` but visits `Ruleset` and `Atrule` nodes only.
#### walkRulesRight(ast, handler)
Same as `walkRules()` but visits nodes in reverse order (from last to first).
## More reading
- [Debugging](docs/debugging.md)
## License
MIT

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc