Comparing version 1.8.1 to 2.0.0
@@ -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 @@ |
@@ -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 @@ |
@@ -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": [ |
217
README.md
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
377
294689
51
5521