From e6bea48e0fd15af2b284c60eff6c8e1db3bc8738 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Sun, 27 Mar 2016 20:26:25 +0600 Subject: [PATCH 01/19] initial scanner implementation --- lib/parser/index.js | 5 +- lib/parser/scanner.js | 348 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 lib/parser/scanner.js diff --git a/lib/parser/index.js b/lib/parser/index.js index 721c018c..61df3315 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -2,7 +2,7 @@ var TokenType = require('./const.js').TokenType; var NodeType = require('./const.js').NodeType; -var tokenize = require('./tokenize.js'); +var Scanner = require('./scanner.js'); var cleanInfo = require('../utils/cleanInfo.js'); var needPositions; var filename; @@ -1624,7 +1624,8 @@ module.exports = function parse(source, context, options) { context = context || 'stylesheet'; pos = 0; - tokens = tokenize(source, blockMode.hasOwnProperty(context), options.line, options.column); + var scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column); + tokens = scanner.tokenize(); if (tokens.length) { ast = rules[context](); diff --git a/lib/parser/scanner.js b/lib/parser/scanner.js new file mode 100644 index 00000000..aed639e2 --- /dev/null +++ b/lib/parser/scanner.js @@ -0,0 +1,348 @@ +'use strict'; + +var TokenType = require('./const.js').TokenType; + +var TAB = 9; +var N = 10; +var F = 12; +var R = 13; +var SPACE = 32; +var DOUBLE_QUOTE = 34; +var QUOTE = 39; +var RIGHT_PARENTHESIS = 41; +var STAR = 42; +var SLASH = 47; +var BACK_SLASH = 92; +var UNDERSCORE = 95; +var LEFT_CURLY_BRACE = 123; +var RIGHT_CURLY_BRACE = 125; + +var WHITESPACE = 1; +var PUNCTUATOR = 2; +var DIGIT = 3; +var STRING_SQ = 4; +var STRING_DQ = 5; + +var PUNCTUATION = { + 9: TokenType.Tab, // '\t' + 10: TokenType.Newline, // '\n' + 13: TokenType.Newline, // '\r' + 32: TokenType.Space, // ' ' + 33: TokenType.ExclamationMark, // '!' + 34: TokenType.QuotationMark, // '"' + 35: TokenType.NumberSign, // '#' + 36: TokenType.DollarSign, // '$' + 37: TokenType.PercentSign, // '%' + 38: TokenType.Ampersand, // '&' + 39: TokenType.Apostrophe, // '\'' + 40: TokenType.LeftParenthesis, // '(' + 41: TokenType.RightParenthesis, // ')' + 42: TokenType.Asterisk, // '*' + 43: TokenType.PlusSign, // '+' + 44: TokenType.Comma, // ',' + 45: TokenType.HyphenMinus, // '-' + 46: TokenType.FullStop, // '.' + 47: TokenType.Solidus, // '/' + 58: TokenType.Colon, // ':' + 59: TokenType.Semicolon, // ';' + 60: TokenType.LessThanSign, // '<' + 61: TokenType.EqualsSign, // '=' + 62: TokenType.GreaterThanSign, // '>' + 63: TokenType.QuestionMark, // '?' + 64: TokenType.CommercialAt, // '@' + 91: TokenType.LeftSquareBracket, // '[' + 93: TokenType.RightSquareBracket, // ']' + 94: TokenType.CircumflexAccent, // '^' + 95: TokenType.LowLine, // '_' + 123: TokenType.LeftCurlyBracket, // '{' + 124: TokenType.VerticalLine, // '|' + 125: TokenType.RightCurlyBracket, // '}' + 126: TokenType.Tilde // '~' +}; +var SYMBOL_CATEGORY_LENGTH = Math.max.apply(null, Object.keys(PUNCTUATION)) + 1; +var SYMBOL_CATEGORY = new Uint32Array(SYMBOL_CATEGORY_LENGTH); +var IS_PUNCTUATOR = new Uint32Array(SYMBOL_CATEGORY_LENGTH); + +// fill categories +Object.keys(PUNCTUATION).forEach(function(key) { + SYMBOL_CATEGORY[Number(key)] = PUNCTUATOR; + IS_PUNCTUATOR[Number(key)] = PUNCTUATOR; +}, SYMBOL_CATEGORY); + +// don't treat as punctuator +IS_PUNCTUATOR[UNDERSCORE] = 0; + +for (var i = 48; i <= 57; i++) { + SYMBOL_CATEGORY[i] = DIGIT; +} + +SYMBOL_CATEGORY[SPACE] = WHITESPACE; +SYMBOL_CATEGORY[TAB] = WHITESPACE; +SYMBOL_CATEGORY[N] = WHITESPACE; +SYMBOL_CATEGORY[R] = WHITESPACE; +SYMBOL_CATEGORY[F] = WHITESPACE; + +SYMBOL_CATEGORY[QUOTE] = STRING_SQ; +SYMBOL_CATEGORY[DOUBLE_QUOTE] = STRING_DQ; + +// +// scanner +// + +var Scanner = function(source, initBlockMode, initLine, initColumn) { + this.source = source; + + this.pos = source.charCodeAt(0) === 0xFEFF ? 1 : 0; + this.lastPos = this.pos; + this.line = typeof initLine === 'undefined' ? 1 : initLine; + this.lineStartPos = typeof initColumn === 'undefined' ? -1 : -initColumn; + + this.minBlockMode = initBlockMode ? 1 : 0; + this.blockMode = this.minBlockMode; + this.urlMode = false; +}; + +Scanner.prototype = { + tokenize: function() { + var tokens = []; + + for (; this.pos < this.source.length; this.pos++) { + tokens.push(this.getToken()); + } + + return tokens; + }, + + createToken: function(type, line, column, value) { + }, + + getToken: function() { + var code = this.source.charCodeAt(this.pos); + var line = this.line; + var column = this.pos - this.lineStartPos; + var lastPos; + var next; + var type; + var value; + + switch (code < SYMBOL_CATEGORY_LENGTH ? SYMBOL_CATEGORY[code] : 0) { + case DIGIT: + type = TokenType.DecimalNumber; + value = this.readDecimalNumber(); + break; + + case STRING_SQ: + case STRING_DQ: + type = TokenType.String; + value = this.readString(code); + break; + + case WHITESPACE: + type = TokenType.Space; + value = this.readSpaces(); + break; + + case PUNCTUATOR: + if (code === SLASH) { + next = this.source.charCodeAt(this.pos + 1); + + if (next === STAR) { // /* + type = TokenType.Comment; + value = this.readComment(); + break; + } else if (next === SLASH && !this.urlMode) { // // + if (this.blockMode > 0) { + var skip = 0; + + while (this.source.charCodeAt(this.pos) === SLASH) { + skip++; + } + + type = TokenType.Identifier; + value = this.readIdentifier(skip); + + this.urlMode = this.urlMode || value === 'url'; + } else { + type = TokenType.Unknown; + value = this.readUnknown(); + } + break; + } + } + + type = PUNCTUATION[code]; + value = String.fromCharCode(code); + + if (code === RIGHT_PARENTHESIS) { + this.urlMode = false; + } else if (code === LEFT_CURLY_BRACE) { + this.blockMode++; + } else if (code === RIGHT_CURLY_BRACE) { + if (this.blockMode > this.minBlockMode) { + this.blockMode--; + } + } + + break; + + default: + type = TokenType.Identifier; + value = this.readIdentifier(0); + + this.urlMode = this.urlMode || value === 'url'; + } + + lastPos = this.lastPos; + this.lastPos = this.pos; + + return { + type: type, + value: value, + + offset: lastPos, + line: line, + column: column + }; + }, + + isNewline: function(code) { + if (code === N || code === F || code === R) { + if (code === R && this.pos + 1 < this.source.length && this.source.charCodeAt(this.pos + 1) === N) { + this.pos++; + } + + this.line++; + this.lineStartPos = this.pos; + return true; + } + + return false; + }, + + readSpaces: function() { + var start = this.pos; + + for (; this.pos < this.source.length; this.pos++) { + var code = this.source.charCodeAt(this.pos); + + if (!this.isNewline(code) && code !== SPACE && code !== TAB) { + break; + } + } + + this.pos--; + return this.source.substring(start, this.pos + 1); + }, + + readComment: function() { + var start = this.pos; + + for (this.pos += 2; this.pos < this.source.length; this.pos++) { + var code = this.source.charCodeAt(this.pos); + + if (code === STAR) { // */ + if (this.source.charCodeAt(this.pos + 1) === SLASH) { + this.pos++; + break; + } + } else { + this.isNewline(code); + } + } + + return this.source.substring(start, this.pos + 1); + }, + + readUnknown: function() { + var start = this.pos; + + for (this.pos += 2; this.pos < this.source.length; this.pos++) { + if (this.isNewline(this.source.charCodeAt(this.pos), this.source)) { + break; + } + } + + return this.source.substring(start, this.pos + 1); + }, + + readString: function(quote) { + var start = this.pos; + var res = ''; + + for (this.pos++; this.pos < this.source.length; this.pos++) { + var code = this.source.charCodeAt(this.pos); + + if (code === BACK_SLASH) { + var end = this.pos++; + + if (this.isNewline(this.source.charCodeAt(this.pos), this.source)) { + res += this.source.substring(start, end); + start = this.pos + 1; + } + } else if (code === quote) { + break; + } + } + + return res + this.source.substring(start, this.pos + 1); + }, + + readDecimalNumber: function() { + var start = this.pos; + var code; + + for (this.pos++; this.pos < this.source.length; this.pos++) { + code = this.source.charCodeAt(this.pos); + + if (code < 48 || code > 57) { // 0 .. 9 + break; + } + } + + this.pos--; + return this.source.substring(start, this.pos + 1); + }, + + readIdentifier: function(skip) { + var start = this.pos; + + for (this.pos += skip; this.pos < this.source.length; this.pos++) { + var code = this.source.charCodeAt(this.pos); + + if (code === BACK_SLASH) { + this.pos++; + + // skip escaped unicode sequence that can ends with space + // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? + for (var i = 0; i < 7 && this.pos + i < this.source.length; i++) { + code = this.source.charCodeAt(this.pos + i); + + if (i !== 6) { + if ((code >= 48 && code <= 57) || // 0 .. 9 + (code >= 65 && code <= 70) || // A .. F + (code >= 97 && code <= 102)) { // a .. f + continue; + } + } + + if (i > 0) { + this.pos += i - 1; + if (code === SPACE || code === TAB || this.isNewline(code)) { + this.pos++; + } + } + + break; + } + } else if (code < SYMBOL_CATEGORY_LENGTH && + IS_PUNCTUATOR[code] === PUNCTUATOR) { + break; + } + } + + this.pos--; + return this.source.substring(start, this.pos + 1); + } +}; + +module.exports = Scanner; From 78e27d308a3bce82f74f198767f209a748d87d1d Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Mon, 28 Mar 2016 00:24:28 +0300 Subject: [PATCH 02/19] lazy scanner --- lib/parser/index.js | 862 +++++++++++++++++++++++------------------- lib/parser/scanner.js | 61 ++- 2 files changed, 521 insertions(+), 402 deletions(-) diff --git a/lib/parser/index.js b/lib/parser/index.js index 61df3315..b3a1375d 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -6,8 +6,8 @@ var Scanner = require('./scanner.js'); var cleanInfo = require('../utils/cleanInfo.js'); var needPositions; var filename; -var tokens; var pos; +var scanner; var SCOPE_ATRULE_EXPRESSION = 1; var SCOPE_SELECTOR = 2; @@ -28,6 +28,7 @@ specialFunctions[SCOPE_VALUE] = { }; var rules = { + // stylesheet, selector, simpleSelector, block, value, atruleExpression 'atkeyword': getAtkeyword, 'atrule': getAtrule, 'attribute': getAttribute, @@ -85,37 +86,19 @@ var blockMode = { function parseError(message) { var error = new Error(message); - var line = 1; - var column = 1; - var lines; - - 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; - } - - } error.name = 'CssSyntaxError'; error.parseError = { - line: line, - column: column + line: scanner.line, + column: scanner.token ? scanner.token.column : 0 }; throw error; } function eat(tokenType) { - if (pos < tokens.length && tokens[pos].type === tokenType) { - pos++; + if (scanner.token !== null && scanner.token.type === tokenType) { + scanner.next(); return true; } @@ -123,12 +106,11 @@ function eat(tokenType) { } 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(); } return true; @@ -139,8 +121,8 @@ function expectIdentifier(name, eat) { } 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]) { return true; } @@ -150,15 +132,13 @@ function expectAny(what) { parseError(what + ' is expected'); } -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 }; } @@ -167,11 +147,11 @@ function getInfo(idx) { } function getStylesheet(nested) { - var stylesheet = [getInfo(pos), NodeType.StylesheetType]; + var stylesheet = [getInfo(), NodeType.StylesheetType]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.Space: stylesheet.push(getS()); break; @@ -203,9 +183,9 @@ function getStylesheet(nested) { return stylesheet; } -function isBlockAtrule(i) { - for (i++; i < tokens.length; i++) { - var type = tokens[i].type; +function isBlockAtrule() { + for (var offset = 1, cursor; cursor = scanner.lookup(offset); offset++) { + var type = cursor.type; if (type === TokenType.RightCurlyBracket) { return true; @@ -221,23 +201,29 @@ function isBlockAtrule(i) { } function getAtkeyword() { + var info = getInfo(); + eat(TokenType.CommercialAt); - return [getInfo(pos - 1), NodeType.AtkeywordType, getIdentifier()]; + return [ + info, + NodeType.AtkeywordType, + getIdentifier() + ]; } function getAtrule() { - var node = [getInfo(pos), NodeType.AtrulesType, getAtkeyword(pos)]; + var node = [getInfo(), NodeType.AtrulesType, getAtkeyword()]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.Semicolon: - pos++; + scanner.next(); break scan; case TokenType.LeftCurlyBracket: - if (isBlockAtrule(pos)) { + if (isBlockAtrule()) { node[1] = NodeType.AtrulebType; node.push(getBlockWithBrackets()); } else { @@ -247,13 +233,13 @@ function getAtrule() { NodeType.AtrulerqType ].concat(node.splice(3))); - pos++; // { + scanner.next(); // { var stylesheet = getStylesheet(true); stylesheet[1] = NodeType.AtrulersType; node.push(stylesheet); - pos++; // } + scanner.next(); // } } break scan; @@ -287,7 +273,7 @@ function getAtrule() { function getRuleset() { return [ - getInfo(pos), + getInfo(), NodeType.RulesetType, getSelector(), getBlockWithBrackets() @@ -295,19 +281,20 @@ function getRuleset() { } function getSelector() { - var selector = [getInfo(pos), NodeType.SelectorType]; + var selector = [getInfo(), NodeType.SelectorType]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.LeftCurlyBracket: break scan; case TokenType.Comma: selector.push([ - getInfo(pos++), + getInfo(), NodeType.DelimType ]); + scanner.next(); break; default: @@ -322,8 +309,8 @@ function getSimpleSelector(nested) { var node = [getInfo(pos), NodeType.SimpleselectorType]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.Comma: break scan; @@ -392,7 +379,7 @@ function getSimpleSelector(nested) { } function getBlockWithBrackets() { - var info = getInfo(pos); + var info = getInfo(); var node; eat(TokenType.LeftCurlyBracket); @@ -403,11 +390,11 @@ function getBlockWithBrackets() { } function getBlock(info) { - var node = [info || getInfo(pos), NodeType.BlockType]; + var node = [info || getInfo(), NodeType.BlockType]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.RightCurlyBracket: break scan; @@ -421,9 +408,10 @@ function getBlock(info) { case TokenType.Semicolon: // ; node.push([ - getInfo(pos++), + getInfo(), NodeType.DecldelimType ]); + scanner.next(); break; default: @@ -435,24 +423,20 @@ function getBlock(info) { } function getDeclaration(nested) { - var startPos = pos; - var info = getInfo(pos); + var info = getInfo(); var property = getProperty(); 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[2][2].toLowerCase())) { // TODO: !!! + if (checkProgid()) { + return [ + info, + NodeType.FilterType, + property, + getFilterValue() + ]; } } @@ -465,11 +449,11 @@ function getDeclaration(nested) { } function getProperty() { - var info = getInfo(pos); + var info = getInfo(); var name = ''; - while (pos < tokens.length) { - var type = tokens[pos].type; + for (; scanner.token !== null; scanner.next()) { + var type = scanner.token.type; if (type !== TokenType.Solidus && type !== TokenType.Asterisk && @@ -477,7 +461,7 @@ function getProperty() { break; } - name += tokens[pos++].value; + name += scanner.token.value; } return readSC([ @@ -492,11 +476,11 @@ function getProperty() { } function getValue(nested) { - var node = [getInfo(pos), NodeType.ValueType]; + var node = [getInfo(), NodeType.ValueType]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.RightCurlyBracket: case TokenType.Semicolon: break scan; @@ -535,18 +519,19 @@ function getValue(nested) { default: // 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; - - node.push([ - getInfo(pos), - NodeType.IdentType, - prefix + '+' + getUnicodeRange(true) - ]); + 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(), + NodeType.IdentType, + prefix + '+' + getUnicodeRange(true) + ]); + } break; } } @@ -560,9 +545,7 @@ function getValue(nested) { // any = string | percentage | dimension | number | uri | functionExpression | funktion | unary | operator | ident function getAny(scope) { - var startPos = pos; - - switch (tokens[pos].type) { + switch (scanner.token.type) { case TokenType.String: return getString(); @@ -577,25 +560,26 @@ function getAny(scope) { var number = tryGetNumber(); 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); } } return number; } - 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) { + if (scanner.token.type === TokenType.HyphenMinus || + scanner.token.type === TokenType.PlusSign) { return getUnary(); } @@ -607,9 +591,8 @@ function getAny(scope) { var ident = getIdentifier(); - 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); } return ident; @@ -618,7 +601,7 @@ function getAny(scope) { // '[' S* attrib_name ']' // '[' S* attrib_name attrib_match [ IDENT | STRING ] S* attrib_flags? ']' function getAttribute() { - var node = [getInfo(pos), NodeType.AttribType]; + var node = [getInfo(), NodeType.AttribType]; eat(TokenType.LeftSquareBracket); @@ -628,11 +611,11 @@ function getAttribute() { readSC(node); - if (pos < tokens.length && tokens[pos].type !== TokenType.RightSquareBracket) { + if (scanner.token !== null && scanner.token.type !== TokenType.RightSquareBracket) { node.push(getAttrselector()); readSC(node); - if (pos < tokens.length && tokens[pos].type === TokenType.String) { + if (scanner.token !== null && scanner.token.type === TokenType.String) { node.push(getString()); } else { node.push(getIdentifier()); @@ -641,12 +624,13 @@ function getAttribute() { readSC(node); // attribute flags - if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) { + if (scanner.token !== null && scanner.token.type === TokenType.Identifier) { node.push([ - getInfo(pos), + getInfo(), 'attribFlags', - tokens[pos++].value + scanner.token.value ]); + scanner.next(); readSC(node); } } @@ -666,19 +650,23 @@ function getAttrselector() { TokenType.VerticalLine // |= ); - var startPos = pos; + var info = getInfo(); var name; - if (tokens[pos].type === TokenType.EqualsSign) { + if (scanner.token.type === TokenType.EqualsSign) { name = '='; - pos++; + scanner.next(); } else { - name = tokens[pos].value + '='; - pos++; + name = scanner.token.value + '='; + scanner.next(); eat(TokenType.EqualsSign); } - return [getInfo(startPos), NodeType.AttrselectorType, name]; + return [ + info, + NodeType.AttrselectorType, + name + ]; } function getBraces(scope) { @@ -689,27 +677,27 @@ function getBraces(scope) { var close; - if (tokens[pos].type === TokenType.LeftParenthesis) { + if (scanner.token.type === TokenType.LeftParenthesis) { close = TokenType.RightParenthesis; } else { close = TokenType.RightSquareBracket; } var node = [ - getInfo(pos), + getInfo(), NodeType.BracesType, - tokens[pos].value, + scanner.token.value, null ]; // left brace - pos++; + scanner.next(); scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case close: - node[3] = tokens[pos].value; + node[3] = scanner.token.value; break scan; case TokenType.Space: @@ -749,12 +737,12 @@ function getBraces(scope) { // '.' ident function getClass() { - var startPos = pos; + var info = getInfo(); eat(TokenType.FullStop); return [ - getInfo(startPos), + info, NodeType.ClassType, getIdentifier() ]; @@ -763,12 +751,12 @@ function getClass() { // '#' 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), + info, NodeType.ShashType, readIdent() ]; @@ -776,20 +764,20 @@ function getShash() { // + | > | ~ | /deep/ function getCombinator() { - var info = getInfo(pos); + var info = getInfo(); var combinator; - switch (tokens[pos].type) { + switch (scanner.token.type) { case TokenType.PlusSign: case TokenType.GreaterThanSign: case TokenType.Tilde: - combinator = tokens[pos].value; - pos++; + combinator = scanner.token.value; + scanner.next(); break; case TokenType.Solidus: combinator = '/deep/'; - pos++; + scanner.next(); expectIdentifier('deep', true); @@ -800,37 +788,48 @@ function getCombinator() { parseError('Combinator (+, >, ~, /deep/) is expected'); } - return [info, NodeType.CombinatorType, combinator]; + return [ + info, + NodeType.CombinatorType, + combinator + ]; } // '/*' .* '*/' function getComment() { - var value = tokens[pos].value; + var info = getInfo(); + var value = scanner.token.value; var len = value.length; if (len > 4 && value.charAt(len - 2) === '*' && value.charAt(len - 1) === '/') { len -= 2; } - return [getInfo(pos++), NodeType.CommentType, value.substring(2, len)]; + scanner.next(); + + return [ + info, + NodeType.CommentType, + value.substring(2, len) + ]; } // special reader for units to avoid adjoined IE hacks (i.e. '1px\9') 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('\\'); // no backslash in unit name if (backSlashPos === -1) { - pos++; + scanner.next(); return unit; } // 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; // return unit w/o backslash part return unit.substr(0, backSlashPos); @@ -840,100 +839,70 @@ function readUnit() { } // number ident -function getDimension(startPos, number) { +function getDimension(number) { return [ - getInfo(startPos || pos), + number ? number[0] : getInfo(), NodeType.DimensionType, number || getNumber(), - [getInfo(pos), NodeType.IdentType, readUnit()] - ]; -} - -// expression '(' raw ')' -function getOldIEExpression(startPos, ident) { - var raw = ''; - var balance = 0; - var startPos = pos; - var ident = getIdentifier(); - - if (ident[2].toLowerCase() !== 'expression') { - pos--; - parseError('`expression` is expected'); - } - - eat(TokenType.LeftParenthesis); - - while (pos < tokens.length) { - if (tokens[pos].type === TokenType.RightParenthesis) { - if (balance === 0) { - break; - } - - balance--; - } else if (tokens[pos].type === TokenType.LeftParenthesis) { - balance++; - } - - raw += tokens[pos++].value; - } - - eat(TokenType.RightParenthesis); - - return [ - getInfo(startPos), - NodeType.FunctionExpressionType, - raw + [ + getInfo(), + NodeType.IdentType, + readUnit() + ] ]; } // ident '(' functionBody ')' | // not '(' * ')' -function getFunction(scope) { - var body = getFunctionBody; +function getFunction(scope, ident) { + var defaultBody = getFunctionBody; + + if (!ident) { + ident = getIdentifier(); + } // parse special functions - if (pos + 1 < tokens.length && tokens[pos].type === TokenType.Identifier) { - var name = tokens[pos].value.toLowerCase(); + var name = ident[2].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(defaultBody, scope, ident); } -function getNotFunction(scope) { - return getFunctionInternal(getNotFunctionBody, scope); +function getNotFunction(scope, ident) { + return getFunctionInternal(getNotFunctionBody, scope, ident); } -function getVarFunction(scope) { - return getFunctionInternal(getVarFunctionBody, scope); +function getVarFunction(scope, ident) { + return getFunctionInternal(getVarFunctionBody, scope, ident); } -function getFunctionInternal(functionBodyReader, scope) { - var startPos = pos; - var ident = getIdentifier(); +function getFunctionInternal(functionBodyReader, scope, ident) { + var info = ident[0]; + var body; eat(TokenType.LeftParenthesis); - - var body = functionBodyReader(scope); - + body = functionBodyReader(scope); eat(TokenType.RightParenthesis); - return [getInfo(startPos), NodeType.FunctionType, ident, body]; + return [ + info, + NodeType.FunctionType, + ident, + body + ]; } function getFunctionBody(scope) { - var node = [getInfo(pos), NodeType.FunctionBodyType]; + var node = [getInfo(), NodeType.FunctionBodyType]; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.RightParenthesis: break scan; @@ -971,12 +940,12 @@ function getFunctionBody(scope) { } function getNotFunctionBody() { - var node = [getInfo(pos), NodeType.FunctionBodyType]; + var node = [getInfo(), NodeType.FunctionBodyType]; var wasSelector = false; scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.RightParenthesis: if (!wasSelector) { parseError('Simple selector is expected'); @@ -991,9 +960,10 @@ function getNotFunctionBody() { wasSelector = false; node.push([ - getInfo(pos++), + getInfo(), NodeType.DelimType ]); + scanner.next(); break; default: @@ -1007,13 +977,13 @@ function getNotFunctionBody() { // var '(' ident (',' )? ')' function getVarFunctionBody() { - var node = [getInfo(pos), NodeType.FunctionBodyType]; + var node = [getInfo(), NodeType.FunctionBodyType]; readSC(node); node.push(getIdentifier(true)); readSC(node); - if (pos < tokens.length && tokens[pos].type === TokenType.Comma) { + if (scanner.token !== null && scanner.token.type === TokenType.Comma) { node.push( getOperator(), getValue(true) @@ -1025,10 +995,12 @@ function getVarFunctionBody() { } // url '(' ws* (string | raw) ws* ')' -function getUri() { - var startPos = pos; - var node = [getInfo(startPos), NodeType.UriType]; - var ident = getIdentifier(); +function getUri(scope, ident) { + if (!ident) { + ident = getIdentifier(); + } + + var node = [ident[0], NodeType.UriType]; if (ident[2].toLowerCase() !== 'url') { pos--; @@ -1039,15 +1011,15 @@ function getUri() { readSC(node); - if (tokens[pos].type === TokenType.String) { + if (scanner.token.type === TokenType.String) { node.push(getString()); readSC(node); } 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; if (type === TokenType.Space || type === TokenType.LeftParenthesis || @@ -1055,11 +1027,11 @@ function getUri() { break; } - raw += tokens[pos++].value; + raw += scanner.token.value; } node.push([ - getInfo(rawStart), + rawInfo, NodeType.RawType, raw ]); @@ -1072,16 +1044,55 @@ function getUri() { return node; } +// expression '(' raw ')' +function getOldIEExpression(scope, ident) { + if (!ident) { + ident = getIdentifier(); + } + + var info = ident[0]; + var balance = 0; + var raw = ''; + + if (ident[2].toLowerCase() !== 'expression') { + parseError('`expression` is expected'); + } + + 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 [ + info, + NodeType.FunctionExpressionType, + raw + ]; +} + function getUnicodeRange(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; } if (!/^[0-9a-f]{1,6}$/i.test(hex)) { @@ -1090,20 +1101,21 @@ function getUnicodeRange(tryNext) { // U+abc??? 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; } } // U+aaa-bbb if (tryNext) { - if (pos < tokens.length && tokens[pos].type === TokenType.HyphenMinus) { - pos++; + if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) { + scanner.next(); + var next = getUnicodeRange(false); if (!next) { @@ -1121,13 +1133,13 @@ function readIdent(varAllowed) { var name = ''; // 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(); } } @@ -1136,12 +1148,13 @@ function readIdent(varAllowed) { TokenType.Identifier ); - if (pos < tokens.length) { - name += tokens[pos].value; - pos++; + if (scanner.token !== null) { + name += scanner.token.value; + scanner.next(); + + for (; scanner.token !== null; scanner.next()) { + var type = scanner.token.type; - for (; pos < tokens.length; pos++) { - var type = tokens[pos].type; if (type !== TokenType.LowLine && type !== TokenType.Identifier && type !== TokenType.DecimalNumber && @@ -1149,7 +1162,7 @@ function readIdent(varAllowed) { break; } - name += tokens[pos].value; + name += scanner.token.value; } } @@ -1157,44 +1170,42 @@ function readIdent(varAllowed) { } 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(); } - 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) { + if (scanner.next() !== null) { + if (scanner.token.type === TokenType.HyphenMinus || + scanner.token.type === TokenType.Identifier || + scanner.token.type === TokenType.LowLine) { name += readIdent(); - } else if (tokens[pos].type === TokenType.Asterisk) { + } else if (scanner.token.type === TokenType.Asterisk) { checkColon = false; name += '*'; - pos++; + scanner.next(); } } } } - if (checkColon && pos < tokens.length && tokens[pos].type === TokenType.Colon) { - pos++; + if (checkColon && scanner.token !== null && scanner.token.type === TokenType.Colon) { + scanner.next(); name += ':' + readIdent(); } @@ -1206,14 +1217,24 @@ function getNamespacedIdentifier(checkColon) { } function getIdentifier(varAllowed) { - return [getInfo(pos), NodeType.IdentType, readIdent(varAllowed)]; + return [ + getInfo(), + NodeType.IdentType, + readIdent(varAllowed) + ]; } // ! ws* important function getImportant() { + var info = getInfo(); + var node; + eat(TokenType.ExclamationMark); - var node = readSC([getInfo(pos - 1), NodeType.ImportantType]); + node = readSC([ + info, + NodeType.ImportantType + ]); expectIdentifier('important', true); @@ -1227,16 +1248,17 @@ function getNth() { TokenType.DecimalNumber ); - 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(); } } else { var cmpValue = value.toLowerCase(); @@ -1245,26 +1267,33 @@ function getNth() { } } - pos++; + scanner.next(); return [ - getInfo(startPos), + info, NodeType.NthType, value ]; } function getNthSelector() { + var info = getInfo(); + var node; + eat(TokenType.Colon); expectIdentifier('nth', false); - var node = [getInfo(pos - 1), NodeType.NthselectorType, getIdentifier()]; + node = [ + info, + NodeType.NthselectorType, + getIdentifier() + ]; eat(TokenType.LeftParenthesis); scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.RightParenthesis: break scan; @@ -1292,36 +1321,43 @@ function getNthSelector() { } function tryGetNumber() { - var startPos = pos; + var info = getInfo(); 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 [ + info, + NodeType.NumberType, + number + ]; } return null; @@ -1349,49 +1385,63 @@ function getOperator() { TokenType.EqualsSign ); - return [getInfo(pos), NodeType.OperatorType, tokens[pos++].value]; + var info = getInfo(); + var value = scanner.token.value; + + scanner.next(); + + return [ + info, + NodeType.OperatorType, + value + ]; } // node: Percentage function tryGetPercentage() { - var startPos = pos; + var info = getInfo(); var number = tryGetNumber(); - if (!number) { - return null; + if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) { + return getPercentage(number); } - if (pos >= tokens.length || tokens[pos].type !== TokenType.PercentSign) { - return null; - } - - return getPercentage(startPos, number); + return null; } -function getPercentage(startPos, number) { - if (!startPos) { - startPos = pos; - } +function getPercentage(number) { + var info; if (!number) { + info = getInfo(); number = getNumber(); + } else { + info = number[0]; } eat(TokenType.PercentSign); - return [getInfo(startPos), NodeType.PercentageType, number]; + return [ + info, + NodeType.PercentageType, + number + ]; } -function getFilterv() { - var node = [getInfo(pos), NodeType.FiltervType]; +function getFilterValue() { + var progid; + var node = [ + getInfo(), + NodeType.FiltervType + ]; - while (checkProgid(pos)) { - node.push(getProgid()); + while (progid = checkProgid()) { + node.push(getProgid(progid)); } readSC(node); - if (pos < tokens.length && tokens[pos].type === TokenType.ExclamationMark) { + if (scanner.token !== null && scanner.token.type === TokenType.ExclamationMark) { node.push(getImportant()); } @@ -1399,81 +1449,78 @@ function getFilterv() { } // 'progid:' ws* 'DXImageTransform.Microsoft.' ident ws* '(' .* ')' -function checkSC(i) { - var start = i; - - while (i < tokens.length) { - if (tokens[i].type === TokenType.Space || - tokens[i].type === TokenType.Comment) { - i++; - } else { - break; +function checkProgid() { + function checkSC(offset) { + for (var cursor; cursor = scanner.lookup(offset); offset++) { + if (cursor.type !== TokenType.Space && + cursor.type !== TokenType.Comment) { + break; + } } - } - - return i - start; -} -function checkProgid(i) { - var start = i; + return offset; + } - i += checkSC(i); + var offset = checkSC(0); - 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 node = [getInfo(), NodeType.ProgidType]; var value = ''; - if (!progidEnd && !checkProgid(pos)) { + if (!progidEnd) { + progidEnd = checkProgid(); + } + + if (!progidEnd) { parseError('progid is expected'); } readSC(node); - 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; } + eat(TokenType.RightParenthesis); + value += ')'; + node.push([ - getInfo(rawStart), + rawInfo, NodeType.RawType, value ]); @@ -1485,17 +1532,17 @@ function getProgid() { // | | function getPseudo() { - if (pos >= tokens.length || tokens[pos].type !== TokenType.Colon) { + if (scanner.type === null || scanner.token.type !== TokenType.Colon) { parseError('Colon is expected'); } - if (pos + 1 >= tokens.length) { - pos++; + var next = scanner.lookup(1); + + if (next === null) { + scanner.next(); parseError('Colon or identifier is expected'); } - var next = tokens[pos + 1]; - if (next.type === TokenType.Colon) { return getPseudoElement(); } @@ -1510,24 +1557,25 @@ function getPseudo() { // :: ident function getPseudoElement() { + var info = getInfo(); + eat(TokenType.Colon); eat(TokenType.Colon); - return [getInfo(pos - 2), NodeType.PseudoeType, getIdentifier()]; + return [info, NodeType.PseudoeType, getIdentifier()]; } // : ( ident | function ) function getPseudoClass() { - var startPos = pos; + var info = getInfo(); var node = eat(TokenType.Colon) && getIdentifier(); - 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) { + node = getFunction(SCOPE_SELECTOR, node); } return [ - getInfo(startPos), + info, NodeType.PseudocType, node ]; @@ -1535,13 +1583,22 @@ function getPseudoClass() { // ws function getS() { - return [getInfo(pos), NodeType.SType, tokens[pos++].value]; + var info = getInfo(); + var value = scanner.token.value; + + scanner.next(); + + return [ + info, + NodeType.SType, + value + ]; } function readSC(node) { scan: - while (pos < tokens.length) { - switch (tokens[pos].type) { + for (; scanner.token !== null;) { + switch (scanner.token.type) { case TokenType.Space: node.push(getS()); break; @@ -1560,7 +1617,16 @@ function readSC(node) { // node: String function getString() { - return [getInfo(pos), NodeType.StringType, tokens[pos++].value]; + var info = getInfo(); + var value = scanner.token.value; + + scanner.next(); + + return [ + info, + NodeType.StringType, + value + ]; } // '+' | '-' @@ -1570,15 +1636,31 @@ function getUnary() { TokenType.PlusSign ); - return [getInfo(pos), NodeType.UnaryType, tokens[pos++].value]; + var info = getInfo(); + var value = scanner.token.value; + + scanner.next(); + + return [ + info, + NodeType.UnaryType, + value + ]; } // '//' ... // TODO: remove it as wrong thing function getUnknown() { + var info = getInfo(); + var value = scanner.token.value; + eat(TokenType.Unknown); - return [getInfo(pos - 1), NodeType.UnknownType, tokens[pos - 1].value]; + return [ + info, + NodeType.UnknownType, + value + ]; } // # ident @@ -1590,15 +1672,22 @@ function getVhash() { TokenType.Identifier ); - var name = tokens[pos].value; + var info = getInfo(); + var name = 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(); + name += scanner.token.value; } - return [getInfo(pos - 1), NodeType.VhashType, name]; + scanner.next(); + + return [ + info, + NodeType.VhashType, + name + ]; } module.exports = function parse(source, context, options) { @@ -1624,14 +1713,15 @@ module.exports = function parse(source, context, options) { context = context || 'stylesheet'; pos = 0; - var scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column); - tokens = scanner.tokenize(); + scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column); + scanner.next(); + // tokens = scanner.tokenize(); - if (tokens.length) { + if (scanner.token) { ast = rules[context](); } - tokens = null; // drop tokens + scanner = null; if (!ast) { switch (context) { diff --git a/lib/parser/scanner.js b/lib/parser/scanner.js index aed639e2..1639024d 100644 --- a/lib/parser/scanner.js +++ b/lib/parser/scanner.js @@ -93,6 +93,7 @@ var Scanner = function(source, initBlockMode, initLine, initColumn) { this.source = source; this.pos = source.charCodeAt(0) === 0xFEFF ? 1 : 0; + this.eof = this.pos === this.source.length; this.lastPos = this.pos; this.line = typeof initLine === 'undefined' ? 1 : initLine; this.lineStartPos = typeof initColumn === 'undefined' ? -1 : -initColumn; @@ -100,9 +101,40 @@ var Scanner = function(source, initBlockMode, initLine, initColumn) { this.minBlockMode = initBlockMode ? 1 : 0; this.blockMode = this.minBlockMode; this.urlMode = false; + + this.token = null; + this.buffer = []; }; Scanner.prototype = { + lookup: function(offset) { + if (offset === 0) { + return this.token; + } + + for (var i = this.buffer.length; !this.eof && i < offset; i++) { + this.buffer.push(this.getToken()); + } + + return offset <= this.buffer.length ? this.buffer[offset - 1] : null; + }, + lookupType: function(offset, type) { + var token = this.lookup(offset); + + return token !== null && token.type === type; + }, + next: function() { + if (this.buffer.length !== 0) { + this.token = this.buffer.shift(); + } else if (!this.eof) { + this.token = this.getToken(); + } else { + this.token = null; + } + + return this.token; + }, + tokenize: function() { var tokens = []; @@ -113,9 +145,6 @@ Scanner.prototype = { return tokens; }, - createToken: function(type, line, column, value) { - }, - getToken: function() { var code = this.source.charCodeAt(this.pos); var line = this.line; @@ -152,9 +181,9 @@ Scanner.prototype = { break; } else if (next === SLASH && !this.urlMode) { // // if (this.blockMode > 0) { - var skip = 0; + var skip = 2; - while (this.source.charCodeAt(this.pos) === SLASH) { + while (this.source.charCodeAt(this.pos + 2) === SLASH) { skip++; } @@ -172,6 +201,7 @@ Scanner.prototype = { type = PUNCTUATION[code]; value = String.fromCharCode(code); + this.pos++; if (code === RIGHT_PARENTHESIS) { this.urlMode = false; @@ -192,8 +222,9 @@ Scanner.prototype = { this.urlMode = this.urlMode || value === 'url'; } - lastPos = this.lastPos; + lastPos = this.lastPos === 0 ? this.lastPos : this.lastPos - 1; this.lastPos = this.pos; + this.eof = this.pos === this.source.length; return { type: type, @@ -230,8 +261,7 @@ Scanner.prototype = { } } - this.pos--; - return this.source.substring(start, this.pos + 1); + return this.source.substring(start, this.pos); }, readComment: function() { @@ -242,7 +272,7 @@ Scanner.prototype = { if (code === STAR) { // */ if (this.source.charCodeAt(this.pos + 1) === SLASH) { - this.pos++; + this.pos += 2; break; } } else { @@ -250,7 +280,7 @@ Scanner.prototype = { } } - return this.source.substring(start, this.pos + 1); + return this.source.substring(start, this.pos); }, readUnknown: function() { @@ -262,7 +292,7 @@ Scanner.prototype = { } } - return this.source.substring(start, this.pos + 1); + return this.source.substring(start, this.pos); }, readString: function(quote) { @@ -280,11 +310,12 @@ Scanner.prototype = { start = this.pos + 1; } } else if (code === quote) { + this.pos++; break; } } - return res + this.source.substring(start, this.pos + 1); + return res + this.source.substring(start, this.pos); }, readDecimalNumber: function() { @@ -299,8 +330,7 @@ Scanner.prototype = { } } - this.pos--; - return this.source.substring(start, this.pos + 1); + return this.source.substring(start, this.pos); }, readIdentifier: function(skip) { @@ -340,8 +370,7 @@ Scanner.prototype = { } } - this.pos--; - return this.source.substring(start, this.pos + 1); + return this.source.substring(start, this.pos); } }; From 9299d34b7781aa727f7ed769950a7d4d14582071 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Mon, 28 Mar 2016 22:51:06 +0300 Subject: [PATCH 03/19] small refactoring --- lib/parser/index.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/parser/index.js b/lib/parser/index.js index b3a1375d..d304632a 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -150,7 +150,7 @@ function getStylesheet(nested) { var stylesheet = [getInfo(), NodeType.StylesheetType]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Space: stylesheet.push(getS()); @@ -216,7 +216,7 @@ function getAtrule() { var node = [getInfo(), NodeType.AtrulesType, getAtkeyword()]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Semicolon: scanner.next(); @@ -284,7 +284,7 @@ function getSelector() { var selector = [getInfo(), NodeType.SelectorType]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.LeftCurlyBracket: break scan; @@ -306,10 +306,10 @@ function getSelector() { } function getSimpleSelector(nested) { - var node = [getInfo(pos), NodeType.SimpleselectorType]; + var node = [getInfo(), NodeType.SimpleselectorType]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Comma: break scan; @@ -393,7 +393,7 @@ function getBlock(info) { var node = [info || getInfo(), NodeType.BlockType]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.RightCurlyBracket: break scan; @@ -479,7 +479,7 @@ function getValue(nested) { var node = [getInfo(), NodeType.ValueType]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.RightCurlyBracket: case TokenType.Semicolon: @@ -694,7 +694,7 @@ function getBraces(scope) { scanner.next(); scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case close: node[3] = scanner.token.value; @@ -901,7 +901,7 @@ function getFunctionBody(scope) { var node = [getInfo(), NodeType.FunctionBodyType]; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.RightParenthesis: break scan; @@ -944,7 +944,7 @@ function getNotFunctionBody() { var wasSelector = false; scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.RightParenthesis: if (!wasSelector) { @@ -1292,7 +1292,7 @@ function getNthSelector() { eat(TokenType.LeftParenthesis); scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.RightParenthesis: break scan; @@ -1399,7 +1399,6 @@ function getOperator() { // node: Percentage function tryGetPercentage() { - var info = getInfo(); var number = tryGetNumber(); if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) { @@ -1597,7 +1596,7 @@ function getS() { function readSC(node) { scan: - for (; scanner.token !== null;) { + while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Space: node.push(getS()); From 1dbea281c964de4bec5be5000218941381925f31 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Tue, 29 Mar 2016 21:59:05 +0300 Subject: [PATCH 04/19] fix parse error position --- lib/parser/index.js | 18 ++++++++++++++++-- lib/parser/scanner.js | 3 +++ test/fixture/parse-errors.json | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/parser/index.js b/lib/parser/index.js index d304632a..2d8a3fc6 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -86,11 +86,25 @@ var blockMode = { function parseError(message) { var error = new Error(message); + var line = 1; + var column = 1; + var lines; + + 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; + } error.name = 'CssSyntaxError'; error.parseError = { - line: scanner.line, - column: scanner.token ? scanner.token.column : 0 + line: line, + column: column }; throw error; diff --git a/lib/parser/scanner.js b/lib/parser/scanner.js index 1639024d..6acd62ba 100644 --- a/lib/parser/scanner.js +++ b/lib/parser/scanner.js @@ -102,6 +102,7 @@ var Scanner = function(source, initBlockMode, initLine, initColumn) { this.blockMode = this.minBlockMode; this.urlMode = false; + this.prevToken = null; this.token = null; this.buffer = []; }; @@ -124,6 +125,8 @@ Scanner.prototype = { return token !== null && token.type === type; }, next: function() { + this.prevToken = this.token; + if (this.buffer.length !== 0) { this.token = this.buffer.shift(); } else if (!this.eof) { diff --git a/test/fixture/parse-errors.json b/test/fixture/parse-errors.json index 9c338ba3..46360923 100644 --- a/test/fixture/parse-errors.json +++ b/test/fixture/parse-errors.json @@ -313,6 +313,20 @@ "line": 1, "column": 3 } +}, { + "css": ".a \n ", + "error": "LeftCurlyBracket is expected", + "position": { + "line": 1, + "column": 3 + } +}, { + "css": " \n \n.a \n ", + "error": "LeftCurlyBracket is expected", + "position": { + "line": 3, + "column": 3 + } }, { "css": ".foo { var(--side): 20px }", "error": "Colon is expected", From a05ae1e71bc5734a3f9bb5e62ce1fde17e0278d1 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Sun, 3 Apr 2016 22:25:16 +0300 Subject: [PATCH 05/19] new AST format - drop gonzales format, internal AST format is single format now - reduce memory consumption and performance boost --- lib/compressor/ast/gonzalesToInternal.js | 544 ------- lib/compressor/ast/internalToGonzales.js | 320 ----- lib/compressor/ast/translate.js | 3 + lib/compressor/ast/translateWithSourceMap.js | 3 + lib/compressor/ast/walk.js | 10 +- lib/compressor/clean/Comment.js | 3 + lib/compressor/clean/index.js | 3 +- lib/compressor/index.js | 123 +- lib/index.js | 8 +- lib/parser/index.js | 1274 +++++++++-------- lib/parser/tokenize.js | 331 ----- test/{internalAst.js => ast.js} | 92 +- test/common.js | 36 +- test/compress.js | 48 +- test/fixture/internal/index.js | 35 - test/fixture/internal/nth.json | 42 - test/fixture/internal/vhash.json | 18 - test/fixture/parse/atkeyword.json | 16 - .../atruleb.json => parse/atrule/block.json} | 0 .../atrules.json => parse/atrule/simple.json} | 34 + .../atrule/stylesheet.json} | 195 ++- test/fixture/parse/atruleb.json | 314 ---- test/fixture/parse/atruler.json | 600 -------- test/fixture/parse/atrules.json | 96 -- test/fixture/parse/attrib.json | 211 --- test/fixture/parse/attrselector.json | 22 - test/fixture/parse/block.json | 228 --- .../block.json => parse/block/Block.json} | 30 +- test/fixture/parse/braces.json | 217 --- test/fixture/parse/clazz.json | 9 - test/fixture/parse/combinator.json | 18 - test/fixture/parse/comment.json | 22 - test/fixture/parse/declaration.json | 150 -- .../declaration/Declaration.json} | 21 + .../declaration}/filter.json | 0 test/fixture/parse/dimension.json | 26 - test/fixture/parse/filter.json | 170 --- test/fixture/parse/functionExpression.json | 34 - test/fixture/parse/funktion.json | 308 ---- test/fixture/parse/ident.json | 90 -- test/fixture/parse/important.json | 30 - test/fixture/parse/index.js | 62 +- test/fixture/parse/nth.json | 22 - test/fixture/parse/nthselector.json | 83 -- test/fixture/parse/number.json | 34 - test/fixture/parse/operator.json | 22 - test/fixture/parse/percentage.json | 23 - test/fixture/parse/property.json | 23 - test/fixture/parse/pseudoc.json | 16 - test/fixture/parse/pseudoe.json | 16 - test/fixture/parse/ruleset.json | 664 --------- .../ruleset/Ruleset.json} | 85 ++ test/fixture/parse/selector.json | 32 - .../selector/Selector.json} | 0 test/fixture/parse/shash.json | 10 - .../simpleSelector/Attribute.json} | 147 ++ .../simpleSelector/Class.json} | 0 .../simpleSelector/Id.json} | 0 .../parse/simpleSelector/Identifier.json | 156 ++ .../simpleSelector/PseudoClass.json} | 0 .../simpleSelector/PseudoElement.json} | 0 .../simpleSelector/SimpleSelector.json} | 290 ++-- .../simpleSelector}/nthselector.json | 95 ++ test/fixture/parse/simpleselector.json | 334 ----- test/fixture/parse/string.json | 33 - test/fixture/parse/stylesheet.json | 656 --------- .../stylesheet/StyleSheet.json} | 159 ++ test/fixture/parse/stylesheet/comment.json | 37 + test/fixture/parse/stylesheet/unknown.json | 16 + test/fixture/parse/unary.json | 10 - test/fixture/parse/unknown.json | 10 - test/fixture/parse/uri.json | 51 - test/fixture/parse/value.json | 197 --- .../braces.json => parse/value/Braces.json} | 0 .../value/Dimension.json} | 0 .../value/Function.json} | 0 test/fixture/parse/value/Hash.json | 39 + test/fixture/parse/value/Important.json | 46 + .../number.json => parse/value/Number.json} | 0 .../value/Percentage.json} | 0 .../string.json => parse/value/String.json} | 24 + .../uri.json => parse/value/Url.json} | 0 .../value.json => parse/value/Value.json} | 31 + .../value/expression.json} | 0 test/fixture/parse/vhash.json | 22 - test/fixture/stringify.txt | 205 ++- test/helpers/stringify.js | 28 + test/parse.js | 77 +- test/sourceMaps.js | 6 +- test/specificity.js | 3 +- 90 files changed, 2352 insertions(+), 7146 deletions(-) delete mode 100644 lib/compressor/ast/gonzalesToInternal.js delete mode 100644 lib/compressor/ast/internalToGonzales.js create mode 100644 lib/compressor/clean/Comment.js delete mode 100644 lib/parser/tokenize.js rename test/{internalAst.js => ast.js} (53%) delete mode 100644 test/fixture/internal/index.js delete mode 100644 test/fixture/internal/nth.json delete mode 100644 test/fixture/internal/vhash.json delete mode 100644 test/fixture/parse/atkeyword.json rename test/fixture/{internal/atruleb.json => parse/atrule/block.json} (100%) rename test/fixture/{internal/atrules.json => parse/atrule/simple.json} (78%) rename test/fixture/{internal/atruler.json => parse/atrule/stylesheet.json} (80%) delete mode 100644 test/fixture/parse/atruleb.json delete mode 100644 test/fixture/parse/atruler.json delete mode 100644 test/fixture/parse/atrules.json delete mode 100644 test/fixture/parse/attrib.json delete mode 100644 test/fixture/parse/attrselector.json delete mode 100644 test/fixture/parse/block.json rename test/fixture/{internal/block.json => parse/block/Block.json} (94%) delete mode 100644 test/fixture/parse/braces.json delete mode 100644 test/fixture/parse/clazz.json delete mode 100644 test/fixture/parse/combinator.json delete mode 100644 test/fixture/parse/comment.json delete mode 100644 test/fixture/parse/declaration.json rename test/fixture/{internal/declaration.json => parse/declaration/Declaration.json} (90%) rename test/fixture/{internal => parse/declaration}/filter.json (100%) delete mode 100644 test/fixture/parse/dimension.json delete mode 100644 test/fixture/parse/filter.json delete mode 100644 test/fixture/parse/functionExpression.json delete mode 100644 test/fixture/parse/funktion.json delete mode 100644 test/fixture/parse/ident.json delete mode 100644 test/fixture/parse/important.json delete mode 100644 test/fixture/parse/nth.json delete mode 100644 test/fixture/parse/nthselector.json delete mode 100644 test/fixture/parse/number.json delete mode 100644 test/fixture/parse/operator.json delete mode 100644 test/fixture/parse/percentage.json delete mode 100644 test/fixture/parse/property.json delete mode 100644 test/fixture/parse/pseudoc.json delete mode 100644 test/fixture/parse/pseudoe.json delete mode 100644 test/fixture/parse/ruleset.json rename test/fixture/{internal/ruleset.json => parse/ruleset/Ruleset.json} (93%) delete mode 100644 test/fixture/parse/selector.json rename test/fixture/{internal/selector.json => parse/selector/Selector.json} (100%) delete mode 100644 test/fixture/parse/shash.json rename test/fixture/{internal/attrib.json => parse/simpleSelector/Attribute.json} (70%) rename test/fixture/{internal/clazz.json => parse/simpleSelector/Class.json} (100%) rename test/fixture/{internal/shash.json => parse/simpleSelector/Id.json} (100%) create mode 100644 test/fixture/parse/simpleSelector/Identifier.json rename test/fixture/{internal/pseudoc.json => parse/simpleSelector/PseudoClass.json} (100%) rename test/fixture/{internal/pseudoe.json => parse/simpleSelector/PseudoElement.json} (100%) rename test/fixture/{internal/simpleselector.json => parse/simpleSelector/SimpleSelector.json} (84%) rename test/fixture/{internal => parse/simpleSelector}/nthselector.json (67%) delete mode 100644 test/fixture/parse/simpleselector.json delete mode 100644 test/fixture/parse/string.json delete mode 100644 test/fixture/parse/stylesheet.json rename test/fixture/{internal/stylesheet.json => parse/stylesheet/StyleSheet.json} (85%) create mode 100644 test/fixture/parse/stylesheet/comment.json create mode 100644 test/fixture/parse/stylesheet/unknown.json delete mode 100644 test/fixture/parse/unary.json delete mode 100644 test/fixture/parse/unknown.json delete mode 100644 test/fixture/parse/uri.json delete mode 100644 test/fixture/parse/value.json rename test/fixture/{internal/braces.json => parse/value/Braces.json} (100%) rename test/fixture/{internal/dimension.json => parse/value/Dimension.json} (100%) rename test/fixture/{internal/funktion.json => parse/value/Function.json} (100%) create mode 100644 test/fixture/parse/value/Hash.json create mode 100644 test/fixture/parse/value/Important.json rename test/fixture/{internal/number.json => parse/value/Number.json} (100%) rename test/fixture/{internal/percentage.json => parse/value/Percentage.json} (100%) rename test/fixture/{internal/string.json => parse/value/String.json} (54%) rename test/fixture/{internal/uri.json => parse/value/Url.json} (100%) rename test/fixture/{internal/value.json => parse/value/Value.json} (93%) rename test/fixture/{internal/functionExpression.json => parse/value/expression.json} (100%) delete mode 100644 test/fixture/parse/vhash.json create mode 100644 test/helpers/stringify.js diff --git a/lib/compressor/ast/gonzalesToInternal.js b/lib/compressor/ast/gonzalesToInternal.js deleted file mode 100644 index 448e28d0..00000000 --- a/lib/compressor/ast/gonzalesToInternal.js +++ /dev/null @@ -1,544 +0,0 @@ -var List = require('../../utils/list.js'); -var styleSheetSeed = 0; // FIXME: until node.js 0.10 support drop and we can't use Map instead - -function StyleSheet(tokens) { - var rules = new List(); - - for (var i = 2; i < tokens.length; i++) { - var token = tokens[i]; - var type = token[1]; - - if (type !== 's' && - type !== 'comment' && - type !== 'unknown') { - rules.insert(List.createItem(convertToInternal(token))); - } - } - - return { - type: 'StyleSheet', - info: tokens[0], - avoidRulesMerge: false, - rules: rules, - id: styleSheetSeed++ - }; -} - -function Atrule(token, expression, block) { - if (expression instanceof List) { - expression = { - type: 'AtruleExpression', - info: expression.head ? expression.head.data.info : null, - sequence: expression, - id: null - }; - } - - return { - type: 'Atrule', - info: token[0], - name: token[2][2][2], - expression: expression, - block: block - }; -} - -function Declaration(token) { - return { - type: 'Declaration', - info: token[0], - property: convertToInternal(token[2]), - value: convertToInternal(token[3]), - id: 0, - length: 0, - fingerprint: null - }; -} - -function Value(token) { - var important = false; - var end = token.length - 1; - - for (; end >= 2; end--) { - var type = token[end][1]; - if (type !== 's' && type !== 'comment') { - if (type === 'important' && !important) { - important = true; - } else { - break; - } - } - } - - return { - type: 'Value', - info: token[0], - important: important, - sequence: trimSC(token, 2, end) - }; -} - -function firstNonSC(token) { - return convertToInternal(token[skipSC(token, 2)]); -} - -function skipSC(token, offset) { - for (; offset < token.length; offset++) { - var type = token[offset][1]; - if (type !== 's' && type !== 'comment') { - break; - } - } - - return offset; -} - -function trimSC(token, start, end) { - var list = new List(); - - start = skipSC(token, start); - for (; end >= start; end--) { - var type = token[end][1]; - if (type !== 's' && type !== 'comment') { - break; - } - } - - for (var i = start; i <= end; i++) { - var node = convertToInternal(token[i]); - if (node) { - list.insert(List.createItem(node)); - } - } - - return list; -} - -function argumentList(token) { - var list = new List(); - var args = token; - var start = 2; - - for (var i = start; i < args.length; i++) { - if (args[i][1] === 'operator' && args[i][2] === ',') { - list.insert(List.createItem({ - type: 'Argument', - info: {}, - sequence: trimSC(args, start, i - 1) - })); - start = i + 1; - } - } - - var lastArg = trimSC(args, start, args.length - 1); - if (lastArg.head || list.head) { - list.insert(List.createItem({ - type: 'Argument', - info: {}, - sequence: lastArg - })); - } - - return list; -} - -var types = { - atkeyword: false, - atruleb: function(token) { - return Atrule( - token, - trimSC(token, 3, token.length - 2), - convertToInternal(token[token.length - 1]) - ); - }, - atruler: function(token) { - return Atrule( - token, - convertToInternal(token[3]), - convertToInternal(token[4]) - ); - }, - atrulerq: function(token) { - return { - type: 'AtruleExpression', - info: token[0], - sequence: trimSC(token, 2, token.length - 1), - id: null - }; - }, - atrulers: StyleSheet, - atrules: function(token) { - return Atrule( - token, - trimSC(token, 3, token.length - 1), - null - ); - }, - attrib: function(token) { - var offset = 2; - var name; - var operator = null; - var value = null; - var flags = null; - - offset = skipSC(token, 2); - name = convertToInternal(token[offset]); - - offset = skipSC(token, offset + 1); - if (offset < token.length) { - operator = token[offset][2]; - - offset = skipSC(token, offset + 1); - value = convertToInternal(token[offset]); - - if (offset < token.length) { - offset = skipSC(token, offset + 1); - if (offset < token.length && token[offset][1] === 'attribFlags') { - flags = token[offset][2]; - } - } - } - - return { - type: 'Attribute', - info: token[0], - name: name, - operator: operator, - value: value, - flags: flags - }; - }, - attrselector: false, - block: function(token) { - var declarations = new List(); - - for (var i = 2; i < token.length; i++) { - var item = token[i]; - var type = item[1]; - - if (type === 'declaration' || type === 'filter') { - declarations.insert(List.createItem(convertToInternal(item))); - } - } - - return { - type: 'Block', - info: token[0], - declarations: declarations - }; - }, - braces: function(token) { - return { - type: 'Braces', - info: token[0], - open: token[2], - close: token[3], - sequence: trimSC(token, 4, token.length - 1) - }; - }, - clazz: function(token) { - return { - type: 'Class', - info: token[0], - name: token[2][2] - }; - }, - combinator: function(token) { - return { - type: 'Combinator', - info: token[0], - name: token[2] - }; - }, - comment: false, - declaration: Declaration, - decldelim: false, // redundant - delim: false, // redundant - dimension: function(token) { - return { - type: 'Dimension', - info: token[0], - value: token[2][2], - unit: token[3][2] - }; - }, - filter: Declaration, - filterv: Value, - functionExpression: function(token) { - return { - type: 'Function', - name: 'expression', - arguments: new List([{ - type: 'Argument', - sequence: new List([{ - type: 'Raw', - value: token[2] - }]) - }]) - }; - }, - funktion: function(token) { - return { - type: 'Function', - info: token[0], - name: token[2][2], - arguments: argumentList(token[3]) - }; - }, - functionBody: false, // redundant - ident: function(token) { - return { - type: 'Identifier', - info: token[0], - name: token[2] - }; - }, - namespace: false, - nth: function(token) { - return { - type: 'Nth', - value: token[2] - }; - }, - nthselector: function(token) { - return { - type: 'FunctionalPseudo', - info: token[0], - name: token[2][2], - arguments: new List([{ - type: 'Argument', - sequence: new List( - token - .slice(3) - .filter(function(item) { - return item[1] !== 's' && item[1] !== 'comment'; - }) - .map(convertToInternal) - ) - }]) - }; - }, - number: function(token) { - return { - type: 'Number', - info: token[0], - value: token[2] - }; - }, - operator: function(token) { - return { - type: 'Operator', - info: token[0], - value: token[2] - }; - }, - percentage: function(token) { - return { - type: 'Percentage', - info: token[0], - value: token[2][2] - }; - }, - progid: function(token) { - return { - type: 'Progid', - info: token[0], - value: firstNonSC(token) - }; - }, - property: function(token) { - return { - type: 'Property', - info: token[0], - name: token[2][2] - }; - }, - pseudoc: function(token) { - var value = token[2]; - - if (value[1] === 'funktion') { - var name = value[2][2]; - - if (name.toLowerCase() === 'not') { - return { - type: 'Negation', - sequence: types.selector(value[3]).selectors - }; - } - - return { - type: 'FunctionalPseudo', - info: value[0], - name: name, - arguments: argumentList(value[3]) - }; - } - - return { - type: 'PseudoClass', - info: token[0], - name: value[2] - }; - }, - pseudoe: function(token) { - var value = token[2]; - - return { - type: 'PseudoElement', - info: token[0], - name: value[2] - }; - }, - raw: function(token) { - return { - type: 'Raw', - info: token[0], - value: token[2] - }; - }, - ruleset: function(token) { - var selector = convertToInternal(token[2]); - var block = convertToInternal(token[3]); - - return { - type: 'Ruleset', - info: token[0], - pseudoSignature: null, - selector: selector, - block: block - }; - }, - s: function(token) { - return { - type: 'Space', - info: token[0] - }; - }, - selector: function(token) { - var last = 'delim'; - var selectors = new List(); - - for (var i = 2; i < token.length; i++) { - var item = token[i]; - var type = item[1]; - - if (type === 'simpleselector' || type === 'delim') { - if (last === type) { - // bad selector - selectors = new List(); - break; - } - last = type; - } - - if (type === 'simpleselector') { - selectors.insert(List.createItem(convertToInternal(item))); - } - } - - // check selector is valid since gonzales parses selectors - // like "foo," or "foo,,bar" as correct; - // w/o this check broken selector will be repaired and broken ruleset apply; - // make selector empty so compressor can remove ruleset with no selector - if (last === 'delim' || (!selectors.isEmpty() && selectors.last().sequence.isEmpty())) { - selectors = new List(); - } - - return { - type: 'Selector', - info: token[0], - selectors: selectors - }; - }, - shash: function(token) { - return { - type: 'Id', - info: token[0], - name: token[2] - }; - }, - simpleselector: function(token) { - var sequence = new List(); - var combinator = null; - - for (var i = skipSC(token, 2); i < token.length; i++) { - var item = token[i]; - - switch (item[1]) { - case 's': - if (!combinator) { - combinator = [item[0], 'combinator', ' ']; - } - break; - - case 'comment': - break; - - case 'combinator': - combinator = item; - break; - - default: - if (combinator !== null) { - sequence.insert(List.createItem(convertToInternal(combinator))); - } - - combinator = null; - sequence.insert(List.createItem(convertToInternal(item))); - } - } - - return { - type: 'SimpleSelector', - info: token[0], - sequence: sequence, - id: null, - compareMarker: null - }; - }, - string: function(token) { - return { - type: 'String', - info: token[0], - value: token[2] - }; - }, - stylesheet: StyleSheet, - unary: function(token) { - return { - type: 'Operator', - info: token[0], - value: token[2] - }; - }, - unknown: false, - uri: function(token) { - return { - type: 'Url', - info: token[0], - value: firstNonSC(token) - }; - }, - value: Value, - vhash: function(token) { - return { - type: 'Hash', - info: token[0], - value: token[2] - }; - } -}; - -function convertToInternal(token) { - if (token) { - var type = token[1]; - - if (types.hasOwnProperty(type) && typeof types[type] === 'function') { - return types[type](token); - } - } - - return null; -} - -module.exports = convertToInternal; diff --git a/lib/compressor/ast/internalToGonzales.js b/lib/compressor/ast/internalToGonzales.js deleted file mode 100644 index df83cb3e..00000000 --- a/lib/compressor/ast/internalToGonzales.js +++ /dev/null @@ -1,320 +0,0 @@ -function eachDelim(node, type, itemsProperty, delimeter) { - var result = [node.info, type]; - var list = node[itemsProperty]; - - list.each(function(data, item) { - result.push(toGonzales(data)); - - if (item.next) { - result.push(delimeter.slice()); - } - }); - - return result; -} - -function buildArguments(body, args) { - args.each(function(data, item) { - body.push.apply(body, data.sequence.map(toGonzales)); - if (item.next) { - body.push([{}, 'operator', ',']); - } - }); -} - -function toGonzales(node) { - switch (node.type) { - case 'StyleSheet': - return [ - node.info || {}, - 'stylesheet' - ].concat(node.rules.map(toGonzales).filter(Boolean)); - - case 'Atrule': - var type = 'atruler'; - - if (!node.block) { - type = 'atrules'; - } else { - if (node.block.type === 'Block') { - type = 'atruleb'; - } - } - - var result = [ - node.info, - type, - [{}, 'atkeyword', [{}, 'ident', node.name]] - ]; - - if (node.expression && !node.expression.sequence.isEmpty()) { - if (type === 'atruler') { - result.push([ - node.expression.info, - 'atrulerq', - [{}, 's', ' '] - ].concat(node.expression.sequence.map(toGonzales))); - } else { - result.push([{}, 's', ' ']); - result = result.concat(node.expression.sequence.map(toGonzales)); - } - } else { - if (type === 'atruler') { - result.push([ - {}, - 'atrulerq' - ]); - } - } - - if (node.block) { - if (type === 'atruler') { - result.push([ - node.block.info, - 'atrulers' - ].concat(node.block.rules.map(toGonzales))); - } else { - result.push(toGonzales(node.block)); - } - } - - return result; - - case 'Ruleset': - return [ - node.info, - 'ruleset', - toGonzales(node.selector), - toGonzales(node.block) - ]; - - case 'Selector': - return eachDelim(node, 'selector', 'selectors', [{}, 'delim']); - - case 'SimpleSelector': - var result = [ - node.info, - 'simpleselector' - ]; - - node.sequence.each(function(data) { - var node = toGonzales(data); - - // add extra spaces around /deep/ combinator since comment beginning/ending may to be produced - if (data.type === 'Combinator' && data.name === '/deep/') { - result.push( - [{}, 's', ' '], - node, - [{}, 's', ' '] - ); - } else { - result.push(node); - } - }); - - return result; - - case 'Negation': - var body = eachDelim(node, 'functionBody', 'sequence', [{}, 'delim']); - - return [ - node.info, - 'pseudoc', - [ - {}, - 'funktion', - [{}, 'ident', 'not'], - body - ] - ]; - - case 'Attribute': - var result = [ - node.info, - 'attrib' - ]; - - result.push([{}, 'ident', node.name.name]); - - if (node.operator !== null) { - result.push([{}, 'attrselector', node.operator]); - - if (node.value !== null) { - result.push(toGonzales(node.value)); - - if (node.flags !== null) { - if (node.value.type !== 'String') { - result.push([{}, 's', ' ']); - } - - result.push([{}, 'attribFlags', node.flags]); - } - } - } - return result; - - case 'FunctionalPseudo': - if (/^nth-/.test(node.name)) { - var result = [ - node.info, - 'nthselector', - [{}, 'ident', node.name] - ]; - - buildArguments(result, node.arguments); - - return result; - } else { - var body = [ - {}, - 'functionBody' - ]; - - buildArguments(body, node.arguments); - - return [ - node.info, - 'pseudoc', - [ - {}, - 'funktion', - [{}, 'ident', node.name], - body - ] - ]; - } - - case 'Function': - var body = [ - {}, - 'functionBody' - ]; - - buildArguments(body, node.arguments); - - if (node.name === 'expression') { - return [{}, 'functionExpression', body[2][2]]; - } - - return [ - node.info, - 'funktion', - [{}, 'ident', node.name], - body - ]; - - case 'Block': - return eachDelim(node, 'block', 'declarations', [{}, 'decldelim']); - - case 'Declaration': - return [ - node.info, - !node.value.sequence.isEmpty() && - node.value.sequence.first().type === 'Progid' && - /(-[a-z]+-|[\*-_])?filter$/.test(node.property.name) - ? 'filter' - : 'declaration', - toGonzales(node.property), - toGonzales(node.value) - ]; - - case 'Braces': - return [ - node.info, - 'braces', - node.open, - node.close - ].concat(node.sequence.map(toGonzales)); - - case 'Value': - var result = [ - node.info, - !node.sequence.isEmpty() && - node.sequence.first().type === 'Progid' - ? 'filterv' - : 'value' - ].concat(node.sequence.map(toGonzales)); - - if (node.important) { - result.push([{}, 'important']); - } - - return result; - - case 'Url': - return [node.info, 'uri', toGonzales(node.value)]; - - case 'Progid': - return [node.info, 'progid', toGonzales(node.value)]; - - case 'Property': - return [node.info, 'property', [{}, 'ident', node.name]]; - - case 'Combinator': - return node.name === ' ' - ? [node.info, 's', node.name] - : [node.info, 'combinator', node.name]; - - case 'Identifier': - return [node.info, 'ident', node.name]; - - case 'PseudoElement': - return [node.info, 'pseudoe', [{}, 'ident', node.name]]; - - case 'PseudoClass': - return [node.info, 'pseudoc', [{}, 'ident', node.name]]; - - case 'Class': - return [node.info, 'clazz', [{}, 'ident', node.name]]; - - case 'Id': - return [node.info, 'shash', node.name]; - - case 'Nth': - return [node.info, 'nth', node.value]; - - case 'Hash': - return [node.info, 'vhash', node.value]; - - case 'Number': - return [node.info, 'number', node.value]; - - case 'Dimension': - return [ - node.info, - 'dimension', - [{}, 'number', node.value], - [{}, 'ident', node.unit] - ]; - - case 'Operator': - return [ - node.info, - node.value === '+' || node.value === '-' ? 'unary' : 'operator', - node.value - ]; - - case 'Raw': - return [node.info, node.value && /\S/.test(node.value) ? 'raw' : 's', node.value]; - - case 'String': - return [node.info, 'string', node.value]; - - case 'Percentage': - return [node.info, 'percentage', [{}, 'number', node.value]]; - - case 'Space': - return [node.info, 's', ' ']; - - case 'Comment': - return [node.info, 'comment', node.value]; - - // nothing to do - // case 'Argument': - - default: - throw new Error('Unknown node type: ' + node.type); - } -} - -module.exports = toGonzales; diff --git a/lib/compressor/ast/translate.js b/lib/compressor/ast/translate.js index 7fb55901..14f7d583 100644 --- a/lib/compressor/ast/translate.js +++ b/lib/compressor/ast/translate.js @@ -148,6 +148,9 @@ function translate(node) { case 'Raw': return node.value; + case 'Unknown': + return node.value; + case 'Percentage': return node.value + '%'; diff --git a/lib/compressor/ast/translateWithSourceMap.js b/lib/compressor/ast/translateWithSourceMap.js index 67a24d6b..4dfd2106 100644 --- a/lib/compressor/ast/translateWithSourceMap.js +++ b/lib/compressor/ast/translateWithSourceMap.js @@ -259,6 +259,9 @@ function translate(node) { case 'Raw': return node.value; + case 'Unknown': + return node.value; + case 'Percentage': return node.value + '%'; diff --git a/lib/compressor/ast/walk.js b/lib/compressor/ast/walk.js index ed123b6a..a7948eb8 100644 --- a/lib/compressor/ast/walk.js +++ b/lib/compressor/ast/walk.js @@ -118,9 +118,16 @@ function walkAll(node, item, list) { this['function'] = null; break; + case 'AtruleExpression': + this.atruleExpression = node; + + node.sequence.each(walkAll, this); + + this.atruleExpression = null; + break; + case 'Value': case 'Argument': - case 'AtruleExpression': case 'SimpleSelector': case 'Braces': case 'Negation': @@ -159,6 +166,7 @@ function createContext(root, fn) { fn: fn, root: root, stylesheet: null, + atruleExpression: null, ruleset: null, selector: null, declaration: null, diff --git a/lib/compressor/clean/Comment.js b/lib/compressor/clean/Comment.js new file mode 100644 index 00000000..aa80108d --- /dev/null +++ b/lib/compressor/clean/Comment.js @@ -0,0 +1,3 @@ +module.exports = function cleanComment(data, item, list) { + list.remove(item); +}; diff --git a/lib/compressor/clean/index.js b/lib/compressor/clean/index.js index fac54728..6d599af9 100644 --- a/lib/compressor/clean/index.js +++ b/lib/compressor/clean/index.js @@ -4,7 +4,8 @@ var handlers = { Atrule: require('./Atrule.js'), Ruleset: require('./Ruleset.js'), Declaration: require('./Declaration.js'), - Identifier: require('./Identifier.js') + Identifier: require('./Identifier.js'), + Comment: require('./Comment.js') }; module.exports = function(ast, usageData) { diff --git a/lib/compressor/index.js b/lib/compressor/index.js index 3b32313b..02114504 100644 --- a/lib/compressor/index.js +++ b/lib/compressor/index.js @@ -1,75 +1,68 @@ 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('./ast/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 + } }; } function compressBlock(ast, usageData, num, logger) { logger('Compress block #' + num, null, true); - var internalAst = convertToInternal(ast); - logger('convertToInternal', internalAst); - - internalAst.firstAtrulesAllowed = ast.firstAtrulesAllowed; + var seed = 1; + ast.firstAtrulesAllowed = ast.firstAtrulesAllowed; + walkRules(ast, function() { + if (!this.stylesheet.id) { + this.stylesheet.id = seed++; + } + }); + logger('init', ast); // 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; } module.exports = function compress(ast, options) { options = options || {}; + ast = ast || { type: 'StyleSheet', rules: new List() }; var logger = typeof options.logger === 'function' ? options.logger : Function(); var restructuring = @@ -77,28 +70,33 @@ module.exports = function compress(ast, options) { 'restructuring' in options ? options.restructuring : true; var result = new List(); - var block = { offset: 2 }; + var block; var firstAtrulesAllowed = true; var blockNum = 1; var blockRules; var blockMode = false; 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 + }]) + }; } if (options.usage) { @@ -106,7 +104,8 @@ module.exports = function compress(ast, options) { } do { - block = readBlock(ast, block.offset); + block = readBlock(ast); + // console.log(JSON.stringify(block.stylesheet, null, 2)); block.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed; block.stylesheet = compressBlock(block.stylesheet, usageData, blockNum++, logger); @@ -126,10 +125,7 @@ module.exports = function compress(ast, options) { })); } - result.insert(List.createItem({ - type: 'Comment', - value: block.comment[2] - })); + result.insert(List.createItem(block.comment)); // add \n after comment if block is not empty if (!blockRules.isEmpty()) { @@ -150,20 +146,21 @@ module.exports = function compress(ast, options) { } 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 }; } - if (!options.outputAst || options.outputAst === 'gonzales') { - return convertToGonzales(result); - } - return result; }; diff --git a/lib/index.js b/lib/index.js index 5919d80d..7db78cd5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,8 +13,7 @@ var justDoIt = function(src, noStructureOptimizations, needInfo) { var ast = parse(src, 'stylesheet', needInfo); var compressed = compress(ast, { - restructure: !noStructureOptimizations, - outputAst: 'internal' + restructure: !noStructureOptimizations }); return traslateInternal(compressed); @@ -66,7 +65,6 @@ function copy(obj) { function buildCompressOptions(options) { options = copy(options); - options.outputAst = 'internal'; if (typeof options.logger !== 'function' && options.debug) { options.logger = createDefaultLogger(options.debug); @@ -117,7 +115,7 @@ function minifyStylesheet(source, options) { }; function minifyBlock(source, options) { - return minify('declarations', source, options); + return minify('block', source, options); } module.exports = { @@ -138,8 +136,6 @@ module.exports = { // internal ast internal: { - fromGonzales: require('./compressor/ast/gonzalesToInternal'), - toGonzales: require('./compressor/ast/internalToGonzales'), translate: traslateInternal, translateWithSourceMap: traslateInternalWithSourceMap, walk: internalWalkers.all, diff --git a/lib/parser/index.js b/lib/parser/index.js index 2d8a3fc6..7069cc1b 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -1,12 +1,11 @@ 'use strict'; -var TokenType = require('./const.js').TokenType; -var NodeType = require('./const.js').NodeType; -var Scanner = require('./scanner.js'); -var cleanInfo = require('../utils/cleanInfo.js'); +var TokenType = require('./const').TokenType; +var NodeType = require('./const').NodeType; +var Scanner = require('./scanner'); +var List = require('../utils/list'); var needPositions; var filename; -var pos; var scanner; var SCOPE_ATRULE_EXPRESSION = 1; @@ -28,55 +27,16 @@ specialFunctions[SCOPE_VALUE] = { }; var rules = { - // stylesheet, selector, simpleSelector, block, value, atruleExpression - '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 + stylesheet: getStylesheet, + atrule: getAtrule, + atruleExpression: getAtruleExpression, + ruleset: getRuleset, + selector: getSelector, + simpleSelector: getSimpleSelector, + simpleselector: getSimpleSelector, + block: getBlock, + declaration: getDeclaration, + value: getValue }; var blockMode = { @@ -160,26 +120,50 @@ function getInfo() { } +function removeTrailingSpaces(list) { + while (list.tail) { + if (list.tail.data.type === 'Space') { + list.remove(list.tail); + } else { + break; + } + } +} + function getStylesheet(nested) { - var stylesheet = [getInfo(), NodeType.StylesheetType]; + var child = null; + var node = { + type: 'StyleSheet', + info: getInfo(), + rules: new List(), + avoidRulesMerge: false, + id: null + }; scan: 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; case TokenType.RightCurlyBracket: @@ -190,11 +174,30 @@ function getStylesheet(nested) { break scan; default: - stylesheet.push(getRuleset()); + child = getRuleset(); + } + + if (child !== null) { + node.rules.insert(List.createItem(child)); } } - return stylesheet; + return node; +} + +// '//' ... +// 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() { @@ -214,71 +217,95 @@ function isBlockAtrule() { return true; } -function getAtkeyword() { - var info = getInfo(); - - eat(TokenType.CommercialAt); - - return [ - info, - NodeType.AtkeywordType, - getIdentifier() - ]; -} - -function getAtrule() { - var node = [getInfo(), NodeType.AtrulesType, getAtkeyword()]; +function getAtruleExpression() { + var child = null; + var node = { + type: 'AtruleExpression', + info: getInfo(), + sequence: new List(), + id: null + }; scan: while (scanner.token !== null) { switch (scanner.token.type) { case TokenType.Semicolon: - scanner.next(); break scan; case TokenType.LeftCurlyBracket: - if (isBlockAtrule()) { - node[1] = NodeType.AtrulebType; - node.push(getBlockWithBrackets()); - } else { - node[1] = NodeType.AtrulerType; - node.push([ - {}, - NodeType.AtrulerqType - ].concat(node.splice(3))); - - scanner.next(); // { - - var stylesheet = getStylesheet(true); - stylesheet[1] = NodeType.AtrulersType; - node.push(stylesheet); - - scanner.next(); // } - } 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'); } } @@ -286,16 +313,23 @@ function getAtrule() { } function getRuleset() { - return [ - getInfo(), - NodeType.RulesetType, - getSelector(), - getBlockWithBrackets() - ]; + return { + type: 'Ruleset', + info: getInfo(), + pseudoSignature: null, + selector: getSelector(), + block: getBlockWithBrackets() + }; } function getSelector() { - var selector = [getInfo(), NodeType.SelectorType]; + var isBadSelector = false; + var lastComma = true; + var node = { + type: 'Selector', + info: getInfo(), + selectors: new List() + }; scan: while (scanner.token !== null) { @@ -304,23 +338,50 @@ function getSelector() { break scan; case TokenType.Comma: - selector.push([ - getInfo(), - 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(), NodeType.SimpleselectorType]; + var child = null; + var combinator = null; + var node = { + type: 'SimpleSelector', + info: getInfo(), + sequence: new List(), + id: null, + compareMarker: null + }; scan: while (scanner.token !== null) { @@ -342,69 +403,83 @@ function getSimpleSelector(nested) { 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; case TokenType.PlusSign: case TokenType.GreaterThanSign: case TokenType.Tilde: 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; default: parseError('Unexpected input'); } - } - return node; -} + if (child !== null) { + if (combinator !== null) { + node.sequence.insert(List.createItem(combinator)); + combinator = null; + } -function getBlockWithBrackets() { - var info = getInfo(); - var node; + node.sequence.insert(List.createItem(child)); + } + } - eat(TokenType.LeftCurlyBracket); - node = getBlock(info); - eat(TokenType.RightCurlyBracket); + if (combinator && combinator.name !== ' ') { + parseError('Unexpected combinator'); + } return node; } -function getBlock(info) { - var node = [info || getInfo(), NodeType.BlockType]; +function getDeclarations() { + var child = null; + var declarations = new List(); scan: while (scanner.token !== null) { @@ -413,58 +488,83 @@ function getBlock(info) { 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(), - 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 info = getInfo(); var property = getProperty(); + var value; eat(TokenType.Colon); // check it's a filter - if (/filter$/.test(property[2][2].toLowerCase())) { // TODO: !!! - if (checkProgid()) { - return [ - info, - NodeType.FilterType, - property, - getFilterValue() - ]; - } + 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, + id: null, + length: null, + fingerprint: null + }; } function getProperty() { - var info = getInfo(); var name = ''; + var node = { + type: 'Property', + info: getInfo(), + name: null + }; for (; scanner.token !== null; scanner.next()) { var type = scanner.token.type; @@ -478,19 +578,23 @@ function getProperty() { 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(), NodeType.ValueType]; + var child = null; + var node = { + type: 'Value', + info: getInfo(), + important: false, + sequence: new List() + }; + + readSC(); scan: while (scanner.token !== null) { @@ -506,29 +610,31 @@ function getValue(nested) { 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: - node.push(getVhash()); + child = getVhash(); break; case TokenType.Solidus: case TokenType.Comma: - node.push(getOperator()); + child = getOperator(); break; case TokenType.LeftParenthesis: 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; default: @@ -540,20 +646,26 @@ function getValue(nested) { scanner.next(); // U or u scanner.next(); // + - node.push([ - getInfo(), - NodeType.IdentType, - prefix + '+' + getUnicodeRange(true) - ]); + child = { + type: 'Identifier', + info: getInfo(), // FIXME: wrong position + name: prefix + '+' + readUnicodeRange(true) + }; } break; } } - node.push(getAny(SCOPE_VALUE)); + child = getAny(SCOPE_VALUE); + } + + if (child !== null) { + node.sequence.insert(List.createItem(child)); } } + removeTrailingSpaces(node.sequence); + return node; } @@ -578,7 +690,7 @@ function getAny(scope) { if (scanner.token.type === TokenType.PercentSign) { return getPercentage(number); } else if (scanner.token.type === TokenType.Identifier) { - return getDimension(number); + return getDimension(number.value); } } @@ -594,7 +706,7 @@ function getAny(scope) { if (scanner.token.type === TokenType.HyphenMinus || scanner.token.type === TokenType.PlusSign) { - return getUnary(); + return getOperator(); } parseError('Unexpected input'); @@ -603,7 +715,7 @@ function getAny(scope) { parseError('Unexpected input'); } - var ident = getIdentifier(); + var ident = getIdentifier(false); if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) { return getFunction(scope, ident); @@ -612,40 +724,69 @@ function getAny(scope) { return ident; } +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(), 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 (scanner.token !== null && scanner.token.type !== TokenType.RightSquareBracket) { - node.push(getAttrselector()); - readSC(node); + node.operator = readAttrselector(); + + readSC(); if (scanner.token !== null && scanner.token.type === TokenType.String) { - node.push(getString()); + node.value = getString(); } else { - node.push(getIdentifier()); + node.value = getIdentifier(false); } - readSC(node); + readSC(); // attribute flags if (scanner.token !== null && scanner.token.type === TokenType.Identifier) { - node.push([ - getInfo(), - 'attribFlags', - scanner.token.value - ]); + node.flags = scanner.token.value; + scanner.next(); - readSC(node); + readSC(); } } @@ -654,42 +795,16 @@ function getAttribute() { return node; } -function getAttrselector() { - expectAny('Attribute selector (=, ~=, ^=, $=, *=, |=)', - TokenType.EqualsSign, // = - TokenType.Tilde, // ~= - TokenType.CircumflexAccent, // ^= - TokenType.DollarSign, // $= - TokenType.Asterisk, // *= - TokenType.VerticalLine // |= - ); - - var info = getInfo(); - var name; - - if (scanner.token.type === TokenType.EqualsSign) { - name = '='; - scanner.next(); - } else { - name = scanner.token.value + '='; - scanner.next(); - eat(TokenType.EqualsSign); - } - - return [ - info, - 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 (scanner.token.type === TokenType.LeftParenthesis) { close = TokenType.RightParenthesis; @@ -697,52 +812,54 @@ function getBraces(scope) { close = TokenType.RightSquareBracket; } - var node = [ - getInfo(), - NodeType.BracesType, - scanner.token.value, - null - ]; - // left brace scanner.next(); + readSC(); + scan: while (scanner.token !== null) { switch (scanner.token.type) { case close: - node[3] = scanner.token.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; case TokenType.LeftParenthesis: case TokenType.LeftSquareBracket: - node.push(getBraces(scope)); + child = getBraces(scope); break; case TokenType.Solidus: case TokenType.Asterisk: case TokenType.Comma: 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 eat(close); @@ -755,25 +872,24 @@ function getClass() { eat(TokenType.FullStop); - return [ - info, - 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 info = getInfo(); eat(TokenType.NumberSign); - return [ - info, - NodeType.ShashType, - readIdent() - ]; + return { + type: 'Id', + info: info, + name: readIdent(false) + }; } // + | > | ~ | /deep/ @@ -782,6 +898,11 @@ function getCombinator() { var combinator; switch (scanner.token.type) { + case TokenType.Space: + combinator = ' '; + scanner.next(); + break; + case TokenType.PlusSign: case TokenType.GreaterThanSign: case TokenType.Tilde: @@ -802,11 +923,11 @@ function getCombinator() { parseError('Combinator (+, >, ~, /deep/) is expected'); } - return [ - info, - NodeType.CombinatorType, - combinator - ]; + return { + type: 'Combinator', + info: info, + name: combinator + }; } // '/*' .* '*/' @@ -821,11 +942,11 @@ function getComment() { scanner.next(); - return [ - info, - NodeType.CommentType, - value.substring(2, len) - ]; + return { + type: 'Comment', + info: info, + value: value.substring(2, len) + }; } // special reader for units to avoid adjoined IE hacks (i.e. '1px\9') @@ -854,29 +975,56 @@ function readUnit() { // number ident function getDimension(number) { - return [ - number ? number[0] : getInfo(), - NodeType.DimensionType, - number || getNumber(), - [ - getInfo(), - NodeType.IdentType, - readUnit() - ] - ]; + return { + type: 'Dimension', + info: getInfo(), + value: number || readNumber(), + unit: readUnit() + }; +} + +// number "%" +function tryGetPercentage() { + var number = tryGetNumber(); + + if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) { + return getPercentage(number); + } + + return null; +} + +function getPercentage(number) { + var info; + + if (!number) { + info = getInfo(); + number = readNumber(); + } else { + info = number.info; + number = number.value; + } + + eat(TokenType.PercentSign); + + return { + type: 'Percentage', + info: info, + value: number + }; } // ident '(' functionBody ')' | // not '(' * ')' function getFunction(scope, ident) { - var defaultBody = getFunctionBody; + var defaultArguments = getFunctionArguments; if (!ident) { - ident = getIdentifier(); + ident = getIdentifier(false); } // parse special functions - var name = ident[2].toLowerCase(); + var name = ident.name.toLowerCase(); if (specialFunctions.hasOwnProperty(scope)) { if (specialFunctions[scope].hasOwnProperty(name)) { @@ -884,35 +1032,30 @@ function getFunction(scope, ident) { } } - return getFunctionInternal(defaultBody, scope, ident); + return getFunctionInternal(defaultArguments, scope, ident); } -function getNotFunction(scope, ident) { - return getFunctionInternal(getNotFunctionBody, scope, ident); -} - -function getVarFunction(scope, ident) { - return getFunctionInternal(getVarFunctionBody, scope, ident); -} - -function getFunctionInternal(functionBodyReader, scope, ident) { - var info = ident[0]; - var body; +function getFunctionInternal(functionArgumentsReader, scope, ident) { + var args; eat(TokenType.LeftParenthesis); - body = functionBodyReader(scope); + args = functionArgumentsReader(scope); eat(TokenType.RightParenthesis); - return [ - info, - 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(), NodeType.FunctionBodyType]; +function getFunctionArguments(scope) { + var args = new List(); + var argument = null; + var child = null; + + readSC(); scan: while (scanner.token !== null) { @@ -921,40 +1064,68 @@ function getFunctionBody(scope) { 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; case TokenType.LeftParenthesis: 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(), NodeType.FunctionBodyType]; +function getVarFunction(scope, ident) { + return getFunctionInternal(getVarFunctionArguments, scope, ident); +} + +function getNotFunctionArguments() { + var args = new List(); var wasSelector = false; scan: @@ -973,61 +1144,77 @@ function getNotFunctionBody() { } wasSelector = false; - node.push([ - getInfo(), - NodeType.DelimType - ]); scanner.next(); break; default: 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 (',' )? ')' -function getVarFunctionBody() { - var node = [getInfo(), NodeType.FunctionBodyType]; +function getVarFunctionArguments() { // TODO: special type Variable? + var args = new List(); - readSC(node); - node.push(getIdentifier(true)); - readSC(node); + readSC(); + + args.insert(List.createItem({ + type: 'Argument', + sequence: new List([getIdentifier(true)]) + })); + + readSC(); if (scanner.token !== null && scanner.token.type === TokenType.Comma) { - node.push( - getOperator(), - getValue(true) - ); - readSC(node); + 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(scope, ident) { - if (!ident) { - ident = getIdentifier(); - } - - var node = [ident[0], NodeType.UriType]; - - if (ident[2].toLowerCase() !== 'url') { - pos--; - parseError('`url` is expected'); - } + var node = { + type: 'Url', + info: ident.info, + // name: ident.name, + value: null + }; eat(TokenType.LeftParenthesis); // ( - readSC(node); + readSC(); if (scanner.token.type === TokenType.String) { - node.push(getString()); - readSC(node); + node.value = getString(); + readSC(); } else { var rawInfo = getInfo(); var raw = ''; @@ -1044,13 +1231,13 @@ function getUri(scope, ident) { raw += scanner.token.value; } - node.push([ - rawInfo, - NodeType.RawType, - raw - ]); + node.value = { + type: 'Raw', + info: rawInfo, + value: raw + }; - readSC(node); + readSC(); } eat(TokenType.RightParenthesis); // ) @@ -1060,18 +1247,9 @@ function getUri(scope, ident) { // expression '(' raw ')' function getOldIEExpression(scope, ident) { - if (!ident) { - ident = getIdentifier(); - } - - var info = ident[0]; var balance = 0; var raw = ''; - if (ident[2].toLowerCase() !== 'expression') { - parseError('`expression` is expected'); - } - eat(TokenType.LeftParenthesis); for (; scanner.token !== null; scanner.next()) { @@ -1090,14 +1268,21 @@ function getOldIEExpression(scope, ident) { eat(TokenType.RightParenthesis); - return [ - info, - NodeType.FunctionExpressionType, - raw - ]; + return { + type: 'Function', + info: ident.info, + name: ident.name, + arguments: new List([{ + type: 'Argument', + sequence: new List([{ + type: 'Raw', + value: raw + }]) + }]) + }; } -function getUnicodeRange(tryNext) { +function readUnicodeRange(tryNext) { var hex = ''; for (; scanner.token !== null; scanner.next()) { @@ -1130,7 +1315,7 @@ function getUnicodeRange(tryNext) { if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) { scanner.next(); - var next = getUnicodeRange(false); + var next = readUnicodeRange(false); if (!next) { parseError('Unexpected input'); @@ -1196,7 +1381,7 @@ function getNamespacedIdentifier(checkColon) { name = '*'; scanner.next(); } else { - name = readIdent(); + name = readIdent(false); } if (scanner.token !== null) { @@ -1208,7 +1393,7 @@ function getNamespacedIdentifier(checkColon) { if (scanner.token.type === TokenType.HyphenMinus || scanner.token.type === TokenType.Identifier || scanner.token.type === TokenType.LowLine) { - name += readIdent(); + name += readIdent(false); } else if (scanner.token.type === TokenType.Asterisk) { checkColon = false; name += '*'; @@ -1220,39 +1405,45 @@ function getNamespacedIdentifier(checkColon) { if (checkColon && scanner.token !== null && scanner.token.type === TokenType.Colon) { scanner.next(); - name += ':' + readIdent(); + name += ':' + readIdent(false); } - return [ - info, - NodeType.IdentType, - name - ]; + return { + type: 'Identifier', + info: info, + name: name + }; } function getIdentifier(varAllowed) { - return [ - getInfo(), - NodeType.IdentType, - readIdent(varAllowed) - ]; + return { + type: 'Identifier', + info: getInfo(), + name: readIdent(varAllowed) + }; } // ! ws* important -function getImportant() { - var info = getInfo(); - var node; +function getImportant() { // TODO? + // var info = getInfo(); eat(TokenType.ExclamationMark); - node = readSC([ - info, - 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; } // odd | even | number? n @@ -1283,25 +1474,31 @@ function getNth() { scanner.next(); - return [ - info, - 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); - node = [ - info, - NodeType.NthselectorType, - getIdentifier() - ]; + node = { + type: 'FunctionalPseudo', + info: info, + name: readIdent(false), + arguments: new List([{ + type: 'Argument', + sequence: sequence + }]) + }; eat(TokenType.LeftParenthesis); @@ -1312,20 +1509,22 @@ function getNthSelector() { break scan; case TokenType.Space: - node.push(getS()); - break; - case TokenType.Comment: - node.push(getComment()); + scanner.next(); + child = null; break; case TokenType.HyphenMinus: case TokenType.PlusSign: - node.push(getUnary()); + child = getOperator(); break; default: - node.push(getNth()); + child = getNth(); + } + + if (child !== null) { + sequence.insert(List.createItem(child)); } } @@ -1334,8 +1533,7 @@ function getNthSelector() { return node; } -function tryGetNumber() { - var info = getInfo(); +function readNumber() { var wasDigits = false; var number = ''; var offset = 0; @@ -1367,11 +1565,22 @@ function tryGetNumber() { scanner.next(); } - return [ - info, - NodeType.NumberType, - number - ]; + return number; + } + + return null; +} + +function tryGetNumber() { + var info = getInfo(); + var number = readNumber(); + + if (number !== null) { + return { + type: 'Number', + info: info, + value: number + }; } return null; @@ -1387,75 +1596,38 @@ function getNumber() { return number; } -// '/' | '*' | ',' | ':' | '=' +// '/' | '*' | ',' | ':' | '=' | '+' | '-' // 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 info = getInfo(); - var value = scanner.token.value; + var node = { + type: 'Operator', + info: getInfo(), + value: scanner.token.value + }; scanner.next(); - return [ - info, - NodeType.OperatorType, - value - ]; -} - -// node: Percentage -function tryGetPercentage() { - var number = tryGetNumber(); - - if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) { - return getPercentage(number); - } - - return null; -} - -function getPercentage(number) { - var info; - - if (!number) { - info = getInfo(); - number = getNumber(); - } else { - info = number[0]; - } - - eat(TokenType.PercentSign); - - return [ - info, - NodeType.PercentageType, - number - ]; + return node; } -function getFilterValue() { +function getFilterValue() { // TODO var progid; - var node = [ - getInfo(), - NodeType.FiltervType - ]; + var node = { + type: 'Value', + info: getInfo(), + important: false, + sequence: new List() + }; while (progid = checkProgid()) { - node.push(getProgid(progid)); + node.sequence.insert(List.createItem(getProgid(progid))); } readSC(node); if (scanner.token !== null && scanner.token.type === TokenType.ExclamationMark) { - node.push(getImportant()); + node.important = getImportant(); } return node; @@ -1511,8 +1683,12 @@ function checkProgid() { } function getProgid(progidEnd) { - var node = [getInfo(), NodeType.ProgidType]; var value = ''; + var node = { + type: 'Progid', + info: getInfo(), + value: null + }; if (!progidEnd) { progidEnd = checkProgid(); @@ -1532,11 +1708,11 @@ function getProgid(progidEnd) { eat(TokenType.RightParenthesis); value += ')'; - node.push([ - rawInfo, - NodeType.RawType, - value - ]); + node.value = { + type: 'Raw', + info: rawInfo, + value: value + }; readSC(node); @@ -1545,10 +1721,6 @@ function getProgid(progidEnd) { // | | function getPseudo() { - if (scanner.type === null || scanner.token.type !== TokenType.Colon) { - parseError('Colon is expected'); - } - var next = scanner.lookup(1); if (next === null) { @@ -1575,49 +1747,55 @@ function getPseudoElement() { eat(TokenType.Colon); eat(TokenType.Colon); - return [info, NodeType.PseudoeType, getIdentifier()]; + return { + type: 'PseudoElement', + info: info, + name: readIdent(false) + }; } // : ( ident | function ) function getPseudoClass() { var info = getInfo(); - var node = eat(TokenType.Colon) && getIdentifier(); + var ident = eat(TokenType.Colon) && getIdentifier(false); if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) { - node = getFunction(SCOPE_SELECTOR, node); + return getFunction(SCOPE_SELECTOR, ident); } - return [ - info, - NodeType.PseudocType, - node - ]; + return { + type: 'PseudoClass', + info: info, + name: ident.name + }; } // ws function getS() { - var info = getInfo(); - var value = scanner.token.value; + var node = { + type: 'Space' + // value: scanner.token.value + }; scanner.next(); - return [ - info, - NodeType.SType, - value - ]; + return node; } -function readSC(node) { +function readSC() { + // var nodes = []; + scan: 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; default: @@ -1625,59 +1803,29 @@ function readSC(node) { } } - return node; + return null; + + // return nodes.length ? new List(nodes) : null; } // node: String function getString() { - var info = getInfo(); - var value = scanner.token.value; - - scanner.next(); - - return [ - info, - NodeType.StringType, - value - ]; -} - -// '+' | '-' -function getUnary() { - expectAny('Unary operator', - TokenType.HyphenMinus, - TokenType.PlusSign - ); - - var info = getInfo(); - var value = scanner.token.value; + var node = { + type: 'String', + info: getInfo(), + value: scanner.token.value + }; scanner.next(); - return [ - info, - NodeType.UnaryType, - value - ]; -} - -// '//' ... -// TODO: remove it as wrong thing -function getUnknown() { - var info = getInfo(); - var value = scanner.token.value; - - eat(TokenType.Unknown); - - return [ - info, - NodeType.UnknownType, - value - ]; + return node; } // # ident function getVhash() { + var info = getInfo(); + var value; + eat(TokenType.NumberSign); expectAny('Number or identifier', @@ -1685,22 +1833,21 @@ function getVhash() { TokenType.Identifier ); - var info = getInfo(); - var name = scanner.token.value; + value = scanner.token.value; if (scanner.token.type === TokenType.DecimalNumber && scanner.lookupType(1, TokenType.Identifier)) { scanner.next(); - name += scanner.token.value; + value += scanner.token.value; } scanner.next(); - return [ - info, - NodeType.VhashType, - name - ]; + return { + type: 'Hash', + info: info, + value: value + }; } module.exports = function parse(source, context, options) { @@ -1724,33 +1871,18 @@ module.exports = function parse(source, context, options) { filename = options.filename || ''; context = context || 'stylesheet'; - pos = 0; + + if (!rules.hasOwnProperty(context)) { + throw new Error('Unknown context `' + context + '`'); + } scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column); scanner.next(); - // tokens = scanner.tokenize(); - - if (scanner.token) { - ast = rules[context](); - } + ast = rules[context](); scanner = null; - if (!ast) { - switch (context) { - case 'stylesheet': - ast = [{}, context]; - break; - // case 'declarations': - // ast = [{}, 'block']; - // break; - } - } - - 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; }; diff --git a/lib/parser/tokenize.js b/lib/parser/tokenize.js deleted file mode 100644 index e51ef9eb..00000000 --- a/lib/parser/tokenize.js +++ /dev/null @@ -1,331 +0,0 @@ -'use strict'; - -var TokenType = require('./const.js').TokenType; -var lineStartPos; -var line; -var pos; - -var TAB = 9; -var N = 10; -var F = 12; -var R = 13; -var SPACE = 32; -var DOUBLE_QUOTE = 34; -var QUOTE = 39; -var RIGHT_PARENTHESIS = 41; -var STAR = 42; -var SLASH = 47; -var BACK_SLASH = 92; -var UNDERSCORE = 95; -var LEFT_CURLY_BRACE = 123; -var RIGHT_CURLY_BRACE = 125; - -var WHITESPACE = 1; -var PUNCTUATOR = 2; -var DIGIT = 3; -var STRING_SQ = 4; -var STRING_DQ = 5; - -var PUNCTUATION = { - 9: TokenType.Tab, // '\t' - 10: TokenType.Newline, // '\n' - 13: TokenType.Newline, // '\r' - 32: TokenType.Space, // ' ' - 33: TokenType.ExclamationMark, // '!' - 34: TokenType.QuotationMark, // '"' - 35: TokenType.NumberSign, // '#' - 36: TokenType.DollarSign, // '$' - 37: TokenType.PercentSign, // '%' - 38: TokenType.Ampersand, // '&' - 39: TokenType.Apostrophe, // '\'' - 40: TokenType.LeftParenthesis, // '(' - 41: TokenType.RightParenthesis, // ')' - 42: TokenType.Asterisk, // '*' - 43: TokenType.PlusSign, // '+' - 44: TokenType.Comma, // ',' - 45: TokenType.HyphenMinus, // '-' - 46: TokenType.FullStop, // '.' - 47: TokenType.Solidus, // '/' - 58: TokenType.Colon, // ':' - 59: TokenType.Semicolon, // ';' - 60: TokenType.LessThanSign, // '<' - 61: TokenType.EqualsSign, // '=' - 62: TokenType.GreaterThanSign, // '>' - 63: TokenType.QuestionMark, // '?' - 64: TokenType.CommercialAt, // '@' - 91: TokenType.LeftSquareBracket, // '[' - 93: TokenType.RightSquareBracket, // ']' - 94: TokenType.CircumflexAccent, // '^' - 95: TokenType.LowLine, // '_' - 123: TokenType.LeftCurlyBracket, // '{' - 124: TokenType.VerticalLine, // '|' - 125: TokenType.RightCurlyBracket, // '}' - 126: TokenType.Tilde // '~' -}; -var SYMBOL_CATEGORY_LENGTH = Math.max.apply(null, Object.keys(PUNCTUATION)) + 1; -var SYMBOL_CATEGORY = new Uint32Array(SYMBOL_CATEGORY_LENGTH); -var IS_PUNCTUATOR = new Uint32Array(SYMBOL_CATEGORY_LENGTH); - -// fill categories -Object.keys(PUNCTUATION).forEach(function(key) { - SYMBOL_CATEGORY[Number(key)] = PUNCTUATOR; - IS_PUNCTUATOR[Number(key)] = PUNCTUATOR; -}, SYMBOL_CATEGORY); - -// don't treat as punctuator -IS_PUNCTUATOR[UNDERSCORE] = 0; - -for (var i = 48; i <= 57; i++) { - SYMBOL_CATEGORY[i] = DIGIT; -} - -SYMBOL_CATEGORY[SPACE] = WHITESPACE; -SYMBOL_CATEGORY[TAB] = WHITESPACE; -SYMBOL_CATEGORY[N] = WHITESPACE; -SYMBOL_CATEGORY[R] = WHITESPACE; -SYMBOL_CATEGORY[F] = WHITESPACE; - -SYMBOL_CATEGORY[QUOTE] = STRING_SQ; -SYMBOL_CATEGORY[DOUBLE_QUOTE] = STRING_DQ; - -// -// main part -// - -function tokenize(source, initBlockMode, initLine, initColumn) { - function pushToken(type, line, column, value) { - tokens.push({ - type: type, - value: value, - - offset: lastPos, - line: line, - column: column - }); - - lastPos = pos; - } - - if (!source) { - return []; - } - - var tokens = []; - var urlMode = false; - var lastPos = 0; - var minBlockMode = initBlockMode ? 1 : 0; - var blockMode = minBlockMode; - var code; - var next; - var ident; - - // ignore first char if it is byte order marker (UTF-8 BOM) - pos = source.charCodeAt(0) === 0xFEFF ? 1 : 0; - lastPos = pos; - line = typeof initLine === 'undefined' ? 1 : initLine; - lineStartPos = typeof initColumn === 'undefined' ? -1 : -initColumn; - - for (; pos < source.length; pos++) { - code = source.charCodeAt(pos); - - switch (code < SYMBOL_CATEGORY_LENGTH ? SYMBOL_CATEGORY[code] : 0) { - case DIGIT: - pushToken(TokenType.DecimalNumber, line, pos - lineStartPos, parseDecimalNumber(source)); - break; - - case STRING_SQ: - case STRING_DQ: - pushToken(TokenType.String, line, pos - lineStartPos, parseString(source, code)); - break; - - case WHITESPACE: - pushToken(TokenType.Space, line, pos - lineStartPos, parseSpaces(source)); - break; - - case PUNCTUATOR: - if (code === SLASH) { - next = source.charCodeAt(pos + 1); - - if (next === STAR) { // /* - pushToken(TokenType.Comment, line, pos - lineStartPos, parseComment(source)); - continue; - } else if (next === SLASH && !urlMode) { // // - if (blockMode > 0) { - var skip = 2; - - while (source.charCodeAt(pos + skip) === SLASH) { - skip++; - } - - pushToken(TokenType.Identifier, line, pos - lineStartPos, ident = parseIdentifier(source, skip)); - urlMode = urlMode || ident === 'url'; - } else { - pushToken(TokenType.Unknown, line, pos - lineStartPos, parseUnknown(source)); - } - continue; - } - } - - pushToken(PUNCTUATION[code], line, pos - lineStartPos, String.fromCharCode(code)); - - if (code === RIGHT_PARENTHESIS) { - urlMode = false; - } else if (code === LEFT_CURLY_BRACE) { - blockMode++; - } else if (code === RIGHT_CURLY_BRACE) { - if (blockMode > minBlockMode) { - blockMode--; - } - } - - break; - - default: - pushToken(TokenType.Identifier, line, pos - lineStartPos, ident = parseIdentifier(source, 0)); - urlMode = urlMode || ident === 'url'; - } - } - - return tokens; -} - -function checkNewline(code, s) { - if (code === N || code === F || code === R) { - if (code === R && pos + 1 < s.length && s.charCodeAt(pos + 1) === N) { - pos++; - } - - line++; - lineStartPos = pos; - return true; - } - - return false; -} - -function parseSpaces(s) { - var start = pos; - - for (; pos < s.length; pos++) { - var code = s.charCodeAt(pos); - - if (!checkNewline(code, s) && code !== SPACE && code !== TAB) { - break; - } - } - - pos--; - return s.substring(start, pos + 1); -} - -function parseComment(s) { - var start = pos; - - for (pos += 2; pos < s.length; pos++) { - var code = s.charCodeAt(pos); - - if (code === STAR) { // */ - if (s.charCodeAt(pos + 1) === SLASH) { - pos++; - break; - } - } else { - checkNewline(code, s); - } - } - - return s.substring(start, pos + 1); -} - -function parseUnknown(s) { - var start = pos; - - for (pos += 2; pos < s.length; pos++) { - if (checkNewline(s.charCodeAt(pos), s)) { - break; - } - } - - return s.substring(start, pos + 1); -} - -function parseString(s, quote) { - var start = pos; - var res = ''; - - for (pos++; pos < s.length; pos++) { - var code = s.charCodeAt(pos); - - if (code === BACK_SLASH) { - var end = pos++; - - if (checkNewline(s.charCodeAt(pos), s)) { - res += s.substring(start, end); - start = pos + 1; - } - } else if (code === quote) { - break; - } - } - - return res + s.substring(start, pos + 1); -} - -function parseDecimalNumber(s) { - var start = pos; - var code; - - for (pos++; pos < s.length; pos++) { - code = s.charCodeAt(pos); - - if (code < 48 || code > 57) { // 0 .. 9 - break; - } - } - - pos--; - return s.substring(start, pos + 1); -} - -function parseIdentifier(s, skip) { - var start = pos; - - for (pos += skip; pos < s.length; pos++) { - var code = s.charCodeAt(pos); - - if (code === BACK_SLASH) { - pos++; - - // skip escaped unicode sequence that can ends with space - // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? - for (var i = 0; i < 7 && pos + i < s.length; i++) { - code = s.charCodeAt(pos + i); - - if (i !== 6) { - if ((code >= 48 && code <= 57) || // 0 .. 9 - (code >= 65 && code <= 70) || // A .. F - (code >= 97 && code <= 102)) { // a .. f - continue; - } - } - - if (i > 0) { - pos += i - 1; - if (code === SPACE || code === TAB || checkNewline(code, s)) { - pos++; - } - } - - break; - } - } else if (code < SYMBOL_CATEGORY_LENGTH && - IS_PUNCTUATOR[code] === PUNCTUATOR) { - break; - } - } - - pos--; - return s.substring(start, pos + 1); -} - -module.exports = tokenize; diff --git a/test/internalAst.js b/test/ast.js similarity index 53% rename from test/internalAst.js rename to test/ast.js index a925cb22..e359a0e8 100644 --- a/test/internalAst.js +++ b/test/ast.js @@ -1,42 +1,12 @@ var assert = require('assert'); var csso = require('../lib/index.js'); -var gonzalesToInternal = require('../lib/compressor/ast/gonzalesToInternal.js'); -var internalToGonzales = require('../lib/compressor/ast/internalToGonzales.js'); var internalWalkAll = require('../lib/compressor/ast/walk.js').all; var internalWalkRules = require('../lib/compressor/ast/walk.js').rules; var internalWalkRulesRight = require('../lib/compressor/ast/walk.js').rulesRight; var internalTranslate = require('../lib/compressor/ast/translate.js'); -var testFiles = require('./fixture/internal').tests; -var forEachTest = require('./fixture/internal').forEachTest; - -function stringifyInternalAST(ast) { - function clean(source) { - if (source && typeof source.toJSON === 'function') { - source = source.toJSON(); - } - - if (Array.isArray(source)) { - return source.map(clean); - } - - if (source && typeof source === 'object') { - var result = {}; - for (var key in source) { - if (key !== 'info' && - key !== 'id' && key !== 'length' && - key !== 'fingerprint' && key !== 'compareMarker' && - key !== 'pseudoSignature' && key !== 'avoidRulesMerge') { - result[key] = clean(source[key]); - } - } - return result; - } - - return source; - } - - return JSON.stringify(clean(ast), null, 2); -} +var testFiles = require('./fixture/parse').tests; +var forEachTest = require('./fixture/parse').forEachTest; +var stringifyInternalAST = require('./helpers/stringify'); function expectedInternalWalk(ast) { function walk(node) { @@ -57,39 +27,9 @@ function expectedInternalWalk(ast) { return result; } -function createGonzalesToInternalTest(name, test, scope) { - return it(name, function() { - var gonzalesAst = csso.parse(test.source, scope, true); - var internalAst = gonzalesToInternal(gonzalesAst); - - assert.equal( - stringifyInternalAST(internalAst), - stringifyInternalAST(test.ast) - ); - }); -} - -function createInternalToGonzalesTest(name, test, scope) { - it(name, function() { - var gonzalesAst = csso.parse(test.source, scope, true); - var internalAst = gonzalesToInternal(gonzalesAst); - var restoredCSS = scope === 'block' - // gonzales parser requires curly braces to parse CSS block correctly - ? '{' + internalTranslate(internalAst) + '}' - : internalTranslate(internalAst); - - // restored gonzales AST should be equal to AST from CSS parser - assert.equal( - JSON.stringify(csso.cleanInfo(internalToGonzales(internalAst))), - JSON.stringify(csso.parse(restoredCSS, scope)) - ); - }); -} - function createInternalWalkAllTest(name, test, scope) { it(name, function() { - var ast = csso.parse(test.source, scope, true); - var internalAst = gonzalesToInternal(ast); + var internalAst = csso.parse(test.source, scope, true); var actual = []; internalWalkAll(internalAst, function(node) { @@ -104,10 +44,9 @@ function createInternalWalkAllTest(name, test, scope) { function createInternalWalkRulesTest(name, test, scope, walker) { it(name, function() { var ast = csso.parse(test.source, scope, true); - var internalAst = gonzalesToInternal(ast); var actual = []; - walker(internalAst, function(node) { + walker(ast, function(node) { actual.push(node.type); }); @@ -124,30 +63,13 @@ function createInternalWalkRulesTest(name, test, scope, walker) { function createInternalTranslateTest(name, test, scope) { it(name, function() { var ast = csso.parse(test.source, scope, true); - var internalAst = gonzalesToInternal(ast); // strings should be equal - assert.equal(internalTranslate(internalAst), test.translate); + assert.equal(internalTranslate(ast), 'translate' in test ? test.translate : test.source); }); } -describe('internal AST', function() { - describe('transform gonzales->internal', function() { - forEachTest(createGonzalesToInternalTest); - }); - - describe('transform internal->gonzales', function() { - forEachTest(createInternalToGonzalesTest); - - it('should throw error on wrong ast node', function() { - assert.throws(function() { - internalToGonzales({ - type: 'xxx' - }); - }, /Unknown node type/); - }); - }); - +describe('AST', function() { describe('walk all', function() { forEachTest(createInternalWalkAllTest); }); diff --git a/test/common.js b/test/common.js index bd76e6de..0c07d3d9 100644 --- a/test/common.js +++ b/test/common.js @@ -1,6 +1,7 @@ var fs = require('fs'); var assert = require('assert'); var csso = require('../lib/index.js'); +var stringify = require('./helpers/stringify.js'); function normalize(str) { return str.replace(/\n|\r\n?|\f/g, '\n'); @@ -32,22 +33,43 @@ describe('csso', function() { function visit(withInfo) { var visitedTypes = {}; - csso.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet', withInfo), function(node) { - visitedTypes[node[withInfo ? 1 : 0]] = true; + csso.internal.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet', withInfo), function(node) { + visitedTypes[node.type] = true; }, withInfo); return Object.keys(visitedTypes).sort(); } - var shouldVisitTypes = ['stylesheet', 'atruler', 'atkeyword', 'ident', 'atrulerq', 's', 'braces', 'operator', 'dimension', 'number', 'atrulers', 'ruleset', 'selector', 'simpleselector', 'clazz', 'nthselector', 'nth', 'block', 'declaration', 'property', 'value', 'funktion', 'functionBody', 'percentage', 'decldelim', 'unary'].sort(); + var shouldVisitTypes = [ + 'Argument', + 'Atrule', + 'AtruleExpression', + 'Block', + 'Braces', + 'Class', + 'Declaration', + 'Dimension', + 'Function', + 'FunctionalPseudo', + 'Identifier', + 'Nth', + 'Operator', + 'Percentage', + 'Property', + 'Ruleset', + 'Selector', + 'SimpleSelector', + 'Space', + 'StyleSheet', + 'Value' + ]; - assert.deepEqual(visit(), shouldVisitTypes, 'w/o info'); - assert.deepEqual(visit(true), shouldVisitTypes, 'with info'); + assert.deepEqual(visit(), shouldVisitTypes); }); - it('strigify', function() { + it('JSON.strigify()', function() { assert.equal( - csso.stringify(csso.parse('.a\n{\rcolor:\r\nred}', 'stylesheet', true)), + stringify(csso.parse('.a\n{\rcolor:\r\nred}', 'stylesheet', true), true), normalize(fs.readFileSync(__dirname + '/fixture/stringify.txt', 'utf-8').trim()) ); }); diff --git a/test/compress.js b/test/compress.js index 28f55ade..b1b3556c 100644 --- a/test/compress.js +++ b/test/compress.js @@ -1,16 +1,14 @@ var path = require('path'); var assert = require('assert'); var csso = require('../lib/index.js'); -var internalToGonzales = require('../lib/compressor/ast/internalToGonzales.js'); var internalTranslate = require('../lib/compressor/ast/translate.js'); -var gonzalesTranslate = require('../lib/utils/translate.js'); var tests = require('./fixture/compress'); function normalize(str) { return str.replace(/\n|\r\n?|\f/g, '\n'); } -function createCompressTest(name, test) { +function createMinifyTest(name, test) { var testFn = function() { var compressed = csso.minify(test.source); @@ -24,28 +22,32 @@ function createCompressTest(name, test) { } } -function createAfterCompressionTest(name, test) { - it(name, function() { +function createCompressTest(name, test) { + var testFn = function() { var ast = csso.parse(test.source, 'stylesheet', true); - var compressed = csso.compress(ast, { outputAst: 'internal' }); - var gonzalesAst = internalToGonzales(compressed); - var css = internalTranslate(compressed); + var compressedAst = csso.compress(ast); + var css = internalTranslate(compressedAst); - assert.equal(gonzalesTranslate(gonzalesAst, true), css, 'CSS should be equal'); - assert.equal(JSON.stringify(csso.cleanInfo(gonzalesAst)), JSON.stringify(csso.parse(css)), 'AST should be equal'); - }); + assert.equal(normalize(css), normalize(test.compressed)); + }; + + if (path.basename(name)[0] === '_') { + it.skip(name, testFn); + } else { + it(name, testFn); + } }; describe('compress', function() { describe('by csso.minify()', function() { for (var name in tests) { - createCompressTest(name, tests[name]); + createMinifyTest(name, tests[name]); } }); describe('step by step', function() { for (var name in tests) { - createAfterCompressionTest(name, tests[name]); + createCompressTest(name, tests[name]); } }); @@ -137,24 +139,6 @@ describe('compress', function() { }); it('should not fail if no ast passed', function() { - assert.equal(gonzalesTranslate(csso.compress(), true), ''); - }); - - it('should return gonzales AST by default', function() { - var ast = csso.parse('.foo{color:#FF0000}'); - - assert.equal(gonzalesTranslate(csso.compress(ast), true), '.foo{color:red}'); - }); - - it('should return gonzales AST when outputAst is `gonzales`', function() { - var ast = csso.parse('.foo{color:#FF0000}'); - - assert.equal(gonzalesTranslate(csso.compress(ast, { outputAst: 'gonzales' }), true), '.foo{color:red}'); - }); - - it('should return internal when outputAst is not undefined or `gonzales`', function() { - var ast = csso.parse('.foo{color:#FF0000}'); - - assert.equal(internalTranslate(csso.compress(ast, { outputAst: 'internal' })), '.foo{color:red}'); + assert.equal(internalTranslate(csso.compress(), true), ''); }); }); diff --git a/test/fixture/internal/index.js b/test/fixture/internal/index.js deleted file mode 100644 index a9ca3cee..00000000 --- a/test/fixture/internal/index.js +++ /dev/null @@ -1,35 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var JsonLocator = require('../../helpers/JsonLocator.js'); - -function forEachTest(factory) { - for (var filename in testFiles) { - var file = testFiles[filename]; - - for (var key in file.tests) { - factory(file.tests[key].name, file.tests[key], file.scope); - } - }; -} - -var testFiles = fs.readdirSync(__dirname).reduce(function(result, rule) { - var filename = path.join(__dirname, rule); - var tests = require(filename); - var locator = new JsonLocator(filename); - - for (var key in tests) { - tests[key].name = locator.get(key); - } - - result[filename] = { - scope: path.basename(rule, path.extname(rule)), - tests: tests - }; - - return result; -}, {}); - -module.exports = { - forEachTest: forEachTest, - tests: testFiles -}; diff --git a/test/fixture/internal/nth.json b/test/fixture/internal/nth.json deleted file mode 100644 index c4b9fed4..00000000 --- a/test/fixture/internal/nth.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "nth.0": { - "source": "10", - "translate": "10", - "ast": { - "type": "Nth", - "value": "10" - } - }, - "nth.1": { - "source": "2n", - "translate": "2n", - "ast": { - "type": "Nth", - "value": "2n" - } - }, - "nth.2": { - "source": "odd", - "translate": "odd", - "ast": { - "type": "Nth", - "value": "odd" - } - }, - "nth.3": { - "source": "even", - "translate": "even", - "ast": { - "type": "Nth", - "value": "even" - } - }, - "nth.4": { - "source": "n", - "translate": "n", - "ast": { - "type": "Nth", - "value": "n" - } - } -} diff --git a/test/fixture/internal/vhash.json b/test/fixture/internal/vhash.json deleted file mode 100644 index 97a17340..00000000 --- a/test/fixture/internal/vhash.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "vhash.0": { - "source": "#100", - "translate": "#100", - "ast": { - "type": "Hash", - "value": "100" - } - }, - "vhash.1": { - "source": "#id", - "translate": "#id", - "ast": { - "type": "Hash", - "value": "id" - } - } -} diff --git a/test/fixture/parse/atkeyword.json b/test/fixture/parse/atkeyword.json deleted file mode 100644 index 89f65756..00000000 --- a/test/fixture/parse/atkeyword.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "atkeyword.0": { - "source": "@import", - "ast": [ - "atkeyword", - ["ident", "import"] - ] - }, - "atkeyword.1": { - "source": "@font-face", - "ast": [ - "atkeyword", - ["ident", "font-face"] - ] - } -} \ No newline at end of file diff --git a/test/fixture/internal/atruleb.json b/test/fixture/parse/atrule/block.json similarity index 100% rename from test/fixture/internal/atruleb.json rename to test/fixture/parse/atrule/block.json diff --git a/test/fixture/internal/atrules.json b/test/fixture/parse/atrule/simple.json similarity index 78% rename from test/fixture/internal/atrules.json rename to test/fixture/parse/atrule/simple.json index 2852be3c..e9b95904 100644 --- a/test/fixture/internal/atrules.json +++ b/test/fixture/parse/atrule/simple.json @@ -55,6 +55,27 @@ "block": null } }, + "atrules.3": { + "source": "@import url(http://example.com)", + "translate": "@import url(http://example.com);", + "ast": { + "type": "Atrule", + "name": "import", + "expression": { + "type": "AtruleExpression", + "sequence": [ + { + "type": "Url", + "value": { + "type": "Raw", + "value": "http://example.com" + } + } + ] + }, + "block": null + } + }, "atrules.c.0": { "source": "@test/*test*/;", "translate": "@test;", @@ -130,5 +151,18 @@ }, "block": null } + }, + "atkeyword.1": { + "source": "@font-face", + "translate": "@font-face;", + "ast": { + "type": "Atrule", + "name": "font-face", + "expression": { + "type": "AtruleExpression", + "sequence": [] + }, + "block": null + } } } diff --git a/test/fixture/internal/atruler.json b/test/fixture/parse/atrule/stylesheet.json similarity index 80% rename from test/fixture/internal/atruler.json rename to test/fixture/parse/atrule/stylesheet.json index 03c26b4b..c284b026 100644 --- a/test/fixture/internal/atruler.json +++ b/test/fixture/parse/atrule/stylesheet.json @@ -124,6 +124,100 @@ } }, "atruler.2": { + "source": "@media screen and (max-width: 300px) {s{p:v}}", + "translate": "@media screen and (max-width: 300px){s{p:v}}", + "ast": { + "type": "Atrule", + "name": "media", + "expression": { + "type": "AtruleExpression", + "sequence": [ + { + "type": "Identifier", + "name": "screen" + }, + { + "type": "Space" + }, + { + "type": "Identifier", + "name": "and" + }, + { + "type": "Space" + }, + { + "type": "Braces", + "open": "(", + "close": ")", + "sequence": [ + { + "type": "Identifier", + "name": "max-width" + }, + { + "type": "Operator", + "value": ":" + }, + { + "type": "Space" + }, + { + "type": "Dimension", + "value": "300", + "unit": "px" + } + ] + } + ] + }, + "block": { + "type": "StyleSheet", + "rules": [ + { + "type": "Ruleset", + "selector": { + "type": "Selector", + "selectors": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "s" + } + ] + } + ] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "p" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "v" + } + ] + } + } + ] + } + } + ] + } + } + }, + "atruler.3": { "source": "@media x, y f(1+2) {s{p:v}}", "translate": "@media x, y f(1+2){s{p:v}}", "ast": { @@ -173,7 +267,8 @@ } ] } - ] + ], + "id": null }, "block": { "type": "StyleSheet", @@ -190,6 +285,104 @@ "type": "Identifier", "name": "s" } + ], + "id": null, + "compareMarker": null + } + ] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "p" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "v" + } + ] + } + } + ] + } + } + ] + } + } + }, + "atruler.4": { + "source": "@media { .a { p: v; } .b { p: v } }", + "translate": "@media{.a{p:v}.b{p:v}}", + "ast": { + "type": "Atrule", + "name": "media", + "expression": { + "type": "AtruleExpression", + "sequence": [], + "id": null + }, + "block": { + "type": "StyleSheet", + "rules": [ + { + "type": "Ruleset", + "selector": { + "type": "Selector", + "selectors": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "a" + } + ] + } + ] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "p" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "v" + } + ] + } + } + ] + } + }, + { + "type": "Ruleset", + "selector": { + "type": "Selector", + "selectors": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "b" + } ] } ] diff --git a/test/fixture/parse/atruleb.json b/test/fixture/parse/atruleb.json deleted file mode 100644 index a82a59e9..00000000 --- a/test/fixture/parse/atruleb.json +++ /dev/null @@ -1,314 +0,0 @@ -{ - "atruleb.0": { - "source": "@test{p:v}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - }, - "atruleb.1": { - "source": "@test x y {p:v}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["ident", "x"], - ["s", " "], - ["ident", "y"], - ["s", " "], - [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - }, - "atruleb.2": { - "source": "@test x, y x(1+2) {p:v}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["ident", "x"], - ["operator", ","], - ["s", " "], - ["ident", "y"], - ["s", " "], - [ - "funktion", - ["ident", "x"], - [ - "functionBody", - ["number", "1"], - ["unary", "+"], - ["number", "2"] - ] - ], - ["s", " "], - [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - }, - "": { - "source": "@page :left {p:v}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "page"] - ], - ["s", " "], - ["pseudoc", - ["ident", "left"] - ], - ["s", " "], - [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - }, - "atruleb.c.0": { - "source": "@test/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["comment", "test"], - [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ] - }, - "atruleb.c.1": { - "source": "@test/*test*/x/*test*/y/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["ident", "y"], - ["comment", "test"], - [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ] - }, - "atruleb.c.2": { - "source": "@test/*test*/x/*test*/,/*test*/y/*test*/x(/*test*/1/*test*/+/*test*/2/*test*/)/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["operator", ","], - ["comment", "test"], - ["ident", "y"], - ["comment", "test"], - [ - "funktion", - ["ident", "x"], - [ - "functionBody", - ["comment", "test"], - ["number", "1"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["number", "2"], - ["comment", "test"] - ] - ], - ["comment", "test"], - [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ] - }, - "atruleb.s.0": { - "source": "@test { p : v }", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ] - }, - "atruleb.s.1": { - "source": "@test x y { p : v }", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["ident", "x"], - ["s", " "], - ["ident", "y"], - ["s", " "], - [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ] - }, - "atruleb.s.2": { - "source": "@test x , y x( 1 + 2 ) { p : v }", - "ast": [ - "atruleb", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["ident", "x"], - ["s", " "], - ["operator", ","], - ["s", " "], - ["ident", "y"], - ["s", " "], - [ - "funktion", - ["ident", "x"], - [ - "functionBody", - ["s", " "], - ["number", "1"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["number", "2"], - ["s", " "] - ] - ], - ["s", " "], - [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ] - } -} diff --git a/test/fixture/parse/atruler.json b/test/fixture/parse/atruler.json deleted file mode 100644 index 68f64b22..00000000 --- a/test/fixture/parse/atruler.json +++ /dev/null @@ -1,600 +0,0 @@ -{ - "atruler.0": { - "source": "@media {s{p:v}}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "] - ], [ - "atrulers", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - ] - ] - }, - "atruler.1": { - "source": "@media x y {s{p:v}}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["ident", "y"], - ["s", " "] - ], [ - "atrulers", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - ] - ] - }, - "atruler.2": { - "source": "@media screen and (max-width: 300px) {s{p:v}}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "screen"], - ["s", " "], - ["ident", "and"], - ["s", " "], - [ - "braces", "(", ")", - ["ident", "max-width"], - ["operator", ":"], - ["s", " "], - [ - "dimension", - ["number", "300"], - ["ident", "px"] - ] - ], - ["s", " "] - ], [ - "atrulers", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - ] - ] - }, - "atruler.3": { - "source": "@media x, y f(1+2) {s{p:v}}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "x"], - ["operator", ","], - ["s", " "], - ["ident", "y"], - ["s", " "], - [ - "funktion", - ["ident", "f"], - [ - "functionBody", - ["number", "1"], - ["unary", "+"], - ["number", "2"] - ] - ], - ["s", " "] - ], [ - "atrulers", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - ] - ] - }, - "atruler.4": { - "source": "@media { .a { p: v; } .b { p: v } }", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "] - ], [ - "atrulers", - ["s", " "], - [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "a"] - ], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["s", " "], - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", " "] - ] - ], - ["s", " "], - [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "b"] - ], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ], - ["s", " "] - ] - ] - }, - "atruler.c.0": { - "source": "@media/*test*/{/*test*/s/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}/*test*/}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["comment", "test"] - ], [ - "atrulers", - ["comment", "test"], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ], - ["comment", "test"] - ] - ] - }, - "atruler.c.1": { - "source": "@media/*test*/x/*test*/y/*test*/{/*test*/s/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}/*test*/}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["ident", "y"], - ["comment", "test"] - ], [ - "atrulers", - ["comment", "test"], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ], - ["comment", "test"] - ] - ] - }, - "atruler.c.2": { - "source": "@media/*test*/x/*test*/,/*test*/y/*test*/ f(/*test*/1/*test*/+/*test*/2/*test*/)/*test*/{/*test*/s/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}/*test*/}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["operator", ","], - ["comment", "test"], - ["ident", "y"], - ["comment", "test"], - ["s", " "], - [ - "funktion", - ["ident", "f"], - [ - "functionBody", - ["comment", "test"], - ["number", "1"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["number", "2"], - ["comment", "test"] - ] - ], - ["comment", "test"] - ], [ - "atrulers", - ["comment", "test"], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ], - ["comment", "test"] - ] - ] - }, - "atruler.s.0": { - "source": "@media { s { p : v } }", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "] - ], [ - "atrulers", - ["s", " "], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ], - ["s", " "] - ] - ] - }, - "atruler.s.1": { - "source": "@media x y { s { p : v } }", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["ident", "y"], - ["s", " "] - ], [ - "atrulers", - ["s", " "], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ], - ["s", " "] - ] - ] - }, - "atruler.s.2": { - "source": "@media x , y f( 1 + 2 ) { s { p : v } }", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["operator", ","], - ["s", " "], - ["ident", "y"], - ["s", " "], - [ - "funktion", - ["ident", "f"], - [ - "functionBody", - ["s", " "], - ["number", "1"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["number", "2"], - ["s", " "] - ] - ], - ["s", " "] - ], [ - "atrulers", - ["s", " "], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ], - ["s", " "] - ] - ] - }, - "webkit.keyfraymes.0": { - "source": "@-webkit-keyframes pulsate {0% {opacity: .5}50% {opacity: 1}100% {opacity: .5}}", - "ast": [ - "atruler", [ - "atkeyword", - ["ident", "-webkit-keyframes"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "pulsate"], - ["s", " "] - ], [ - "atrulers", [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "percentage", - ["number", "0"] - ], - ["s", " "] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "opacity"] - ], [ - "value", - ["s", " "], - ["number", ".5"] - ] - ] - ] - ], [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "percentage", - ["number", "50"] - ], - ["s", " "] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "opacity"] - ], [ - "value", - ["s", " "], - ["number", "1"] - ] - ] - ] - ], [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "percentage", - ["number", "100"] - ], - ["s", " "] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "opacity"] - ], [ - "value", - ["s", " "], - ["number", ".5"] - ] - ] - ] - ] - ] - ] - } -} diff --git a/test/fixture/parse/atrules.json b/test/fixture/parse/atrules.json deleted file mode 100644 index 6e056599..00000000 --- a/test/fixture/parse/atrules.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "atrules.0": { - "source": "@test;", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ] - ] - }, - "atrules.1": { - "source": "@test x y;", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["ident", "x"], - ["s", " "], - ["ident", "y"] - ] - }, - "atrules.2": { - "source": "@test \"blah\";", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["string", "\"blah\""] - ] - }, - "atrules.3": { - "source": "@import url(http://example.com)", - "restoredSource": "@import url(http://example.com);", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "import"] - ], - ["s", " "], - [ - "uri", - ["raw", "http://example.com"] - ] - ] - }, - "atrules.c.0": { - "source": "@test/*test*/;", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ], - ["comment", "test"] - ] - }, - "atrules.c.1": { - "source": "@test/*test*/x/*test*/y;", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ], - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["ident", "y"] - ] - }, - "atrules.s.0": { - "source": "@test ;", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "] - ] - }, - "atrules.s.1": { - "source": "@test x y;", - "ast": [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ], - ["s", " "], - ["ident", "x"], - ["s", " "], - ["ident", "y"] - ] - } -} diff --git a/test/fixture/parse/attrib.json b/test/fixture/parse/attrib.json deleted file mode 100644 index 7cea57b0..00000000 --- a/test/fixture/parse/attrib.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "attrib.0": { - "source": "[a=b]", - "ast": [ - "attrib", - ["ident", "a"], - ["attrselector", "="], - ["ident", "b"] - ] - }, - "attrib.1": { - "source": "[a='b']", - "ast": [ - "attrib", - ["ident", "a"], - ["attrselector", "="], - ["string", "'b'"] - ] - }, - "attrib with flags": { - "source": "[a='b'i]", - "ast": [ - "attrib", - ["ident", "a"], - ["attrselector", "="], - ["string", "'b'"], - ["attribFlags", "i"] - ] - }, - "attrib.2": { - "source": "[b]", - "ast": [ - "attrib", - ["ident", "b"] - ] - }, - "attrib.3": { - "source": "[ng:cloak]", - "ast": [ - "attrib", - ["ident", "ng:cloak"] - ] - }, - "attrib.4": { - "source": "[ng:cloak=x]", - "ast": [ - "attrib", - ["ident", "ng:cloak"], - ["attrselector", "="], - ["ident", "x"] - ] - }, - "attrib.c.0": { - "source": "[/*test*/a/*test*/=/*test*/b/*test*/]", - "ast": [ - "attrib", - ["comment", "test"], - ["ident", "a"], - ["comment", "test"], - ["attrselector", "="], - ["comment", "test"], - ["ident", "b"], - ["comment", "test"] - ] - }, - "attrib.c.1": { - "source": "[/*test*/a/*test*/=/*test*/'b'/*test*/]", - "ast": [ - "attrib", - ["comment", "test"], - ["ident", "a"], - ["comment", "test"], - ["attrselector", "="], - ["comment", "test"], - ["string", "'b'"], - ["comment", "test"] - ] - }, - "attrib.s.0": { - "source": "[ a = b ]", - "ast": [ - "attrib", - ["s", " "], - ["ident", "a"], - ["s", " "], - ["attrselector", "="], - ["s", " "], - ["ident", "b"], - ["s", " "] - ] - }, - "attrib.s.1": { - "source": "[ a = 'b' ]", - "ast": [ - "attrib", - ["s", " "], - ["ident", "a"], - ["s", " "], - ["attrselector", "="], - ["s", " "], - ["string", "'b'"], - ["s", " "] - ] - }, - "attrib with flags and spaces": { - "source": "[ a = 'b' i ]", - "ast": [ - "attrib", - ["s", " "], - ["ident", "a"], - ["s", " "], - ["attrselector", "="], - ["s", " "], - ["string", "'b'"], - ["s", " "], - ["attribFlags", "i"], - ["s", " "] - ] - }, - "attrib with flags and comments": { - "source": "[/*1*/a/*2*/=/*3*/'b'/*4*/i/*5*/]", - "ast": [ - "attrib", - ["comment", "1"], - ["ident", "a"], - ["comment", "2"], - ["attrselector", "="], - ["comment", "3"], - ["string", "'b'"], - ["comment", "4"], - ["attribFlags", "i"], - ["comment", "5"] - ] - }, - "namespace": { - "source": "[xlink|href=\"#test\"]", - "ast": [ - "attrib", - ["ident", "xlink|href"], - ["attrselector", "="], - ["string", "\"#test\""] - ] - }, - "namespace unversal": { - "source": "[*|href=\"#test\"]", - "ast": [ - "attrib", - ["ident", "*|href"], - ["attrselector", "="], - ["string", "\"#test\""] - ] - }, - "namespace w/o attrselector": { - "source": "[xlink|href]", - "ast": [ - "attrib", - ["ident", "xlink|href"] - ] - }, - "namespace unversal w/o attrselector": { - "source": "[*|href]", - "ast": [ - "attrib", - ["ident", "*|href"] - ] - }, - "attrib with dashmatch": { - "source": "[a|='b']", - "ast": [ - "attrib", - ["ident", "a"], - ["attrselector", "|="], - ["string", "'b'"] - ] - }, - "attrib with namespace and dashmatch": { - "source": "[a|b|='c']", - "ast": [ - "attrib", - ["ident", "a|b"], - ["attrselector", "|="], - ["string", "'c'"] - ] - }, - "attrib with dashmatch and spaces": { - "source": "[ a |= 'b' ]", - "ast": [ - "attrib", - ["s", " "], - ["ident", "a"], - ["s", " "], - ["attrselector", "|="], - ["s", " "], - ["string", "'b'"], - ["s", " "] - ] - }, - "attrib with namespace, dashmatch and spaces": { - "source": "[ a|b |= 'c' ]", - "ast": [ - "attrib", - ["s", " "], - ["ident", "a|b"], - ["s", " "], - ["attrselector", "|="], - ["s", " "], - ["string", "'c'"], - ["s", " "] - ] - } -} diff --git a/test/fixture/parse/attrselector.json b/test/fixture/parse/attrselector.json deleted file mode 100644 index 92814c6e..00000000 --- a/test/fixture/parse/attrselector.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "attrselector.0": { - "source": "=", - "ast": ["attrselector", "="] - }, - "attrselector.1": { - "source": "~=", - "ast": ["attrselector", "~="] - }, - "attrselector.2": { - "source": "^=", - "ast": ["attrselector", "^="] - }, - "attrselector.3": { - "source": "$=", - "ast": ["attrselector", "$="] - }, - "attrselector.4": { - "source": "*=", - "ast": ["attrselector", "*="] - } -} \ No newline at end of file diff --git a/test/fixture/parse/block.json b/test/fixture/parse/block.json deleted file mode 100644 index fb65389f..00000000 --- a/test/fixture/parse/block.json +++ /dev/null @@ -1,228 +0,0 @@ -{ - "block.0": { - "source": "{}", - "ast": ["block"] - }, - "block.1": { - "source": "{;}", - "ast": [ - "block", - ["decldelim"] - ] - }, - "block.2": { - "source": "{p:v}", - "ast": [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - }, - "block.3": { - "source": "{p:v;}", - "ast": [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ], - ["decldelim"] - ] - }, - "block.4": { - "source": "{p0:v0;p1:v1}", - "ast": [ - "block", [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["ident", "v0"] - ] - ], - ["decldelim"], - [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["ident", "v1"] - ] - ] - ] - }, - "block.c.0": { - "source": "{/*test*/}", - "ast": [ - "block", - ["comment", "test"] - ] - }, - "block.c.1": { - "source": "{/*test*/;/*test*/}", - "ast": [ - "block", - ["comment", "test"], - ["decldelim"], - ["comment", "test"] - ] - }, - "block.c.2": { - "source": "{/*test*/p:v/*test*/}", - "ast": [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - }, - "block.c.3": { - "source": "{/*test*/p:v/*test*/;/*test*/}", - "ast": [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"], - ["comment", "test"] - ] - ], - ["decldelim"], - ["comment", "test"] - ] - }, - "block.c.4": { - "source": "{/*test*/p0:v0/*test*/;/*test*/p1:v1/*test*/}", - "ast": [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["ident", "v0"], - ["comment", "test"] - ] - ], - ["decldelim"], - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["ident", "v1"], - ["comment", "test"] - ] - ] - ] - }, - "block.s.0": { - "source": "{ }", - "ast": [ - "block", - ["s", " "] - ] - }, - "block.s.1": { - "source": "{ ; }", - "ast": [ - "block", - ["s", " "], - ["decldelim"], - ["s", " "] - ] - }, - "block.s.2": { - "source": "{ p:v }", - "ast": [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"], - ["s", " "] - ] - ] - ] - }, - "block.s.3": { - "source": "{ p:v ; }", - "ast": [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"], - ["s", " "] - ] - ], - ["decldelim"], - ["s", " "] - ] - }, - "block.s.4": { - "source": "{ p0:v0 ; p1:v1 }", - "ast": [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["ident", "v0"], - ["s", " "] - ] - ], - ["decldelim"], - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["ident", "v1"], - ["s", " "] - ] - ] - ] - } -} \ No newline at end of file diff --git a/test/fixture/internal/block.json b/test/fixture/parse/block/Block.json similarity index 94% rename from test/fixture/internal/block.json rename to test/fixture/parse/block/Block.json index b9ce878c..73cc28a2 100644 --- a/test/fixture/internal/block.json +++ b/test/fixture/parse/block/Block.json @@ -1,6 +1,6 @@ { "block.0": { - "source": "{}", + "source": "", "translate": "", "ast": { "type": "Block", @@ -8,7 +8,7 @@ } }, "block.1": { - "source": "{;}", + "source": ";", "translate": "", "ast": { "type": "Block", @@ -16,7 +16,7 @@ } }, "block.2": { - "source": "{p:v}", + "source": "p:v", "translate": "p:v", "ast": { "type": "Block", @@ -42,7 +42,7 @@ } }, "block.3": { - "source": "{p:v;}", + "source": "p:v;", "translate": "p:v", "ast": { "type": "Block", @@ -68,7 +68,7 @@ } }, "block.4": { - "source": "{p0:v0;p1:v1}", + "source": "p0:v0;p1:v1", "translate": "p0:v0;p1:v1", "ast": { "type": "Block", @@ -111,7 +111,7 @@ } }, "block.c.0": { - "source": "{/*test*/}", + "source": "/*test*/", "translate": "", "ast": { "type": "Block", @@ -119,7 +119,7 @@ } }, "block.c.1": { - "source": "{/*test*/;/*test*/}", + "source": "/*test*/;/*test*/", "translate": "", "ast": { "type": "Block", @@ -127,7 +127,7 @@ } }, "block.c.2": { - "source": "{/*test*/p:v/*test*/}", + "source": "/*test*/p:v/*test*/", "translate": "p:v", "ast": { "type": "Block", @@ -153,7 +153,7 @@ } }, "block.c.3": { - "source": "{/*test*/p:v/*test*/;/*test*/}", + "source": "/*test*/p:v/*test*/;/*test*/", "translate": "p:v", "ast": { "type": "Block", @@ -179,7 +179,7 @@ } }, "block.c.4": { - "source": "{/*test*/p0:v0/*test*/;/*test*/p1:v1/*test*/}", + "source": "/*test*/p0:v0/*test*/;/*test*/p1:v1/*test*/", "translate": "p0:v0;p1:v1", "ast": { "type": "Block", @@ -222,7 +222,7 @@ } }, "block.s.0": { - "source": "{ }", + "source": " ", "translate": "", "ast": { "type": "Block", @@ -230,7 +230,7 @@ } }, "block.s.1": { - "source": "{ ; }", + "source": " ; ", "translate": "", "ast": { "type": "Block", @@ -238,7 +238,7 @@ } }, "block.s.2": { - "source": "{ p:v }", + "source": " p:v ", "translate": "p:v", "ast": { "type": "Block", @@ -264,7 +264,7 @@ } }, "block.s.3": { - "source": "{ p:v ; }", + "source": " p:v ; ", "translate": "p:v", "ast": { "type": "Block", @@ -290,7 +290,7 @@ } }, "block.s.4": { - "source": "{ p0:v0 ; p1:v1 }", + "source": " p0:v0 ; p1:v1 ", "translate": "p0:v0;p1:v1", "ast": { "type": "Block", diff --git a/test/fixture/parse/braces.json b/test/fixture/parse/braces.json deleted file mode 100644 index 1344c8af..00000000 --- a/test/fixture/parse/braces.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "braces.0": { - "source": "()", - "ast": ["braces", "(", ")"] - }, - "braces.1": { - "source": "(1)", - "ast": [ - "braces", "(", ")", - ["number", "1"] - ] - }, - "braces.2": { - "source": "(x)", - "ast": [ - "braces", "(", ")", - ["ident", "x"] - ] - }, - "braces.3": { - "source": "(x+1)", - "ast": [ - "braces", "(", ")", - ["ident", "x"], - ["unary", "+"], - ["number", "1"] - ] - }, - "braces.4": { - "source": "[]", - "ast": ["braces", "[", "]"] - }, - "braces.5": { - "source": "[1]", - "ast": [ - "braces", "[", "]", - ["number", "1"] - ] - }, - "braces.6": { - "source": "[x]", - "ast": [ - "braces", "[", "]", - ["ident", "x"] - ] - }, - "braces.7": { - "source": "[x+1]", - "ast": [ - "braces", "[", "]", - ["ident", "x"], - ["unary", "+"], - ["number", "1"] - ] - }, - "braces.8": { - "source": "(test:0)", - "ast": [ - "braces", "(", ")", - ["ident", "test"], - ["operator", ":"], - ["number", "0"] - ] - }, - "braces.c.0": { - "source": "(/*test*/)", - "ast": [ - "braces", "(", ")", - ["comment", "test"] - ] - }, - "braces.c.1": { - "source": "(/*test*/1/*test*/)", - "ast": [ - "braces", "(", ")", - ["comment", "test"], - ["number", "1"], - ["comment", "test"] - ] - }, - "braces.c.2": { - "source": "(/*test*/x/*test*/)", - "ast": [ - "braces", "(", ")", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"] - ] - }, - "braces.c.3": { - "source": "(/*test*/x/*test*/+/*test*/1/*test*/)", - "ast": [ - "braces", "(", ")", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["number", "1"], - ["comment", "test"] - ] - }, - "braces.c.4": { - "source": "[/*test*/]", - "ast": [ - "braces", "[", "]", - ["comment", "test"] - ] - }, - "braces.c.5": { - "source": "[/*test*/1/*test*/]", - "ast": [ - "braces", "[", "]", - ["comment", "test"], - ["number", "1"], - ["comment", "test"] - ] - }, - "braces.c.6": { - "source": "[/*test*/x/*test*/]", - "ast": [ - "braces", "[", "]", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"] - ] - }, - "braces.c.7": { - "source": "[/*test*/x/*test*/+/*test*/1/*test*/]", - "ast": [ - "braces", "[", "]", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["number", "1"], - ["comment", "test"] - ] - }, - "braces.s.0": { - "source": "( )", - "ast": [ - "braces", "(", ")", - ["s", " "] - ] - }, - "braces.s.1": { - "source": "( 1 )", - "ast": [ - "braces", "(", ")", - ["s", " "], - ["number", "1"], - ["s", " "] - ] - }, - "braces.s.2": { - "source": "( x )", - "ast": [ - "braces", "(", ")", - ["s", " "], - ["ident", "x"], - ["s", " "] - ] - }, - "braces.s.3": { - "source": "( x + 1 )", - "ast": [ - "braces", "(", ")", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["number", "1"], - ["s", " "] - ] - }, - "braces.s.4": { - "source": "[ ]", - "ast": [ - "braces", "[", "]", - ["s", " "] - ] - }, - "braces.s.5": { - "source": "[ 1 ]", - "ast": [ - "braces", "[", "]", - ["s", " "], - ["number", "1"], - ["s", " "] - ] - }, - "braces.s.6": { - "source": "[ x ]", - "ast": [ - "braces", "[", "]", - ["s", " "], - ["ident", "x"], - ["s", " "] - ] - }, - "braces.s.7": { - "source": "[ x + 1 ]", - "ast": [ - "braces", "[", "]", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["number", "1"], - ["s", " "] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/clazz.json b/test/fixture/parse/clazz.json deleted file mode 100644 index 6cafc261..00000000 --- a/test/fixture/parse/clazz.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "clazz.0": { - "source": ".abc", - "ast": [ - "clazz", - ["ident", "abc"] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/combinator.json b/test/fixture/parse/combinator.json deleted file mode 100644 index 3e06e454..00000000 --- a/test/fixture/parse/combinator.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "combinator.0": { - "source": "+", - "ast": ["combinator", "+"] - }, - "combinator.1": { - "source": ">", - "ast": ["combinator", ">"] - }, - "combinator.2": { - "source": "~", - "ast": ["combinator", "~"] - }, - "combinator.3": { - "source": "/deep/", - "ast": ["combinator", "/deep/"] - } -} \ No newline at end of file diff --git a/test/fixture/parse/comment.json b/test/fixture/parse/comment.json deleted file mode 100644 index b368ff67..00000000 --- a/test/fixture/parse/comment.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "comment.0": { - "source": "/*test*/", - "ast": ["comment", "test"] - }, - "comment.1": { - "source": "/*te\nst*/", - "ast": ["comment", "te\nst"] - }, - "comment.2": { - "source": "/*te\rst*/", - "ast": ["comment", "te\rst"] - }, - "comment.3": { - "source": "/*te\r\nst*/", - "ast": ["comment", "te\r\nst"] - }, - "comment.4": { - "source": "/*te**st*/", - "ast": ["comment", "te**st"] - } -} diff --git a/test/fixture/parse/declaration.json b/test/fixture/parse/declaration.json deleted file mode 100644 index dd85f0a4..00000000 --- a/test/fixture/parse/declaration.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "declaration.0": { - "source": "property:value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"] - ], [ - "value", - ["ident", "value"] - ] - ] - }, - "declaration.1": { - "source": "-my-property:value", - "ast": [ - "declaration", [ - "property", - ["ident", "-my-property"] - ], [ - "value", - ["ident", "value"] - ] - ] - }, - "declaration.2": { - "source": "//hack:value", - "ast": [ - "declaration", [ - "property", - ["ident", "//hack"] - ], [ - "value", - ["ident", "value"] - ] - ] - }, - "declaration.c.0": { - "source": "property/*test*/:value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"], - ["comment", "test"] - ], [ - "value", - ["ident", "value"] - ] - ] - }, - "declaration.c.1": { - "source": "property:/*test*/value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"] - ], [ - "value", - ["comment", "test"], - ["ident", "value"] - ] - ] - }, - "declaration.c.2": { - "source": "property/*test*/:/*test*/value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "value"] - ] - ] - }, - "declaration.c.3": { - "source": "property /*test*/ : /*test*/ value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"], - ["s", " "], - ["comment", "test"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["comment", "test"], - ["s", " "], - ["ident", "value"] - ] - ] - }, - "declaration.s.0": { - "source": "property :value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"], - ["s", " "] - ], [ - "value", - ["ident", "value"] - ] - ] - }, - "declaration.s.1": { - "source": "property: value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"] - ], [ - "value", - ["s", " "], - ["ident", "value"] - ] - ] - }, - "declaration.s.2": { - "source": "property : value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "value"] - ] - ] - }, - "declaration.s.3": { - "source": "property : value", - "ast": [ - "declaration", [ - "property", - ["ident", "property"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "value"] - ] - ] - } -} diff --git a/test/fixture/internal/declaration.json b/test/fixture/parse/declaration/Declaration.json similarity index 90% rename from test/fixture/internal/declaration.json rename to test/fixture/parse/declaration/Declaration.json index 01489be7..20d166b2 100644 --- a/test/fixture/internal/declaration.json +++ b/test/fixture/parse/declaration/Declaration.json @@ -41,6 +41,27 @@ } } }, + "declaration property with hack": { + "source": "//property:value", + "translate": "//property:value", + "ast": { + "type": "Declaration", + "property": { + "type": "Property", + "name": "//property" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "value" + } + ] + } + } + }, "declaration.c.0": { "source": "property/*test*/:value", "translate": "property:value", diff --git a/test/fixture/internal/filter.json b/test/fixture/parse/declaration/filter.json similarity index 100% rename from test/fixture/internal/filter.json rename to test/fixture/parse/declaration/filter.json diff --git a/test/fixture/parse/dimension.json b/test/fixture/parse/dimension.json deleted file mode 100644 index 84ba5a64..00000000 --- a/test/fixture/parse/dimension.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "dimension.0": { - "source": "10px", - "ast": [ - "dimension", - ["number", "10"], - ["ident", "px"] - ] - }, - "dimension.1": { - "source": ".10px", - "ast": [ - "dimension", - ["number", ".10"], - ["ident", "px"] - ] - }, - "dimension.2": { - "source": "12.34px", - "ast": [ - "dimension", - ["number", "12.34"], - ["ident", "px"] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/filter.json b/test/fixture/parse/filter.json deleted file mode 100644 index 45e19226..00000000 --- a/test/fixture/parse/filter.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "filter.0": { - "source": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.1": { - "source": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='something')", - "ast": [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ], [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='something')"] - ] - ] - ] - }, - "filter.2": { - "source": "*filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "*filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.3": { - "source": "-filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "-filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.4": { - "source": "_filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "_filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.5": { - "source": "-ms-filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "-ms-filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.c.0": { - "source": "filter/*test*/:/*test*/progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "filter"], - ["comment", "test"] - ], [ - "filterv", [ - "progid", - ["comment", "test"], - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.c.1": { - "source": "filter/*test*/:/*test*/progid:DXImageTransform.Microsoft.AlphaImageLoader(/*)*/src='images/transparent-border.png'/*)*/,/*)*/sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "filter"], - ["comment", "test"] - ], [ - "filterv", [ - "progid", - ["comment", "test"], - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(/*)*/src='images/transparent-border.png'/*)*/,/*)*/sizingMethod='scale')"] - ] - ] - ] - }, - "filter.s.0": { - "source": "filter : progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')", - "ast": [ - "filter", [ - "property", - ["ident", "filter"], - ["s", " "] - ], [ - "filterv", [ - "progid", - ["s", " "], - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ] - }, - "filter.s.1": { - "source": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')\n progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='something')", - "ast": [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"], - ["s", "\n "] - ], [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='something')"] - ] - ] - ] - }, - "filter.s.2": { - "source": "filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='$start', endColorstr='$stop', GradientType=0)", - "ast": [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["s", " "], - ["raw", "progid: DXImageTransform.Microsoft.gradient(startColorstr='$start', endColorstr='$stop', GradientType=0)"] - ] - ] - ] - } -} diff --git a/test/fixture/parse/functionExpression.json b/test/fixture/parse/functionExpression.json deleted file mode 100644 index 0e457f84..00000000 --- a/test/fixture/parse/functionExpression.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "functionExpression.0": { - "source": "expression()", - "ast": ["functionExpression", ""] - }, - "functionExpression.1": { - "source": "expression(())", - "ast": ["functionExpression", "()"] - }, - "functionExpression.2": { - "source": "expression(expression())", - "ast": ["functionExpression", "expression()"] - }, - "functionExpression.3": { - "source": "expression(/*)*/)", - "ast": ["functionExpression", "/*)*/"] - }, - "functionExpression.4": { - "source": "expression(\")\")", - "ast": ["functionExpression", "\")\""] - }, - "functionExpression.5": { - "source": "expression(')')", - "ast": ["functionExpression", "')'"] - }, - "functionExpression.6": { - "source": "expression(()())", - "ast": ["functionExpression", "()()"] - }, - "functionExpression.7": { - "source": "expression(\n// )\n)", - "ast": ["functionExpression", "\n// )\n"] - } -} \ No newline at end of file diff --git a/test/fixture/parse/funktion.json b/test/fixture/parse/funktion.json deleted file mode 100644 index c3176185..00000000 --- a/test/fixture/parse/funktion.json +++ /dev/null @@ -1,308 +0,0 @@ -{ - "function.0": { - "source": "test()", - "ast": [ - "funktion", - ["ident", "test"], - ["functionBody"] - ] - }, - "function.1": { - "source": "test(n)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["ident", "n"] - ] - ] - }, - "function.2": { - "source": "test-test(n)", - "ast": [ - "funktion", - ["ident", "test-test"], - [ - "functionBody", - ["ident", "n"] - ] - ] - }, - "function.3": { - "source": "test(x+y)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["ident", "x"], - ["unary", "+"], - ["ident", "y"] - ] - ] - }, - "function.4": { - "source": "test(x,y)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["ident", "x"], - ["operator", ","], - ["ident", "y"] - ] - ] - }, - "function.5": { - "source": "test(10px,'test' test(x),89%)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", [ - "dimension", - ["number", "10"], - ["ident", "px"] - ], - ["operator", ","], - ["string", "'test'"], - ["s", " "], - [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["ident", "x"] - ] - ], - ["operator", ","], - [ - "percentage", - ["number", "89"] - ] - ] - ] - }, - "function.c.0": { - "source": "test(/*test*/)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["comment", "test"] - ] - ] - }, - "function.c.1": { - "source": "test(/*test*/n/*test*/)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["comment", "test"], - ["ident", "n"], - ["comment", "test"] - ] - ] - }, - "function.c.2": { - "source": "test-test(/*test*/n/*test*/)", - "ast": [ - "funktion", - ["ident", "test-test"], - [ - "functionBody", - ["comment", "test"], - ["ident", "n"], - ["comment", "test"] - ] - ] - }, - "function.c.3": { - "source": "test(/*test*/x/*test*/+/*test*/y/*test*/)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["ident", "y"], - ["comment", "test"] - ] - ] - }, - "function.c.4": { - "source": "test(/*test*/x/*test*/,/*test*/y/*test*/)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"], - ["operator", ","], - ["comment", "test"], - ["ident", "y"], - ["comment", "test"] - ] - ] - }, - "function.c.5": { - "source": "test(/*test*/10px/*test*/,/*test*/'test'/*test*/test(/*test*/x/*test*/)/*test*/,/*test*/89%/*test*/)", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["comment", "test"], - [ - "dimension", - ["number", "10"], - ["ident", "px"] - ], - ["comment", "test"], - ["operator", ","], - ["comment", "test"], - ["string", "'test'"], - ["comment", "test"], - [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["comment", "test"], - ["ident", "x"], - ["comment", "test"] - ] - ], - ["comment", "test"], - ["operator", ","], - ["comment", "test"], - [ - "percentage", - ["number", "89"] - ], - ["comment", "test"] - ] - ] - }, - "function.s.0": { - "source": "test( )", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["s", " "] - ] - ] - }, - "function.s.1": { - "source": "test( n )", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["s", " "], - ["ident", "n"], - ["s", " "] - ] - ] - }, - "function.s.2": { - "source": "test-test( n )", - "ast": [ - "funktion", - ["ident", "test-test"], - [ - "functionBody", - ["s", " "], - ["ident", "n"], - ["s", " "] - ] - ] - }, - "function.s.3": { - "source": "test( x + y )", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["ident", "y"], - ["s", " "] - ] - ] - }, - "function.s.4": { - "source": "test( x , y )", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["s", " "], - ["ident", "x"], - ["s", " "], - ["operator", ","], - ["s", " "], - ["ident", "y"], - ["s", " "] - ] - ] - }, - "function.s.5": { - "source": "test( 10px , 'test' test( x ) , 89% )", - "ast": [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["s", " "], - [ - "dimension", - ["number", "10"], - ["ident", "px"] - ], - ["s", " "], - ["operator", ","], - ["s", " "], - ["string", "'test'"], - ["s", " "], - [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["s", " "], - ["ident", "x"], - ["s", " "] - ] - ], - ["s", " "], - ["operator", ","], - ["s", " "], - [ - "percentage", - ["number", "89"] - ], - ["s", " "] - ] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/ident.json b/test/fixture/parse/ident.json deleted file mode 100644 index bebd7117..00000000 --- a/test/fixture/parse/ident.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "ident.0": { - "source": "x", - "ast": ["ident", "x"] - }, - "ident.2": { - "source": "-x", - "ast": ["ident", "-x"] - }, - "ident.3": { - "source": "-x-test", - "ast": ["ident", "-x-test"] - }, - "ident.4": { - "source": "x\\:y", - "ast": ["ident", "x\\:y"] - }, - "ident.6": { - "source": "_0", - "ast": ["ident", "_0"] - }, - "ident.7": { - "source": "\\1 ", - "ast": ["ident", "\\1 "] - }, - "ident.8": { - "source": "\\a ", - "ast": ["ident", "\\a "] - }, - "ident.9": { - "source": "\\d ", - "ast": ["ident", "\\d "] - }, - "ident.10": { - "source": "\\7f ", - "ast": ["ident", "\\7f "] - }, - "ident.11": { - "source": "\\30 ", - "ast": ["ident", "\\30 "] - }, - "ident.12": { - "source": "\\35 ", - "ast": ["ident", "\\35 "] - }, - "ident.13": { - "source": "\\39 ", - "ast": ["ident", "\\39 "] - }, - "ident.14": { - "source": "\\-", - "ast": ["ident", "\\-"] - }, - "ident.15": { - "source": "\\-\\30 ", - "ast": ["ident", "\\-\\30 "] - }, - "ident.16": { - "source": "\\-\\30 -", - "ast": ["ident", "\\-\\30 -"] - }, - "ident.17": { - "source": "\\30 123", - "ast": ["ident", "\\30 123"] - }, - "ident.18": { - "source": "\\--\\+\\a \\d \\30 asd", - "ast": ["ident", "\\--\\+\\a \\d \\30 asd"] - }, - "ident.19": { - "source": "\uFFFDasd", - "ast": ["ident", "\uFFFDasd"] - }, - "ident.20": { - "source": "asd\\30 \\a -asd", - "ast": ["ident", "asd\\30 \\a -asd"] - }, - "ident.21": { - "source": "\\20 123", - "ast": ["ident", "\\20 123"] - }, - "ident.22": { - "source": "\\123456 123", - "ast": ["ident", "\\123456 123"] - }, - "ident.23": { - "source": "\\123456zzz", - "ast": ["ident", "\\123456zzz"] - } -} diff --git a/test/fixture/parse/important.json b/test/fixture/parse/important.json deleted file mode 100644 index 3d0dd8c5..00000000 --- a/test/fixture/parse/important.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "important.0": { - "source": "!important", - "ast": ["important"] - }, - "important.1": { - "source": "!ImpoRtant", - "restoredSource": "!important", - "ast": ["important"] - }, - "important.2": { - "source": "!IMPORTANT", - "restoredSource": "!important", - "ast": ["important"] - }, - "important.c.0": { - "source": "!/*test*/important", - "ast": [ - "important", - ["comment", "test"] - ] - }, - "important.s.0": { - "source": "! important", - "ast": [ - "important", - ["s", " "] - ] - } -} diff --git a/test/fixture/parse/index.js b/test/fixture/parse/index.js index a9ca3cee..2228a307 100644 --- a/test/fixture/parse/index.js +++ b/test/fixture/parse/index.js @@ -1,6 +1,37 @@ var fs = require('fs'); var path = require('path'); var JsonLocator = require('../../helpers/JsonLocator.js'); +var wrapper = { + ruleset: function(ast) { + return { + type: 'Ruleset', + selector: { + type: 'Selector', + selectors: [] + }, + block: ast + }; + }, + simpleSelector: function(ast) { + return { + type: 'SimpleSelector', + sequence: [ast] + }; + }, + stylesheet: function(ast) { + return { + type: 'StyleSheet', + rules: [ast] + }; + }, + value: function(ast) { + return { + type: 'Value', + important: false, + sequence: [ast] + }; + } +}; function forEachTest(factory) { for (var filename in testFiles) { @@ -12,19 +43,28 @@ function forEachTest(factory) { }; } -var testFiles = fs.readdirSync(__dirname).reduce(function(result, rule) { - var filename = path.join(__dirname, rule); - var tests = require(filename); - var locator = new JsonLocator(filename); +var testFiles = fs.readdirSync(__dirname).reduce(function(result, scope) { + var dir = path.join(__dirname, scope); - for (var key in tests) { - tests[key].name = locator.get(key); - } + if (fs.statSync(dir).isDirectory()) { + fs.readdirSync(dir).forEach(function(fn) { + var filename = path.join(dir, fn); + var tests = require(filename); + var locator = new JsonLocator(filename); - result[filename] = { - scope: path.basename(rule, path.extname(rule)), - tests: tests - }; + for (var key in tests) { + tests[key].name = locator.get(key); + if (tests[key].ast.type.toLowerCase() !== scope.toLowerCase() && wrapper.hasOwnProperty(scope)) { + tests[key].ast = wrapper[scope](tests[key].ast); + } + } + + result[filename] = { + scope: scope, + tests: tests + }; + }); + } return result; }, {}); diff --git a/test/fixture/parse/nth.json b/test/fixture/parse/nth.json deleted file mode 100644 index 89411e6f..00000000 --- a/test/fixture/parse/nth.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "nth.0": { - "source": "10", - "ast": ["nth", "10"] - }, - "nth.1": { - "source": "2n", - "ast": ["nth", "2n"] - }, - "nth.2": { - "source": "odd", - "ast": ["nth", "odd"] - }, - "nth.3": { - "source": "even", - "ast": ["nth", "even"] - }, - "nth.4": { - "source": "n", - "ast": ["nth", "n"] - } -} \ No newline at end of file diff --git a/test/fixture/parse/nthselector.json b/test/fixture/parse/nthselector.json deleted file mode 100644 index 8936687a..00000000 --- a/test/fixture/parse/nthselector.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "nthselector.0": { - "source": ":nth-child(2n+1)", - "ast": [ - "nthselector", - ["ident", "nth-child"], - ["nth", "2n"], - ["unary", "+"], - ["nth", "1"] - ] - }, - "nthselector.1": { - "source": ":nth-last-child(+3n-2)", - "ast": [ - "nthselector", - ["ident", "nth-last-child"], - ["unary", "+"], - ["nth", "3n"], - ["unary", "-"], - ["nth", "2"] - ] - }, - "nthselector.c.0": { - "source": ":nth-child(/*test*/2n/*test*/+/*test*/1/*test*/)", - "ast": [ - "nthselector", - ["ident", "nth-child"], - ["comment", "test"], - ["nth", "2n"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["nth", "1"], - ["comment", "test"] - ] - }, - "nthselector.c.1": { - "source": ":nth-last-child(/*test*/+/*test*/3n/*test*/-/*test*/2/*test*/)", - "ast": [ - "nthselector", - ["ident", "nth-last-child"], - ["comment", "test"], - ["unary", "+"], - ["comment", "test"], - ["nth", "3n"], - ["comment", "test"], - ["unary", "-"], - ["comment", "test"], - ["nth", "2"], - ["comment", "test"] - ] - }, - "nthselector.s.0": { - "source": ":nth-child( 2n + 1 )", - "ast": [ - "nthselector", - ["ident", "nth-child"], - ["s", " "], - ["nth", "2n"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["nth", "1"], - ["s", " "] - ] - }, - "nthselector.s.1": { - "source": ":nth-last-child( + 3n - 2 )", - "ast": [ - "nthselector", - ["ident", "nth-last-child"], - ["s", " "], - ["unary", "+"], - ["s", " "], - ["nth", "3n"], - ["s", " "], - ["unary", "-"], - ["s", " "], - ["nth", "2"], - ["s", " "] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/number.json b/test/fixture/parse/number.json deleted file mode 100644 index 9b74168c..00000000 --- a/test/fixture/parse/number.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "number.0": { - "source": "10", - "ast": ["number", "10"] - }, - "number.1": { - "source": ".10", - "ast": ["number", ".10"] - }, - "number.2": { - "source": "12.34", - "ast": ["number", "12.34"] - }, - "number.3": { - "source": "0.1", - "ast": ["number", "0.1"] - }, - "number.4": { - "source": "1.0", - "ast": ["number", "1.0"] - }, - "number.5": { - "source": "0.0", - "ast": ["number", "0.0"] - }, - "number.6": { - "source": ".0", - "ast": ["number", ".0"] - }, - "number.7": { - "source": "1.200000", - "ast": ["number", "1.200000"] - } -} \ No newline at end of file diff --git a/test/fixture/parse/operator.json b/test/fixture/parse/operator.json deleted file mode 100644 index b166009a..00000000 --- a/test/fixture/parse/operator.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "operator.0": { - "source": "/", - "ast": ["operator", "/"] - }, - "operator.1": { - "source": ",", - "ast": ["operator", ","] - }, - "operator.2": { - "source": ":", - "ast": ["operator", ":"] - }, - "operator.3": { - "source": "=", - "ast": ["operator", "="] - }, - "operator.4": { - "source": "*", - "ast": ["operator", "*"] - } -} diff --git a/test/fixture/parse/percentage.json b/test/fixture/parse/percentage.json deleted file mode 100644 index 9e8a54d1..00000000 --- a/test/fixture/parse/percentage.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "percentage.0": { - "source": "10%", - "ast": [ - "percentage", - ["number", "10"] - ] - }, - "percentage.1": { - "source": ".10%", - "ast": [ - "percentage", - ["number", ".10"] - ] - }, - "percentage.2": { - "source": "12.34%", - "ast": [ - "percentage", - ["number", "12.34"] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/property.json b/test/fixture/parse/property.json deleted file mode 100644 index a3c1f748..00000000 --- a/test/fixture/parse/property.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "property.0": { - "source": "color", - "ast": [ - "property", - ["ident", "color"] - ] - }, - "property.1": { - "source": "-my-color", - "ast": [ - "property", - ["ident", "-my-color"] - ] - }, - "property.1": { - "source": "//hack", - "ast": [ - "property", - ["ident", "//hack"] - ] - } -} diff --git a/test/fixture/parse/pseudoc.json b/test/fixture/parse/pseudoc.json deleted file mode 100644 index da3ae911..00000000 --- a/test/fixture/parse/pseudoc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "pseudoc.0": { - "source": ":test", - "ast": [ - "pseudoc", - ["ident", "test"] - ] - }, - "pseudoc.1": { - "source": ":test-test", - "ast": [ - "pseudoc", - ["ident", "test-test"] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/pseudoe.json b/test/fixture/parse/pseudoe.json deleted file mode 100644 index ef581966..00000000 --- a/test/fixture/parse/pseudoe.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "pseudoe.0": { - "source": "::test", - "ast": [ - "pseudoe", - ["ident", "test"] - ] - }, - "pseudoe.1": { - "source": "::test-test", - "ast": [ - "pseudoe", - ["ident", "test-test"] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/ruleset.json b/test/fixture/parse/ruleset.json deleted file mode 100644 index c3e7d75c..00000000 --- a/test/fixture/parse/ruleset.json +++ /dev/null @@ -1,664 +0,0 @@ -{ - "ruleset.0": { - "source": "s{p:v}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - }, - "ruleset.1": { - "source": "s{p0:v0;p1:v1}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["ident", "v0"] - ] - ], - ["decldelim"], - [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["ident", "v1"] - ] - ] - ] - ] - }, - "ruleset.2": { - "source": "s0,s1{p:v}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"] - ], - ["delim"], - [ - "simpleselector", - ["ident", "s1"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - }, - "ruleset.3": { - "source": "s0,s1{p0:v0;p1:v1}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"] - ], - ["delim"], - [ - "simpleselector", - ["ident", "s1"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["ident", "v0"] - ] - ], - ["decldelim"], - [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["ident", "v1"] - ] - ] - ] - ] - }, - "ruleset.4": { - "source": ".test{filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale');color:red}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "test"] - ] - ] - ], [ - "block", [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ], - ["decldelim"], - [ - "declaration", [ - "property", - ["ident", "color"] - ], [ - "value", - ["ident", "red"] - ] - ] - ] - ] - }, - "ruleset.5": { - "source": ".test{color:red;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='test')}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "test"] - ] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "color"] - ], [ - "value", - ["ident", "red"] - ] - ], - ["decldelim"], - [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ], [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='test')"] - ] - ] - ] - ] - ] - }, - "ruleset.c.0": { - "source": "s/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ] - }, - "ruleset.c.1": { - "source": "s/*test*/{/*test*/p0/*test*/:/*test*/v0/*test*/;/*test*/p1/*test*/:/*test*/v1/*test*/}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p0"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v0"], - ["comment", "test"] - ] - ], - ["decldelim"], - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p1"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v1"], - ["comment", "test"] - ] - ] - ] - ] - }, - "ruleset.c.2": { - "source": "s0/*test*/,/*test*/s1/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"], - ["comment", "test"] - ], - ["delim"], - [ - "simpleselector", - ["comment", "test"], - ["ident", "s1"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v"], - ["comment", "test"] - ] - ] - ] - ] - }, - "ruleset.c.3": { - "source": "s0/*test*/,/*test*/s1/*test*/{/*test*/p0/*test*/:/*test*/v0/*test*/;/*test*/p1/*test*/:/*test*/v1/*test*/}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"], - ["comment", "test"] - ], - ["delim"], - [ - "simpleselector", - ["comment", "test"], - ["ident", "s1"], - ["comment", "test"] - ] - ], [ - "block", - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p0"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v0"], - ["comment", "test"] - ] - ], - ["decldelim"], - ["comment", "test"], - [ - "declaration", [ - "property", - ["ident", "p1"], - ["comment", "test"] - ], [ - "value", - ["comment", "test"], - ["ident", "v1"], - ["comment", "test"] - ] - ] - ] - ] - }, - "ruleset.s.0": { - "source": "s { p : v }", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ] - }, - "ruleset.s.1": { - "source": "s { p0 : v0 ; p1 : v1 }", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p0"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v0"], - ["s", " "] - ] - ], - ["decldelim"], - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p1"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v1"], - ["s", " "] - ] - ] - ] - ] - }, - "ruleset.s.2": { - "source": "s0 , s1 { p : v }", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"], - ["s", " "] - ], - ["delim"], - [ - "simpleselector", - ["s", " "], - ["ident", "s1"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", " "] - ] - ] - ] - ] - }, - "ruleset.s.3": { - "source": "s0 , s1 { p0 : v0 ; p1 : v1 }", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"], - ["s", " "] - ], - ["delim"], - [ - "simpleselector", - ["s", " "], - ["ident", "s1"], - ["s", " "] - ] - ], [ - "block", - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p0"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v0"], - ["s", " "] - ] - ], - ["decldelim"], - ["s", " "], - [ - "declaration", [ - "property", - ["ident", "p1"], - ["s", " "] - ], [ - "value", - ["s", " "], - ["ident", "v1"], - ["s", " "] - ] - ] - ] - ] - }, - "ruleset.s.4": { - "source": ".test {\n filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale');\n color:red\n}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "test"] - ], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"] - ] - ] - ], - ["decldelim"], - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "color"] - ], [ - "value", - ["ident", "red"], - ["s", "\n"] - ] - ] - ] - ] - }, - "ruleset.s.5": { - "source": ".test {\n color:red;\n filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')\n progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='test')\n}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "test"] - ], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "color"] - ], [ - "value", - ["ident", "red"] - ] - ], - ["decldelim"], - ["s", "\n "], - [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/transparent-border.png',sizingMethod='scale')"], - ["s", "\n "] - ], [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png',sizingMethod='test')"], - ["s", "\n"] - ] - ] - ] - ] - ] - }, - "ruleset.s.6": { - "source": ".test\n{\rcolor:red;\r\n}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "test"] - ], - ["s", "\n"] - ] - ], [ - "block", - ["s", "\r"], - [ - "declaration", [ - "property", - ["ident", "color"] - ], [ - "value", - ["ident", "red"] - ] - ], - ["decldelim"], - ["s", "\r\n"] - ] - ] - }, - "value.color.ident.0": { - "source": "s{p:yellow}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "yellow"] - ] - ] - ] - ] - }, - "value.color.ident.1": { - "source": "yellow{p:yellow}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "yellow"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "yellow"] - ] - ] - ] - ] - }, - "declaration with //": { - "source": "test{//color:red}", - "ast": [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "test"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "//color"] - ], [ - "value", - ["ident", "red"] - ] - ] - ] - ] - } -} diff --git a/test/fixture/internal/ruleset.json b/test/fixture/parse/ruleset/Ruleset.json similarity index 93% rename from test/fixture/internal/ruleset.json rename to test/fixture/parse/ruleset/Ruleset.json index ea0beb85..25c7e25e 100644 --- a/test/fixture/internal/ruleset.json +++ b/test/fixture/parse/ruleset/Ruleset.json @@ -356,6 +356,91 @@ } } }, + "ruleset.s.6": { + "source": ".test\n{\rcolor:red;\r\n}", + "translate": ".test{color:red}", + "ast": { + "type": "Ruleset", + "selector": { + "type": "Selector", + "selectors": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "test" + } + ] + } + ] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "color" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "red" + } + ] + } + } + ] + } + } + }, + "declaration with //": { + "source": ".test{//color:red}", + "ast": { + "type": "Ruleset", + "selector": { + "type": "Selector", + "selectors": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "test" + } + ] + } + ] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "//color" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "red" + } + ] + } + } + ] + } + } + }, "ruleset.c.0": { "source": "s/*test*/{/*test*/p/*test*/:/*test*/v/*test*/}", "translate": "s{p:v}", diff --git a/test/fixture/parse/selector.json b/test/fixture/parse/selector.json deleted file mode 100644 index 2ece7cbb..00000000 --- a/test/fixture/parse/selector.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "selector.0": { - "source": "a,b", - "ast": [ - "selector", [ - "simpleselector", - ["ident", "a"] - ], - ["delim"], - [ - "simpleselector", - ["ident", "b"] - ] - ] - }, - "selector.1": { - "source": "a+b,c", - "ast": [ - "selector", [ - "simpleselector", - ["ident", "a"], - ["combinator", "+"], - ["ident", "b"] - ], - ["delim"], - [ - "simpleselector", - ["ident", "c"] - ] - ] - } -} \ No newline at end of file diff --git a/test/fixture/internal/selector.json b/test/fixture/parse/selector/Selector.json similarity index 100% rename from test/fixture/internal/selector.json rename to test/fixture/parse/selector/Selector.json diff --git a/test/fixture/parse/shash.json b/test/fixture/parse/shash.json deleted file mode 100644 index 8b4bca7c..00000000 --- a/test/fixture/parse/shash.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "shash.0": { - "source": "#id", - "ast": ["shash", "id"] - }, - "shash.1": { - "source": "#-id", - "ast": ["shash", "-id"] - } -} diff --git a/test/fixture/internal/attrib.json b/test/fixture/parse/simpleSelector/Attribute.json similarity index 70% rename from test/fixture/internal/attrib.json rename to test/fixture/parse/simpleSelector/Attribute.json index 98eeeb74..e2ccb5ae 100644 --- a/test/fixture/internal/attrib.json +++ b/test/fixture/parse/simpleSelector/Attribute.json @@ -326,5 +326,152 @@ }, "flags": null } + }, + + "attrselector.0": { + "source": "[a=b]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "=", + "value": { + "type": "Identifier", + "name": "b" + }, + "flags": null + } + }, + "attrselector.1": { + "source": "[a~=b]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "~=", + "value": { + "type": "Identifier", + "name": "b" + }, + "flags": null + } + }, + "attrselector.2": { + "source": "[a^=b]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "^=", + "value": { + "type": "Identifier", + "name": "b" + }, + "flags": null + } + }, + "attrselector.3": { + "source": "[a$=b]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "$=", + "value": { + "type": "Identifier", + "name": "b" + }, + "flags": null + } + }, + "attrselector.4": { + "source": "[a*=b]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "*=", + "value": { + "type": "Identifier", + "name": "b" + }, + "flags": null + } + }, + "attrselector.4": { + "source": "[a|=b]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "|=", + "value": { + "type": "Identifier", + "name": "b" + }, + "flags": null + } + }, + "attrib with flags": { + "source": "[a='b'i]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "=", + "value": { + "type": "String", + "value": "'b'" + }, + "flags": "i" + } + }, + "attrib with flags and spaces": { + "source": "[ a = 'b' i ]", + "translate": "[a='b'i]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "=", + "value": { + "type": "String", + "value": "'b'" + }, + "flags": "i" + } + }, + "attrib with flags and comments": { + "source": "[/*1*/a/*2*/=/*3*/'b'/*4*/i/*5*/]", + "translate": "[a='b'i]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "=", + "value": { + "type": "String", + "value": "'b'" + }, + "flags": "i" + } } } diff --git a/test/fixture/internal/clazz.json b/test/fixture/parse/simpleSelector/Class.json similarity index 100% rename from test/fixture/internal/clazz.json rename to test/fixture/parse/simpleSelector/Class.json diff --git a/test/fixture/internal/shash.json b/test/fixture/parse/simpleSelector/Id.json similarity index 100% rename from test/fixture/internal/shash.json rename to test/fixture/parse/simpleSelector/Id.json diff --git a/test/fixture/parse/simpleSelector/Identifier.json b/test/fixture/parse/simpleSelector/Identifier.json new file mode 100644 index 00000000..2c14a053 --- /dev/null +++ b/test/fixture/parse/simpleSelector/Identifier.json @@ -0,0 +1,156 @@ +{ + "ident.0": { + "source": "x", + "ast": { + "type": "Identifier", + "name": "x" + } + }, + "ident.2": { + "source": "-x", + "ast": { + "type": "Identifier", + "name": "-x" + } + }, + "ident.3": { + "source": "-x-test", + "ast": { + "type": "Identifier", + "name": "-x-test" + } + }, + "ident.4": { + "source": "x\\:y", + "ast": { + "type": "Identifier", + "name": "x\\:y" + } + }, + "ident.6": { + "source": "_0", + "ast": { + "type": "Identifier", + "name": "_0" + } + }, + "ident.7": { + "source": "\\1 ", + "ast": { + "type": "Identifier", + "name": "\\1 " + } + }, + "ident.8": { + "source": "\\a ", + "ast": { + "type": "Identifier", + "name": "\\a " + } + }, + "ident.9": { + "source": "\\d ", + "ast": { + "type": "Identifier", + "name": "\\d " + } + }, + "ident.10": { + "source": "\\7f ", + "ast": { + "type": "Identifier", + "name": "\\7f " + } + }, + "ident.11": { + "source": "\\30 ", + "ast": { + "type": "Identifier", + "name": "\\30 " + } + }, + "ident.12": { + "source": "\\35 ", + "ast": { + "type": "Identifier", + "name": "\\35 " + } + }, + "ident.13": { + "source": "\\39 ", + "ast": { + "type": "Identifier", + "name": "\\39 " + } + }, + "ident.14": { + "source": "\\-", + "ast": { + "type": "Identifier", + "name": "\\-" + } + }, + "ident.15": { + "source": "\\-\\30 ", + "ast": { + "type": "Identifier", + "name": "\\-\\30 " + } + }, + "ident.16": { + "source": "\\-\\30 -", + "ast": { + "type": "Identifier", + "name": "\\-\\30 -" + } + }, + "ident.17": { + "source": "\\30 123", + "ast": { + "type": "Identifier", + "name": "\\30 123" + } + }, + "ident.18": { + "source": "\\--\\+\\a \\d \\30 asd", + "ast": { + "type": "Identifier", + "name": "\\--\\+\\a \\d \\30 asd" + } + }, + "ident.19": { + "source": "\uFFFDasd", + "ast": { + "type": "Identifier", + "name": "\uFFFDasd" + } + }, + "ident.20": { + "source": "asd\\30 \\a -asd", + "ast": { + "type": "Identifier", + "name": "asd\\30 \\a -asd" + } + }, + "ident.21": { + "source": "\\20 123", + "ast": { + "type": "Identifier", + "name": "\\20 123" + } + }, + "ident.22": { + "source": "\\123456 123", + "ast": { + "type": "Identifier", + "name": "\\123456 123" + } + }, + "ident.23": { + "source": "\\123456zzz", + "ast": { + "type": "Identifier", + "name": "\\123456zzz" + } + } +} diff --git a/test/fixture/internal/pseudoc.json b/test/fixture/parse/simpleSelector/PseudoClass.json similarity index 100% rename from test/fixture/internal/pseudoc.json rename to test/fixture/parse/simpleSelector/PseudoClass.json diff --git a/test/fixture/internal/pseudoe.json b/test/fixture/parse/simpleSelector/PseudoElement.json similarity index 100% rename from test/fixture/internal/pseudoe.json rename to test/fixture/parse/simpleSelector/PseudoElement.json diff --git a/test/fixture/internal/simpleselector.json b/test/fixture/parse/simpleSelector/SimpleSelector.json similarity index 84% rename from test/fixture/internal/simpleselector.json rename to test/fixture/parse/simpleSelector/SimpleSelector.json index ec66fd0a..4b16d93a 100644 --- a/test/fixture/internal/simpleselector.json +++ b/test/fixture/parse/simpleSelector/SimpleSelector.json @@ -1,4 +1,110 @@ { + "combinator.1": { + "source": "a b", + "translate": "a b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": " " + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, + "combinator.2": { + "source": "a+b", + "translate": "a+b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": "+" + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, + "combinator.3": { + "source": "a>b", + "translate": "a>b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": ">" + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, + "combinator.4": { + "source": "a~b", + "translate": "a~b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": "~" + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, + "combinator.5": { + "source": "a/deep/b", + "translate": "a /deep/ b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": "/deep/" + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, + "simpleselector.0": { "source": "test", "translate": "test", @@ -95,90 +201,6 @@ ] } }, - "simpleselector.5": { - "source": "a b", - "translate": "a b", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "a" - }, - { - "type": "Combinator", - "name": " " - }, - { - "type": "Identifier", - "name": "b" - } - ] - } - }, - "simpleselector.6": { - "source": "a+b", - "translate": "a+b", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "a" - }, - { - "type": "Combinator", - "name": "+" - }, - { - "type": "Identifier", - "name": "b" - } - ] - } - }, - "simpleselector.7": { - "source": "a>b", - "translate": "a>b", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "a" - }, - { - "type": "Combinator", - "name": ">" - }, - { - "type": "Identifier", - "name": "b" - } - ] - } - }, - "simpleselector.8": { - "source": "a~b", - "translate": "a~b", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "a" - }, - { - "type": "Combinator", - "name": "~" - }, - { - "type": "Identifier", - "name": "b" - } - ] - } - }, "simpleselector.9": { "source": ":nth-child(+3n-2)", "translate": ":nth-child(+3n-2)", @@ -348,6 +370,62 @@ } }, "simpleselector.14": { + "source": ".\\31 0\\+", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "\\31 0\\+" + } + ] + } + }, + "simpleselector.15": { + "source": ".\\31 b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "\\31 b" + } + ] + } + }, + "simpleselector.16": { + "source": ".\\31 b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "\\31 " + }, + { + "type": "Combinator", + "name": " " + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, + "simpleselector.17": { + "source": ".\\31 \\ b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Class", + "name": "\\31 \\ b" + } + ] + } + }, + "simpleselector.18": { "source": ":lang(nl-be)", "translate": ":lang(nl-be)", "ast": { @@ -371,7 +449,7 @@ ] } }, - "simpleselector.15": { + "simpleselector.19": { "source": ":not(.a,.b)", "translate": ":not(.a,.b)", "ast": { @@ -487,6 +565,27 @@ ] } }, + "simpleselector.c.4": { + "source": "a/*test*/ /deep/ /*test*/b", + "translate": "a /deep/ b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": "/deep/" + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, "simpleselector.s.0": { "source": "a b", "translate": "a b", @@ -571,6 +670,27 @@ ] } }, + "simpleselector.s.4": { + "source": "a /deep/ b", + "translate": "a /deep/ b", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": "/deep/" + }, + { + "type": "Identifier", + "name": "b" + } + ] + } + }, "simpleselector.s.15": { "source": ":not( .a , .b )", "translate": ":not(.a,.b)", diff --git a/test/fixture/internal/nthselector.json b/test/fixture/parse/simpleSelector/nthselector.json similarity index 67% rename from test/fixture/internal/nthselector.json rename to test/fixture/parse/simpleSelector/nthselector.json index 1b4d2d18..46c07505 100644 --- a/test/fixture/internal/nthselector.json +++ b/test/fixture/parse/simpleSelector/nthselector.json @@ -57,6 +57,101 @@ ] } }, + "nth.0": { + "source": ":nth-child(10)", + "translate": ":nth-child(10)", + "ast": { + "type": "FunctionalPseudo", + "name": "nth-child", + "arguments": [ + { + "type": "Argument", + "sequence": [ + { + "type": "Nth", + "value": "10" + } + ] + } + ] + } + }, + "nth.1": { + "source": ":nth-child(2n)", + "translate": ":nth-child(2n)", + "ast": { + "type": "FunctionalPseudo", + "name": "nth-child", + "arguments": [ + { + "type": "Argument", + "sequence": [ + { + "type": "Nth", + "value": "2n" + } + ] + } + ] + } + }, + "nth.2": { + "source": ":nth-child(odd)", + "translate": ":nth-child(odd)", + "ast": { + "type": "FunctionalPseudo", + "name": "nth-child", + "arguments": [ + { + "type": "Argument", + "sequence": [ + { + "type": "Nth", + "value": "odd" + } + ] + } + ] + } + }, + "nth.3": { + "source": ":nth-child(even)", + "translate": ":nth-child(even)", + "ast": { + "type": "FunctionalPseudo", + "name": "nth-child", + "arguments": [ + { + "type": "Argument", + "sequence": [ + { + "type": "Nth", + "value": "even" + } + ] + } + ] + } + }, + "nth.4": { + "source": ":nth-child(n)", + "translate": ":nth-child(n)", + "ast": { + "type": "FunctionalPseudo", + "name": "nth-child", + "arguments": [ + { + "type": "Argument", + "sequence": [ + { + "type": "Nth", + "value": "n" + } + ] + } + ] + } + }, "nthselector.c.0": { "source": ":nth-child(/*test*/2n/*test*/+/*test*/1/*test*/)", "translate": ":nth-child(2n+1)", diff --git a/test/fixture/parse/simpleselector.json b/test/fixture/parse/simpleselector.json deleted file mode 100644 index 0114d32d..00000000 --- a/test/fixture/parse/simpleselector.json +++ /dev/null @@ -1,334 +0,0 @@ -{ - "simpleselector.0": { - "source": "test", - "ast": [ - "simpleselector", - ["ident", "test"] - ] - }, - "simpleselector.1": { - "source": ".test", - "ast": [ - "simpleselector", [ - "clazz", - ["ident", "test"] - ] - ] - }, - "simpleselector.2": { - "source": "#test", - "ast": [ - "simpleselector", - ["shash", "test"] - ] - }, - "simpleselector.3": { - "source": "[a=b]", - "ast": [ - "simpleselector", [ - "attrib", - ["ident", "a"], - ["attrselector", "="], - ["ident", "b"] - ] - ] - }, - "simpleselector.4": { - "source": "[a=b][c='d']", - "ast": [ - "simpleselector", [ - "attrib", - ["ident", "a"], - ["attrselector", "="], - ["ident", "b"] - ], [ - "attrib", - ["ident", "c"], - ["attrselector", "="], - ["string", "'d'"] - ] - ] - }, - "simpleselector.5": { - "source": "a b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["ident", "b"] - ] - }, - "simpleselector.6": { - "source": "a+b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["combinator", "+"], - ["ident", "b"] - ] - }, - "simpleselector.7": { - "source": "a>b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["combinator", ">"], - ["ident", "b"] - ] - }, - "simpleselector.8": { - "source": "a~b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["combinator", "~"], - ["ident", "b"] - ] - }, - "simpleselector.9": { - "source": ":nth-child(+3n-2)", - "ast": [ - "simpleselector", [ - "nthselector", - ["ident", "nth-child"], - ["unary", "+"], - ["nth", "3n"], - ["unary", "-"], - ["nth", "2"] - ] - ] - }, - "simpleselector.10": { - "source": "a|b", - "ast": [ - "simpleselector", - ["ident", "a|b"] - ] - }, - "simpleselector.11": { - "source": "*|*:not(*)", - "ast": [ - "simpleselector", - ["ident", "*|*"], - [ - "pseudoc", [ - "funktion", - ["ident", "not"], - [ - "functionBody", [ - "simpleselector", - ["ident", "*"] - ] - ] - ] - ] - ] - }, - "simpleselector.12": { - "source": "x:not([ABC])", - "ast": [ - "simpleselector", - ["ident", "x"], - [ - "pseudoc", [ - "funktion", - ["ident", "not"], - [ - "functionBody", [ - "simpleselector", [ - "attrib", - ["ident", "ABC"] - ] - ] - ] - ] - ] - ] - }, - "simpleselector.13": { - "source": ":not(el.class-postfix)", - "ast": [ - "simpleselector", [ - "pseudoc", [ - "funktion", - ["ident", "not"], - [ - "functionBody", [ - "simpleselector", - ["ident", "el"], - [ - "clazz", - ["ident", "class-postfix"] - ] - ] - ] - ] - ] - ] - }, - "simpleselector.14": { - "source": ".\\31 0\\+", - "ast": [ - "simpleselector", [ - "clazz", - ["ident", "\\31 0\\+"] - ] - ] - }, - "simpleselector.15": { - "source": ".\\31 b", - "ast": [ - "simpleselector", [ - "clazz", - ["ident", "\\31 b"] - ] - ] - }, - "simpleselector.16": { - "source": ".\\31 b", - "ast": [ - "simpleselector", - [ - "clazz", - ["ident", "\\31 "] - ], - ["s", " "], - ["ident", "b"] - ] - }, - "simpleselector.17": { - "source": ".\\31 \\ b", - "ast": [ - "simpleselector", [ - "clazz", - ["ident", "\\31 \\ b"] - ] - ] - }, - "simpleselector.c.0": { - "source": "a/*test*/b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["comment", "test"], - ["ident", "b"] - ] - }, - "simpleselector.c.1": { - "source": "a/*test*/+/*test*/b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["comment", "test"], - ["combinator", "+"], - ["comment", "test"], - ["ident", "b"] - ] - }, - "simpleselector.c.2": { - "source": "a/*test*/>/*test*/b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["comment", "test"], - ["combinator", ">"], - ["comment", "test"], - ["ident", "b"] - ] - }, - "simpleselector.c.3": { - "source": "a/*test*/~/*test*/b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["comment", "test"], - ["combinator", "~"], - ["comment", "test"], - ["ident", "b"] - ] - }, - "simpleselector.c.4": { - "source": "a b + c > d ~ e", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["ident", "b"], - ["s", " "], - ["combinator", "+"], - ["s", " "], - ["ident", "c"], - ["s", " "], - ["combinator", ">"], - ["s", " "], - ["ident", "d"], - ["s", " "], - ["combinator", "~"], - ["s", " "], - ["ident", "e"] - ] - }, - "simpleselector.s.0": { - "source": "a b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["ident", "b"] - ] - }, - "simpleselector.s.1": { - "source": "a + b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["combinator", "+"], - ["s", " "], - ["ident", "b"] - ] - }, - "simpleselector.s.2": { - "source": "a > b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["combinator", ">"], - ["s", " "], - ["ident", "b"] - ] - }, - "simpleselector.s.3": { - "source": "a ~ b", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["combinator", "~"], - ["s", " "], - ["ident", "b"] - ] - }, - "simpleselector.s.4": { - "source": "a b + c > d ~ e", - "ast": [ - "simpleselector", - ["ident", "a"], - ["s", " "], - ["ident", "b"], - ["s", " "], - ["combinator", "+"], - ["s", " "], - ["ident", "c"], - ["s", " "], - ["combinator", ">"], - ["s", " "], - ["ident", "d"], - ["s", " "], - ["combinator", "~"], - ["s", " "], - ["ident", "e"] - ] - } -} diff --git a/test/fixture/parse/string.json b/test/fixture/parse/string.json deleted file mode 100644 index 1f455dbc..00000000 --- a/test/fixture/parse/string.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "string.0": { - "source": "'test'", - "ast": ["string", "'test'"] - }, - "string.1": { - "source": "\"test\"", - "ast": ["string", "\"test\""] - }, - "string.2": { - "source": "'te\\'st'", - "ast": ["string", "'te\\'st'"] - }, - "string.3": { - "source": "\"te\\\"st\"", - "ast": ["string", "\"te\\\"st\""] - }, - "string with escaped \\n": { - "source": "\"te\\\nst\"", - "restoredSource": "\"test\"", - "ast": ["string", "\"test\""] - }, - "string with escaped \\r": { - "source": "\"te\\\rst\"", - "restoredSource": "\"test\"", - "ast": ["string", "\"test\""] - }, - "string with escaped \\r\\n": { - "source": "\"te\\\r\nst\"", - "restoredSource": "\"test\"", - "ast": ["string", "\"test\""] - } -} diff --git a/test/fixture/parse/stylesheet.json b/test/fixture/parse/stylesheet.json deleted file mode 100644 index 71202e55..00000000 --- a/test/fixture/parse/stylesheet.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "empty": { - "source": "", - "ast": ["stylesheet"] - }, - "BOM.1": { - "source": "\uFEFF", - "restoredSource": "", - "ast": ["stylesheet"] - }, - "BOM.2": { - "source": "\uFEFFa{}", - "restoredSource": "a{}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "a"] - ] - ], ["block"] - ] - ] - }, - "issue111.test1": { - "source": "body:not([x|x]) {}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "body"], - [ - "pseudoc", [ - "funktion", - ["ident", "not"], - [ - "functionBody", [ - "simpleselector", [ - "attrib", - ["ident", "x|x"] - ] - ] - ] - ] - ], - ["s", " "] - ] - ], - ["block"] - ] - ] - }, - "issue86.test1": { - "source": ".ie-test\n{\n filter: progid:DXImageTransform.Microsoft.gradient (startColorstr=#33CCCCCC, endColorstr=#33CCCCCC);\n}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "ie-test"] - ], - ["s", "\n"] - ] - ], [ - "block", - ["s", "\n "], - [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["s", " "], - ["raw", "progid:DXImageTransform.Microsoft.gradient (startColorstr=#33CCCCCC, endColorstr=#33CCCCCC)"] - ] - ] - ], - ["decldelim"], - ["s", "\n"] - ] - ] - ] - }, - "issue87.test1": { - "source": ".t\n{\n filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40) !important;\n}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", [ - "clazz", - ["ident", "t"] - ], - ["s", "\n"] - ] - ], [ - "block", - ["s", "\n "], - [ - "filter", [ - "property", - ["ident", "filter"] - ], [ - "filterv", [ - "progid", - ["raw", "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"], - ["s", " "] - ], - ["important"] - ] - ], - ["decldelim"], - ["s", "\n"] - ] - ] - ] - }, - "issue90.test1": { - "source": "@media test {\n @page {\n p: v;\n }\n}", - "ast": [ - "stylesheet", [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "test"], - ["s", " "] - ], [ - "atrulers", - ["s", "\n "], - [ - "atruleb", [ - "atkeyword", - ["ident", "page"] - ], - ["s", " "], - [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["s", " "], - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", "\n "] - ] - ], - ["s", "\n"] - ] - ] - ] - }, - "issue90.test2": { - "source": "x {\n p:v;\n}\n\n@media test {\n a {\n p:v;\n }\n\n /* comment */\n\n @page {\n p: v;\n }\n\n b {\n p:v;\n }\n}\n\ny {\n p:v;\n}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "x"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", "\n"] - ] - ], - ["s", "\n\n"], - [ - "atruler", [ - "atkeyword", - ["ident", "media"] - ], [ - "atrulerq", - ["s", " "], - ["ident", "test"], - ["s", " "] - ], [ - "atrulers", - ["s", "\n "], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "a"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", "\n "] - ] - ], - ["s", "\n\n "], - ["comment", " comment "], - ["s", "\n\n "], - [ - "atruleb", [ - "atkeyword", - ["ident", "page"] - ], - ["s", " "], - [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["s", " "], - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", "\n "] - ] - ], - ["s", "\n\n "], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "b"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", "\n "] - ] - ], - ["s", "\n"] - ] - ], - ["s", "\n\n"], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "y"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ], - ["decldelim"], - ["s", "\n"] - ] - ] - ] - }, - "stylesheet.0": { - "source": "s{p:v}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ] - ] - }, - "stylesheet.1": { - "source": "s0{p0:v0}s1{p1:v1}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["ident", "v0"] - ] - ] - ] - ], [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s1"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["ident", "v1"] - ] - ] - ] - ] - ] - }, - "stylesheet.2": { - "source": "@test;", - "ast": [ - "stylesheet", [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ] - ] - ] - }, - "stylesheet.3": { - "source": "@test0;@test1;", - "ast": [ - "stylesheet", [ - "atrules", [ - "atkeyword", - ["ident", "test0"] - ] - ], [ - "atrules", [ - "atkeyword", - ["ident", "test1"] - ] - ] - ] - }, - "stylesheet.4": { - "source": "s{p:v}@test;", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"] - ] - ], [ - "block", [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"] - ] - ] - ] - ], [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ] - ] - ] - }, - "stylesheet.c.0": { - "source": "/* test */\ns {\n p: v\n}\n/* test */", - "ast": [ - "stylesheet", - ["comment", " test "], - ["s", "\n"], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", "\n"] - ] - ] - ] - ], - ["s", "\n"], - ["comment", " test "] - ] - }, - "stylesheet.s.0": { - "source": "s {\n p: v\n}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["s", " "], - ["ident", "v"], - ["s", "\n"] - ] - ] - ] - ] - ] - }, - "stylesheet.s.1": { - "source": "s0 {\n p0: v0\n}\n\ns1 {\n p1: v1\n}", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s0"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p0"] - ], [ - "value", - ["s", " "], - ["ident", "v0"], - ["s", "\n"] - ] - ] - ] - ], - ["s", "\n\n"], - [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s1"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p1"] - ], [ - "value", - ["s", " "], - ["ident", "v1"], - ["s", "\n"] - ] - ] - ] - ] - ] - }, - "stylesheet.s.2": { - "source": "@test0;\n@test1;", - "ast": [ - "stylesheet", [ - "atrules", [ - "atkeyword", - ["ident", "test0"] - ] - ], - ["s", "\n"], - [ - "atrules", [ - "atkeyword", - ["ident", "test1"] - ] - ] - ] - }, - "stylesheet.s.3": { - "source": "s {\n p:v\n}\n\n@test;", - "ast": [ - "stylesheet", [ - "ruleset", [ - "selector", [ - "simpleselector", - ["ident", "s"], - ["s", " "] - ] - ], [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "p"] - ], [ - "value", - ["ident", "v"], - ["s", "\n"] - ] - ] - ] - ], - ["s", "\n\n"], - [ - "atrules", [ - "atkeyword", - ["ident", "test"] - ] - ] - ] - }, - "issue #250": { - "source": "@x {\n unicode-range: U+2074, U+20AC, U+2212, U+2215, U+E0FF;\n}\n@x {\n a: a();\n}", - "ast": [ - "stylesheet", [ - "atruleb", [ - "atkeyword", - ["ident", "x"] - ], - ["s", " "], - [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "unicode-range"] - ], [ - "value", - ["s", " "], - ["ident", "U+2074"], - ["operator", ","], - ["s", " "], - ["ident", "U+20AC"], - ["operator", ","], - ["s", " "], - ["ident", "U+2212"], - ["operator", ","], - ["s", " "], - ["ident", "U+2215"], - ["operator", ","], - ["s", " "], - ["ident", "U+E0FF"] - ] - ], - ["decldelim"], - ["s", "\n"] - ] - ], - ["s", "\n"], - [ - "atruleb", [ - "atkeyword", - ["ident", "x"] - ], - ["s", " "], - [ - "block", - ["s", "\n "], - [ - "declaration", [ - "property", - ["ident", "a"] - ], [ - "value", - ["s", " "], - [ - "funktion", - ["ident", "a"], - ["functionBody"] - ] - ] - ], - ["decldelim"], - ["s", "\n"] - ] - ] - ] - } -} diff --git a/test/fixture/internal/stylesheet.json b/test/fixture/parse/stylesheet/StyleSheet.json similarity index 85% rename from test/fixture/internal/stylesheet.json rename to test/fixture/parse/stylesheet/StyleSheet.json index 71d651ba..67277799 100644 --- a/test/fixture/internal/stylesheet.json +++ b/test/fixture/parse/stylesheet/StyleSheet.json @@ -1,4 +1,49 @@ { + "empty": { + "source": "", + "ast": { + "type": "StyleSheet", + "rules": [] + } + }, + "BOM.1": { + "source": "\uFEFF", + "translate": "", + "ast": { + "type": "StyleSheet", + "rules": [] + } + }, + "BOM.2": { + "source": "\uFEFFa{}", + "translate": "a{}", + "ast": { + "type": "StyleSheet", + "rules": [ + { + "type": "Ruleset", + "selector": { + "type": "Selector", + "selectors": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + } + ] + } + ] + }, + "block": { + "type": "Block", + "declarations": [] + } + } + ] + } + }, "issue111.test1": { "source": "body:not([x|x]) {}", "translate": "body:not([x|x]){}", @@ -944,5 +989,119 @@ } ] } + }, + "issue #250": { + "source": "@x {\n unicode-range: U+2074, U+20AC, U+2212, U+2215, U+E0FF;\n}\n@x {\n a: a();\n}", + "translate": "@x{unicode-range:U+2074, U+20AC, U+2212, U+2215, U+E0FF}@x{a:a()}", + "ast": { + "type": "StyleSheet", + "rules": [ + { + "type": "Atrule", + "name": "x", + "expression": { + "type": "AtruleExpression", + "sequence": [] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "unicode-range" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Identifier", + "name": "U+2074" + }, + { + "type": "Operator", + "value": "," + }, + { + "type": "Space" + }, + { + "type": "Identifier", + "name": "U+20AC" + }, + { + "type": "Operator", + "value": "," + }, + { + "type": "Space" + }, + { + "type": "Identifier", + "name": "U+2212" + }, + { + "type": "Operator", + "value": "," + }, + { + "type": "Space" + }, + { + "type": "Identifier", + "name": "U+2215" + }, + { + "type": "Operator", + "value": "," + }, + { + "type": "Space" + }, + { + "type": "Identifier", + "name": "U+E0FF" + } + ] + } + } + ] + } + }, + { + "type": "Atrule", + "name": "x", + "expression": { + "type": "AtruleExpression", + "sequence": [] + }, + "block": { + "type": "Block", + "declarations": [ + { + "type": "Declaration", + "property": { + "type": "Property", + "name": "a" + }, + "value": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Function", + "name": "a", + "arguments": [] + } + ] + } + } + ] + } + } + ] + } } } diff --git a/test/fixture/parse/stylesheet/comment.json b/test/fixture/parse/stylesheet/comment.json new file mode 100644 index 00000000..eb8531c7 --- /dev/null +++ b/test/fixture/parse/stylesheet/comment.json @@ -0,0 +1,37 @@ +{ + "comment.0": { + "source": "/*!test*/", + "ast": { + "type": "Comment", + "value": "!test" + } + }, + "comment.1": { + "source": "/*!te\nst*/", + "ast": { + "type": "Comment", + "value": "!te\nst" + } + }, + "comment.2": { + "source": "/*!te\rst*/", + "ast": { + "type": "Comment", + "value": "!te\rst" + } + }, + "comment.3": { + "source": "/*!te\r\nst*/", + "ast": { + "type": "Comment", + "value": "!te\r\nst" + } + }, + "comment.4": { + "source": "/*!te**st*/", + "ast": { + "type": "Comment", + "value": "!te**st" + } + } +} diff --git a/test/fixture/parse/stylesheet/unknown.json b/test/fixture/parse/stylesheet/unknown.json new file mode 100644 index 00000000..4f53b11f --- /dev/null +++ b/test/fixture/parse/stylesheet/unknown.json @@ -0,0 +1,16 @@ +{ + "unknown.0": { + "source": "//", + "ast": { + "type": "Unknown", + "value": "//" + } + }, + "unknown.1": { + "source": "// invalid css", + "ast": { + "type": "Unknown", + "value": "// invalid css" + } + } +} diff --git a/test/fixture/parse/unary.json b/test/fixture/parse/unary.json deleted file mode 100644 index 1c78a53d..00000000 --- a/test/fixture/parse/unary.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "unary.0": { - "source": "-", - "ast": ["unary", "-"] - }, - "unary.1": { - "source": "+", - "ast": ["unary", "+"] - } -} \ No newline at end of file diff --git a/test/fixture/parse/unknown.json b/test/fixture/parse/unknown.json deleted file mode 100644 index 80790843..00000000 --- a/test/fixture/parse/unknown.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "unknown.0": { - "source": "//", - "ast": ["unknown", "//"] - }, - "unknown.1": { - "source": "// invalid css", - "ast": ["unknown", "// invalid css"] - } -} \ No newline at end of file diff --git a/test/fixture/parse/uri.json b/test/fixture/parse/uri.json deleted file mode 100644 index 4a6fb674..00000000 --- a/test/fixture/parse/uri.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "uri.0": { - "source": "url('http://test.com')", - "ast": [ - "uri", - ["string", "'http://test.com'"] - ] - }, - "uri.1": { - "source": "url(http://test.com)", - "ast": [ - "uri", - ["raw", "http://test.com"] - ] - }, - "uri.c.0": { - "source": "url(/*test*/'http://test.com'/*test*/)", - "ast": [ - "uri", - ["comment", "test"], - ["string", "'http://test.com'"], - ["comment", "test"] - ] - }, - "uri.c.1": { - "source": "url(/*test*/http://test.com/*test*/)", - "ast": [ - "uri", - ["comment", "test"], - ["raw", "http://test.com/*test*/"] - ] - }, - "uri.s.0": { - "source": "url( 'http://test.com' )", - "ast": [ - "uri", - ["s", " "], - ["string", "'http://test.com'"], - ["s", " "] - ] - }, - "uri.s.1": { - "source": "url( http://test.com )", - "ast": [ - "uri", - ["s", " "], - ["raw", "http://test.com"], - ["s", " "] - ] - } -} \ No newline at end of file diff --git a/test/fixture/parse/value.json b/test/fixture/parse/value.json deleted file mode 100644 index 438f2593..00000000 --- a/test/fixture/parse/value.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "value.0": { - "source": "x", - "ast": [ - "value", - ["ident", "x"] - ] - }, - "value.1": { - "source": "10", - "ast": [ - "value", - ["number", "10"] - ] - }, - "value.2": { - "source": "test(x)", - "ast": [ - "value", [ - "funktion", - ["ident", "test"], - [ - "functionBody", - ["ident", "x"] - ] - ] - ] - }, - "value.3": { - "source": "10 !important", - "ast": [ - "value", - ["number", "10"], - ["s", " "], - ["important"] - ] - }, - "value.4": { - "source": "-100%", - "ast": [ - "value", - [ - "percentage", - ["number", "-100"] - ] - ] - }, - "value.dimension.0": { - "source": "0px", - "ast": [ - "value", [ - "dimension", - ["number", "0"], - ["ident", "px"] - ] - ] - }, - "value.dimension.1": { - "source": ".0px", - "ast": [ - "value", [ - "dimension", - ["number", ".0"], - ["ident", "px"] - ] - ] - }, - "value.dimension.2": { - "source": "0000.000px", - "ast": [ - "value", [ - "dimension", - ["number", "0000.000"], - ["ident", "px"] - ] - ] - }, - "value.rgb.0": { - "source": "rgb(10, 10, 10)", - "ast": [ - "value", [ - "funktion", - ["ident", "rgb"], - [ - "functionBody", - ["number", "10"], - ["operator", ","], - ["s", " "], - ["number", "10"], - ["operator", ","], - ["s", " "], - ["number", "10"] - ] - ] - ] - }, - "value.rgb.1": { - "source": "rgb(255,0,0)", - "ast": [ - "value", [ - "funktion", - ["ident", "rgb"], - [ - "functionBody", - ["number", "255"], - ["operator", ","], - ["number", "0"], - ["operator", ","], - ["number", "0"] - ] - ] - ] - }, - "value.rgb.2": { - "source": "rgb(10.0,00.00,10%)", - "ast": [ - "value", [ - "funktion", - ["ident", "rgb"], - [ - "functionBody", - ["number", "10.0"], - ["operator", ","], - ["number", "00.00"], - ["operator", ","], - [ - "percentage", - ["number", "10"] - ] - ] - ] - ] - }, - "value.vhash.0": { - "source": "#ffffff", - "ast": [ - "value", - ["vhash", "ffffff"] - ] - }, - "value.vhash.1": { - "source": "#f00", - "ast": [ - "value", - ["vhash", "f00"] - ] - }, - "value.vhash.2": { - "source": "#F0f0C0", - "ast": [ - "value", - ["vhash", "F0f0C0"] - ] - }, - "value.vhash.3": { - "source": "#AaBbCc", - "ast": [ - "value", - ["vhash", "AaBbCc"] - ] - }, - "unicode range one hex": { - "source": "U+0F00", - "ast": [ - "value", - ["ident", "U+0F00"] - ] - }, - "unicode range hex pair": { - "source": "U+0F00-0FFF", - "ast": [ - "value", - ["ident", "U+0F00-0FFF"] - ] - }, - "unicode range hex with ?": { - "source": "u+0F00??", - "ast": [ - "value", - ["ident", "u+0F00??"] - ] - }, - "ident name starts with minus": { - "source": "-moz-flex", - "ast": [ - "value", - ["ident", "-moz-flex"] - ] - }, - "expression": { - "source": "expression(a=1)", - "ast": [ - "value", - ["functionExpression", "a=1"] - ] - } -} diff --git a/test/fixture/internal/braces.json b/test/fixture/parse/value/Braces.json similarity index 100% rename from test/fixture/internal/braces.json rename to test/fixture/parse/value/Braces.json diff --git a/test/fixture/internal/dimension.json b/test/fixture/parse/value/Dimension.json similarity index 100% rename from test/fixture/internal/dimension.json rename to test/fixture/parse/value/Dimension.json diff --git a/test/fixture/internal/funktion.json b/test/fixture/parse/value/Function.json similarity index 100% rename from test/fixture/internal/funktion.json rename to test/fixture/parse/value/Function.json diff --git a/test/fixture/parse/value/Hash.json b/test/fixture/parse/value/Hash.json new file mode 100644 index 00000000..a46e4088 --- /dev/null +++ b/test/fixture/parse/value/Hash.json @@ -0,0 +1,39 @@ +{ + "vhash.0": { + "source": "#100", + "translate": "#100", + "ast": { + "type": "Hash", + "value": "100" + } + }, + "vhash.1": { + "source": "#id", + "translate": "#id", + "ast": { + "type": "Hash", + "value": "id" + } + }, + "vhash.2": { + "source": "#123abc", + "ast": { + "type": "Hash", + "value": "123abc" + } + }, + "vhash.3": { + "source": "#abc123", + "ast": { + "type": "Hash", + "value": "abc123" + } + }, + "vhash.4": { + "source": "#a1b2c3d4", + "ast": { + "type": "Hash", + "value": "a1b2c3d4" + } + } +} diff --git a/test/fixture/parse/value/Important.json b/test/fixture/parse/value/Important.json new file mode 100644 index 00000000..492a671f --- /dev/null +++ b/test/fixture/parse/value/Important.json @@ -0,0 +1,46 @@ +{ + "important.0": { + "source": "!important", + "ast": { + "type": "Value", + "important": true, + "sequence": [] + } + }, + "important.1": { + "source": "!ImpoRtant", + "translate": "!important", + "ast": { + "type": "Value", + "important": true, + "sequence": [] + } + }, + "important.2": { + "source": "!IMPORTANT", + "translate": "!important", + "ast": { + "type": "Value", + "important": true, + "sequence": [] + } + }, + "important.c.0": { + "source": "!/*test*/important", + "translate": "!important", + "ast": { + "type": "Value", + "important": true, + "sequence": [] + } + }, + "important.s.0": { + "source": "! important", + "translate": "!important", + "ast": { + "type": "Value", + "important": true, + "sequence": [] + } + } +} diff --git a/test/fixture/internal/number.json b/test/fixture/parse/value/Number.json similarity index 100% rename from test/fixture/internal/number.json rename to test/fixture/parse/value/Number.json diff --git a/test/fixture/internal/percentage.json b/test/fixture/parse/value/Percentage.json similarity index 100% rename from test/fixture/internal/percentage.json rename to test/fixture/parse/value/Percentage.json diff --git a/test/fixture/internal/string.json b/test/fixture/parse/value/String.json similarity index 54% rename from test/fixture/internal/string.json rename to test/fixture/parse/value/String.json index e896f752..8780c063 100644 --- a/test/fixture/internal/string.json +++ b/test/fixture/parse/value/String.json @@ -30,5 +30,29 @@ "type": "String", "value": "\"te\\\"st\"" } + }, + "string with escaped \\n": { + "source": "\"te\\\nst\"", + "translate": "\"test\"", + "ast": { + "type": "String", + "value": "\"test\"" + } + }, + "string with escaped \\r": { + "source": "\"te\\\rst\"", + "translate": "\"test\"", + "ast": { + "type": "String", + "value": "\"test\"" + } + }, + "string with escaped \\r\\n": { + "source": "\"te\\\r\nst\"", + "translate": "\"test\"", + "ast": { + "type": "String", + "value": "\"test\"" + } } } diff --git a/test/fixture/internal/uri.json b/test/fixture/parse/value/Url.json similarity index 100% rename from test/fixture/internal/uri.json rename to test/fixture/parse/value/Url.json diff --git a/test/fixture/internal/value.json b/test/fixture/parse/value/Value.json similarity index 93% rename from test/fixture/internal/value.json rename to test/fixture/parse/value/Value.json index fae4385b..0392d7cd 100644 --- a/test/fixture/internal/value.json +++ b/test/fixture/parse/value/Value.json @@ -447,5 +447,36 @@ } ] } + }, + "ident name starts with minus": { + "source": "-moz-flex", + "ast": { + "type": "Value", + "important": false, + "sequence": [{ + "type": "Identifier", + "name": "-moz-flex" + }] + } + }, + "expression": { + "source": "expression(a=1)", + "ast": { + "type": "Value", + "important": false, + "sequence": [ + { + "type": "Function", + "name": "expression", + "arguments": [{ + "type": "Argument", + "sequence": [{ + "type": "Raw", + "value": "a=1" + }] + }] + } + ] + } } } diff --git a/test/fixture/internal/functionExpression.json b/test/fixture/parse/value/expression.json similarity index 100% rename from test/fixture/internal/functionExpression.json rename to test/fixture/parse/value/expression.json diff --git a/test/fixture/parse/vhash.json b/test/fixture/parse/vhash.json deleted file mode 100644 index b1a3532d..00000000 --- a/test/fixture/parse/vhash.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "vhash.0": { - "source": "#100", - "ast": ["vhash", "100"] - }, - "vhash.1": { - "source": "#id", - "ast": ["vhash", "id"] - }, - "vhash.2": { - "source": "#123abc", - "ast": ["vhash", "123abc"] - }, - "vhash.3": { - "source": "#abc123", - "ast": ["vhash", "abc123"] - }, - "vhash.4": { - "source": "#a1b2c3d4", - "ast": ["vhash", "a1b2c3d4"] - } -} diff --git a/test/fixture/stringify.txt b/test/fixture/stringify.txt index 2f80b245..b747b555 100644 --- a/test/fixture/stringify.txt +++ b/test/fixture/stringify.txt @@ -1,113 +1,104 @@ -[ - { - "source": "", - "offset": 0, - "line": 1, - "column": 1 - }, "stylesheet", [ - { - "source": "", - "offset": 0, - "line": 1, - "column": 1 - }, "ruleset", [ - { +{ + "type": "StyleSheet", + "info": { "source": "", "offset": 0, "line": 1, "column": 1 - }, "selector", [ + }, + "rules": [ { - "source": "", - "offset": 0, - "line": 1, - "column": 1 - }, "simpleselector", [ - { - "source": "", - "offset": 0, - "line": 1, - "column": 1 - }, "clazz", - [ - { - "source": "", - "offset": 0, - "line": 1, - "column": 2 - }, "ident", "a" - ] - ], - [ - { - "source": "", - "offset": 1, - "line": 1, - "column": 3 - }, "s", "\n" - ] - ] - ], [ - { - "source": "", - "offset": 2, - "line": 2, - "column": 1 - }, "block", - [ - { - "source": "", - "offset": 3, - "line": 2, - "column": 2 - }, "s", "\r" - ], - [ - { - "source": "", - "offset": 4, - "line": 3, - "column": 1 - }, "declaration", [ - { - "source": "", - "offset": 4, - "line": 3, - "column": 1 - }, "property", - [ - { - "source": "", - "offset": 4, - "line": 3, - "column": 1 - }, "ident", "color" - ] - ], [ - { - "source": "", - "offset": 10, - "line": 3, - "column": 7 - }, "value", - [ - { - "source": "", - "offset": 10, - "line": 3, - "column": 7 - }, "s", "\r\n" - ], - [ - { - "source": "", - "offset": 12, - "line": 4, - "column": 1 - }, "ident", "red" - ] - ] - ] + "type": "Ruleset", + "info": { + "source": "", + "offset": 0, + "line": 1, + "column": 1 + }, + "selector": { + "type": "Selector", + "info": { + "source": "", + "offset": 0, + "line": 1, + "column": 1 + }, + "selectors": [ + { + "type": "SimpleSelector", + "info": { + "source": "", + "offset": 0, + "line": 1, + "column": 1 + }, + "sequence": [ + { + "type": "Class", + "info": { + "source": "", + "offset": 0, + "line": 1, + "column": 1 + }, + "name": "a" + } + ] + } + ] + }, + "block": { + "type": "Block", + "info": { + "source": "", + "offset": 2, + "line": 2, + "column": 1 + }, + "declarations": [ + { + "type": "Declaration", + "info": { + "source": "", + "offset": 4, + "line": 3, + "column": 1 + }, + "property": { + "type": "Property", + "info": { + "source": "", + "offset": 4, + "line": 3, + "column": 1 + }, + "name": "color" + }, + "value": { + "type": "Value", + "info": { + "source": "", + "offset": 10, + "line": 3, + "column": 7 + }, + "important": false, + "sequence": [ + { + "type": "Identifier", + "info": { + "source": "", + "offset": 12, + "line": 4, + "column": 1 + }, + "name": "red" + } + ] + } + } + ] + } + } ] - ] -] +} diff --git a/test/helpers/stringify.js b/test/helpers/stringify.js new file mode 100644 index 00000000..2cc7089e --- /dev/null +++ b/test/helpers/stringify.js @@ -0,0 +1,28 @@ +module.exports = function stringify(ast, withInfo) { + function clean(source) { + if (source && typeof source.toJSON === 'function') { + source = source.toJSON(); + } + + if (Array.isArray(source)) { + return source.map(clean); + } + + if (source && typeof source === 'object') { + var result = {}; + for (var key in source) { + if ((withInfo || key !== 'info') && + key !== 'id' && key !== 'length' && + key !== 'fingerprint' && key !== 'compareMarker' && + key !== 'pseudoSignature' && key !== 'avoidRulesMerge') { + result[key] = clean(source[key]); + } + } + return result; + } + + return source; + } + + return JSON.stringify(clean(ast), null, 4); +}; diff --git a/test/parse.js b/test/parse.js index a79a9b50..dbd60c75 100644 --- a/test/parse.js +++ b/test/parse.js @@ -1,8 +1,9 @@ var path = require('path'); var assert = require('assert'); -var csso = require('../lib/index.js'); -var JsonLocator = require('./helpers/JsonLocator.js'); +var csso = require('../lib/index'); +var JsonLocator = require('./helpers/JsonLocator'); var forEachParseTest = require('./fixture/parse').forEachTest; +var stringify = require('./helpers/stringify'); function createParseErrorTest(location, test, options) { it(location + ' ' + JSON.stringify(test.css), function() { @@ -28,10 +29,10 @@ describe('parse', function() { var ast = csso.parse(test.source, scope); // AST should be equal - assert.equal(csso.stringify(ast), csso.stringify(test.ast)); + assert.equal(stringify(ast), stringify(test.ast)); // translated AST should be equal to original source - assert.equal(csso.translate(ast), 'restoredSource' in test ? test.restoredSource : test.source); + assert.equal(csso.internal.translate(ast), 'translate' in test ? test.translate : test.source); }); }); }); @@ -58,30 +59,22 @@ describe('positions', function() { var ast = csso.parse('.foo.bar {\n property: value;\n}', null, true); var positions = []; - csso.walk(ast, function(node) { - positions.push([node[0].line, node[0].column, node[1]]); + csso.internal.walk(ast, function(node) { + positions.unshift([node.info.line, node.info.column, node.type]); }, true); assert.deepEqual(positions, [ - [1, 1, 'stylesheet'], - [1, 1, 'ruleset'], - [1, 1, 'selector'], - [1, 1, 'simpleselector'], - [1, 1, 'clazz'], - [1, 2, 'ident'], - [1, 5, 'clazz'], - [1, 6, 'ident'], - [1, 9, 's'], - [1, 10, 'block'], - [1, 11, 's'], - [2, 3, 'declaration'], - [2, 3, 'property'], - [2, 3, 'ident'], - [2, 12, 'value'], - [2, 12, 's'], - [2, 13, 'ident'], - [2, 18, 'decldelim'], - [2, 19, 's'] + [1, 1, 'StyleSheet'], + [1, 1, 'Ruleset'], + [1, 10, 'Block'], + [2, 3, 'Declaration'], + [2, 12, 'Value'], + [2, 13, 'Identifier'], + [2, 3, 'Property'], + [1, 1, 'Selector'], + [1, 1, 'SimpleSelector'], + [1, 5, 'Class'], + [1, 1, 'Class'] ]); }); @@ -94,30 +87,22 @@ describe('positions', function() { }); var positions = []; - csso.walk(ast, function(node) { - positions.push([node[0].line, node[0].column, node[1]]); + csso.internal.walk(ast, function(node) { + positions.unshift([node.info.line, node.info.column, node.type]); }, true); assert.deepEqual(positions, [ - [3, 5, 'stylesheet'], - [3, 5, 'ruleset'], - [3, 5, 'selector'], - [3, 5, 'simpleselector'], - [3, 5, 'clazz'], - [3, 6, 'ident'], - [3, 9, 'clazz'], - [3, 10, 'ident'], - [3, 13, 's'], - [3, 14, 'block'], - [3, 15, 's'], - [4, 3, 'declaration'], - [4, 3, 'property'], - [4, 3, 'ident'], - [4, 12, 'value'], - [4, 12, 's'], - [4, 13, 'ident'], - [4, 18, 'decldelim'], - [4, 19, 's'] + [3, 5, 'StyleSheet'], + [3, 5, 'Ruleset'], + [3, 14, 'Block'], + [4, 3, 'Declaration'], + [4, 12, 'Value'], + [4, 13, 'Identifier'], + [4, 3, 'Property'], + [3, 5, 'Selector'], + [3, 5, 'SimpleSelector'], + [3, 9, 'Class'], + [3, 5, 'Class'] ]); }); }); diff --git a/test/sourceMaps.js b/test/sourceMaps.js index b7b957c1..53ca0b1b 100644 --- a/test/sourceMaps.js +++ b/test/sourceMaps.js @@ -2,9 +2,8 @@ var fs = require('fs'); var assert = require('assert'); var csso = require('../lib/index.js'); var SourceMapConsumer = require('source-map').SourceMapConsumer; -var gonzalesToInternal = require('../lib/compressor/ast/gonzalesToInternal.js'); var internalTranslateWithSourceMap = require('../lib/compressor/ast/translateWithSourceMap.js'); -var forEachTest = require('./fixture/internal').forEachTest; +var forEachTest = require('./fixture/parse').forEachTest; var css = '.a { color: #ff0000; }\n.b { display: block; float: left; }'; var minifiedCss = '.a{color:red}.b{display:block;float:left}'; var anonymousMap = defineSourceMap(''); @@ -71,10 +70,9 @@ function extractSourceMap(source) { function createInternalTranslateWidthSourceMapTest(name, test, scope) { it(name, function() { var ast = csso.parse(test.source, scope, true); - var internalAst = gonzalesToInternal(ast); // strings should be equal - assert.equal(internalTranslateWithSourceMap(internalAst).css, test.translate); + assert.equal(internalTranslateWithSourceMap(ast).css, 'translate' in test ? test.translate : test.source); }); } diff --git a/test/specificity.js b/test/specificity.js index 6dcc33fa..82fea3bd 100644 --- a/test/specificity.js +++ b/test/specificity.js @@ -1,12 +1,11 @@ var fs = require('fs'); var assert = require('assert'); var csso = require('../lib/index.js'); -var gonzalesToInternal = require('../lib/compressor/ast/gonzalesToInternal.js'); var specificity = require('../lib/compressor/restructure/prepare/specificity.js'); function createSpecificityTest(test) { it(test.selector, function() { - var ast = gonzalesToInternal(csso.parse(test.selector, 'simpleselector', true)); + var ast = csso.parse(test.selector, 'simpleselector', true); assert.equal(String(specificity(ast)), test.expected); }); From e39efc7600a483a4eb1dcabe2951678355f2c2e4 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Sun, 3 Apr 2016 23:14:07 +0300 Subject: [PATCH 06/19] remove gonzales related code, move compressor/ast -> utils, no more internal --- lib/compressor/ast/translate.js | 168 ---------- lib/compressor/clean/index.js | 2 +- lib/compressor/compress/Atrule.js | 2 +- lib/compressor/compress/Value.js | 2 +- lib/compressor/compress/index.js | 2 +- lib/compressor/index.js | 2 +- .../restructure/1-initialMergeRuleset.js | 4 +- lib/compressor/restructure/2-mergeAtrule.js | 4 +- .../restructure/3-disjoinRuleset.js | 4 +- .../restructure/4-restructShorthand.js | 6 +- lib/compressor/restructure/6-restructBlock.js | 10 +- lib/compressor/restructure/7-mergeRuleset.js | 4 +- .../restructure/8-restructRuleset.js | 4 +- .../prepare/createDeclarationIndexer.js | 2 +- lib/compressor/restructure/prepare/index.js | 8 +- .../restructure/prepare/processSelector.js | 2 +- lib/index.js | 31 +- lib/utils/cleanInfo.js | 11 - lib/{compressor/ast => utils}/names.js | 0 lib/utils/stringify.js | 53 --- lib/utils/translate.js | 301 ++++++++---------- .../ast => utils}/translateWithSourceMap.js | 0 lib/{compressor/ast => utils}/walk.js | 0 lib/utils/walker.js | 88 ----- test/ast.js | 9 +- test/common.js | 2 +- test/compress.js | 2 +- test/parse.js | 6 +- test/sourceMaps.js | 2 +- 29 files changed, 185 insertions(+), 546 deletions(-) delete mode 100644 lib/compressor/ast/translate.js delete mode 100644 lib/utils/cleanInfo.js rename lib/{compressor/ast => utils}/names.js (100%) delete mode 100644 lib/utils/stringify.js rename lib/{compressor/ast => utils}/translateWithSourceMap.js (100%) rename lib/{compressor/ast => utils}/walk.js (100%) delete mode 100644 lib/utils/walker.js diff --git a/lib/compressor/ast/translate.js b/lib/compressor/ast/translate.js deleted file mode 100644 index 14f7d583..00000000 --- a/lib/compressor/ast/translate.js +++ /dev/null @@ -1,168 +0,0 @@ -function each(list) { - if (list.head === null) { - return ''; - } - - if (list.head === list.tail) { - return translate(list.head.data); - } - - return list.map(translate).join(''); -} - -function eachDelim(list, delimeter) { - if (list.head === null) { - return ''; - } - - if (list.head === list.tail) { - return translate(list.head.data); - } - - return list.map(translate).join(delimeter); -} - -function translate(node) { - switch (node.type) { - case 'StyleSheet': - return each(node.rules); - - case 'Atrule': - var result = '@' + node.name; - - if (node.expression && !node.expression.sequence.isEmpty()) { - result += ' ' + translate(node.expression); - } - - if (node.block) { - return result + '{' + translate(node.block) + '}'; - } else { - return result + ';'; - } - - case 'Ruleset': - return translate(node.selector) + '{' + translate(node.block) + '}'; - - case 'Selector': - return eachDelim(node.selectors, ','); - - 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) + ' '; - } - - return translate(node); - }).join(''); - - case 'Declaration': - return translate(node.property) + ':' + translate(node.value); - - case 'Property': - return node.name; - - case 'Value': - return node.important - ? each(node.sequence) + '!important' - : each(node.sequence); - - case 'Attribute': - var result = translate(node.name); - - if (node.operator !== null) { - result += node.operator; - - if (node.value !== null) { - result += translate(node.value); - - if (node.flags !== null) { - result += (node.value.type !== 'String' ? ' ' : '') + node.flags; - } - } - } - - return '[' + result + ']'; - - case 'FunctionalPseudo': - return ':' + node.name + '(' + eachDelim(node.arguments, ',') + ')'; - - case 'Function': - return node.name + '(' + eachDelim(node.arguments, ',') + ')'; - - case 'Block': - return eachDelim(node.declarations, ';'); - - case 'Negation': - return ':not(' + eachDelim(node.sequence, ',') + ')'; - - case 'Braces': - return node.open + each(node.sequence) + node.close; - - case 'Argument': - case 'AtruleExpression': - return each(node.sequence); - - 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; diff --git a/lib/compressor/clean/index.js b/lib/compressor/clean/index.js index 6d599af9..750ee44e 100644 --- a/lib/compressor/clean/index.js +++ b/lib/compressor/clean/index.js @@ -1,4 +1,4 @@ -var walk = require('../ast/walk.js').all; +var walk = require('../../utils/walk.js').all; var handlers = { Space: require('./Space.js'), Atrule: require('./Atrule.js'), diff --git a/lib/compressor/compress/Atrule.js b/lib/compressor/compress/Atrule.js index 3fa8409c..c5410ab9 100644 --- a/lib/compressor/compress/Atrule.js +++ b/lib/compressor/compress/Atrule.js @@ -1,4 +1,4 @@ -var resolveKeyword = require('../ast/names.js').keyword; +var resolveKeyword = require('../../utils/names.js').keyword; var compressKeyframes = require('./atrule/keyframes.js'); module.exports = function(node) { diff --git a/lib/compressor/compress/Value.js b/lib/compressor/compress/Value.js index a5ca9273..8196ba82 100644 --- a/lib/compressor/compress/Value.js +++ b/lib/compressor/compress/Value.js @@ -1,4 +1,4 @@ -var resolveName = require('../ast/names.js').property; +var resolveName = require('../../utils/names.js').property; var handlers = { 'font': require('./property/font.js'), 'font-weight': require('./property/font-weight.js'), diff --git a/lib/compressor/compress/index.js b/lib/compressor/compress/index.js index ea5df097..ff6b0296 100644 --- a/lib/compressor/compress/index.js +++ b/lib/compressor/compress/index.js @@ -1,4 +1,4 @@ -var walk = require('../ast/walk.js').all; +var walk = require('../../utils/walk.js').all; var handlers = { Atrule: require('./Atrule.js'), Attribute: require('./Attribute.js'), diff --git a/lib/compressor/index.js b/lib/compressor/index.js index 02114504..ff50d007 100644 --- a/lib/compressor/index.js +++ b/lib/compressor/index.js @@ -3,7 +3,7 @@ var usageUtils = require('./usage'); var clean = require('./clean'); var compress = require('./compress'); var restructureBlock = require('./restructure'); -var walkRules = require('./ast/walk').rules; +var walkRules = require('../utils/walk').rules; function readBlock(stylesheet) { var buffer = new List(); diff --git a/lib/compressor/restructure/1-initialMergeRuleset.js b/lib/compressor/restructure/1-initialMergeRuleset.js index e11368e2..0e9612e6 100644 --- a/lib/compressor/restructure/1-initialMergeRuleset.js +++ b/lib/compressor/restructure/1-initialMergeRuleset.js @@ -1,5 +1,5 @@ var utils = require('./utils.js'); -var internalWalkRules = require('../ast/walk.js').rules; +var walkRules = require('../../utils/walk.js').rules; function processRuleset(node, item, list) { var selectors = node.selector.selectors; @@ -40,7 +40,7 @@ function processRuleset(node, item, list) { // ruleset. When direction right to left unmerged rulesets may prevent lookup // TODO: remove initial merge module.exports = function initialMergeRuleset(ast) { - internalWalkRules(ast, function(node, item, list) { + walkRules(ast, function(node, item, list) { if (node.type === 'Ruleset') { processRuleset(node, item, list); } diff --git a/lib/compressor/restructure/2-mergeAtrule.js b/lib/compressor/restructure/2-mergeAtrule.js index 4a8004a5..d07318f7 100644 --- a/lib/compressor/restructure/2-mergeAtrule.js +++ b/lib/compressor/restructure/2-mergeAtrule.js @@ -1,4 +1,4 @@ -var internalWalkRulesRight = require('../ast/walk.js').rulesRight; +var walkRulesRight = require('../../utils/walk.js').rulesRight; function isMediaRule(node) { return node.type === 'Atrule' && node.name === 'media'; @@ -27,7 +27,7 @@ function processAtrule(node, item, list) { }; module.exports = function rejoinAtrule(ast) { - internalWalkRulesRight(ast, function(node, item, list) { + walkRulesRight(ast, function(node, item, list) { if (node.type === 'Atrule') { processAtrule(node, item, list); } diff --git a/lib/compressor/restructure/3-disjoinRuleset.js b/lib/compressor/restructure/3-disjoinRuleset.js index 381bd201..6df4f807 100644 --- a/lib/compressor/restructure/3-disjoinRuleset.js +++ b/lib/compressor/restructure/3-disjoinRuleset.js @@ -1,5 +1,5 @@ var List = require('../../utils/list.js'); -var internalWalkRulesRight = require('../ast/walk.js').rulesRight; +var walkRulesRight = require('../../utils/walk.js').rulesRight; function processRuleset(node, item, list) { var selectors = node.selector.selectors; @@ -34,7 +34,7 @@ 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') { processRuleset(node, item, list); } diff --git a/lib/compressor/restructure/4-restructShorthand.js b/lib/compressor/restructure/4-restructShorthand.js index ef48fdf4..37ca2044 100644 --- a/lib/compressor/restructure/4-restructShorthand.js +++ b/lib/compressor/restructure/4-restructShorthand.js @@ -1,6 +1,6 @@ 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; var REPLACE = 1; var REMOVE = 2; @@ -384,7 +384,7 @@ module.exports = function restructBlock(ast, indexer) { var stylesheetMap = {}; var shortDeclarations = []; - internalWalkRulesRight(ast, function(node) { + walkRulesRight(ast, function(node) { if (node.type !== 'Ruleset') { return; } diff --git a/lib/compressor/restructure/6-restructBlock.js b/lib/compressor/restructure/6-restructBlock.js index 9d33b47f..5751e011 100644 --- a/lib/compressor/restructure/6-restructBlock.js +++ b/lib/compressor/restructure/6-restructBlock.js @@ -1,7 +1,7 @@ -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 = { 'src': 1 // https://github.com/afelix/csso/issues/50 }; @@ -219,7 +219,7 @@ module.exports = function restructBlock(ast) { var stylesheetMap = {}; var fingerprints = Object.create(null); - internalWalkRulesRight(ast, function(node, item, list) { + walkRulesRight(ast, function(node, item, list) { if (node.type !== 'Ruleset') { return; } diff --git a/lib/compressor/restructure/7-mergeRuleset.js b/lib/compressor/restructure/7-mergeRuleset.js index fbccdc13..0ae7edb6 100644 --- a/lib/compressor/restructure/7-mergeRuleset.js +++ b/lib/compressor/restructure/7-mergeRuleset.js @@ -1,5 +1,5 @@ var utils = require('./utils.js'); -var internalWalkRules = require('../ast/walk.js').rules; +var walkRules = require('../../utils/walk.js').rules; /* At this step all rules has single simple selector. We try to join by equal @@ -79,7 +79,7 @@ function processRuleset(node, item, list) { }; module.exports = function mergeRuleset(ast) { - internalWalkRules(ast, function(node, item, list) { + walkRules(ast, function(node, item, list) { if (node.type === 'Ruleset') { processRuleset(node, item, list); } diff --git a/lib/compressor/restructure/8-restructRuleset.js b/lib/compressor/restructure/8-restructRuleset.js index efce4473..9d31b087 100644 --- a/lib/compressor/restructure/8-restructRuleset.js +++ b/lib/compressor/restructure/8-restructRuleset.js @@ -1,6 +1,6 @@ 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; function calcSelectorLength(list) { var length = 0; @@ -134,7 +134,7 @@ function processRuleset(node, item, list) { }; module.exports = function restructRuleset(ast) { - internalWalkRulesRight(ast, function(node, item, list) { + walkRulesRight(ast, function(node, item, list) { if (node.type === 'Ruleset') { processRuleset.call(this, node, item, list); } diff --git a/lib/compressor/restructure/prepare/createDeclarationIndexer.js b/lib/compressor/restructure/prepare/createDeclarationIndexer.js index c5981970..c5235309 100644 --- a/lib/compressor/restructure/prepare/createDeclarationIndexer.js +++ b/lib/compressor/restructure/prepare/createDeclarationIndexer.js @@ -1,4 +1,4 @@ -var translate = require('../../ast/translate.js'); +var translate = require('../../../utils/translate.js'); function Index() { this.seed = 0; diff --git a/lib/compressor/restructure/prepare/index.js b/lib/compressor/restructure/prepare/index.js index fa45ee06..075dc5f1 100644 --- a/lib/compressor/restructure/prepare/index.js +++ b/lib/compressor/restructure/prepare/index.js @@ -1,6 +1,6 @@ -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'); var processSelector = require('./processSelector.js'); @@ -34,7 +34,7 @@ function walk(node, markDeclaration, usageData) { module.exports = function prepare(ast, usageData) { var markDeclaration = createDeclarationIndexer(); - internalWalkRules(ast, function(node) { + walkRules(ast, function(node) { walk(node, markDeclaration, usageData); }); diff --git a/lib/compressor/restructure/prepare/processSelector.js b/lib/compressor/restructure/prepare/processSelector.js index 642a4af7..7a4590c9 100644 --- a/lib/compressor/restructure/prepare/processSelector.js +++ b/lib/compressor/restructure/prepare/processSelector.js @@ -1,4 +1,4 @@ -var translate = require('../../ast/translate.js'); +var translate = require('../../../utils/translate.js'); var specificity = require('./specificity.js'); var nonFreezePseudoElements = { diff --git a/lib/index.js b/lib/index.js index 7db78cd5..5078f679 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,12 +1,9 @@ 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 translateWithSourceMap = require('./utils/translateWithSourceMap'); +var walkers = require('./utils/walk'); var stringify = require('./utils/stringify'); -var cleanInfo = require('./utils/cleanInfo'); var justDoIt = function(src, noStructureOptimizations, needInfo) { console.warn('`csso.justDoIt()` method is deprecated, use `csso.minify()` instead'); @@ -16,7 +13,7 @@ var justDoIt = function(src, noStructureOptimizations, needInfo) { restructure: !noStructureOptimizations }); - return traslateInternal(compressed); + return translate(compressed); }; function debugOutput(name, options, startTime, data) { @@ -38,7 +35,7 @@ function createDefaultLogger(level) { } if (level > 1 && ast) { - var css = traslateInternal(ast, true); + var css = translate(ast, true); // when level 2, limit css to 256 symbols if (level === 2 && css.length > 256) { @@ -96,14 +93,14 @@ function minify(context, source, options) { // translate if (options.sourceMap) { result = debugOutput('translateWithSourceMap', options, Date.now(), (function() { - var tmp = traslateInternalWithSourceMap(compressedAst); + var tmp = translateWithSourceMap(compressedAst); tmp.map._file = filename; // since other tools can relay on file in source map transform chain tmp.map.setSourceContent(filename, source); return tmp; })()); } else { result = debugOutput('translate', options, Date.now(), - traslateInternal(compressedAst) + translate(compressedAst) ); } @@ -129,19 +126,11 @@ module.exports = { parse: parse, compress: compress, translate: translate, + translateWithSourceMap: translateWithSourceMap, - walk: walk, - stringify: stringify, - cleanInfo: cleanInfo, - - // internal ast - internal: { - translate: traslateInternal, - translateWithSourceMap: traslateInternalWithSourceMap, - walk: internalWalkers.all, - walkRules: internalWalkers.rules, - walkRulesRight: internalWalkers.rulesRight - }, + walk: walkers.all, + walkRules: walkers.rules, + walkRulesRight: walkers.rulesRight, // deprecated justDoIt: justDoIt diff --git a/lib/utils/cleanInfo.js b/lib/utils/cleanInfo.js deleted file mode 100644 index 51f6560a..00000000 --- a/lib/utils/cleanInfo.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function cleanInfo(tree) { - var res = tree.slice(1); - - for (var i = 1, token; token = res[i]; i++) { - if (Array.isArray(token)) { - res[i] = cleanInfo(token); - } - } - - return res; -}; diff --git a/lib/compressor/ast/names.js b/lib/utils/names.js similarity index 100% rename from lib/compressor/ast/names.js rename to lib/utils/names.js diff --git a/lib/utils/stringify.js b/lib/utils/stringify.js deleted file mode 100644 index 161d1ee9..00000000 --- a/lib/utils/stringify.js +++ /dev/null @@ -1,53 +0,0 @@ -function indent(num) { - return new Array(num + 1).join(' '); -} - -function escape(str) { - return str - .replace(/\\/g, '\\\\') - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - .replace(/"/g, '\\"'); -} - -module.exports = function stringify(val, level) { - level = level || 0; - - if (typeof val == 'string') { - return '"' + escape(val) + '"'; - } - - if (val && val.constructor === Object) { - var body = Object.keys(val).map(function(k) { - return indent(level + 1) + '"' + escape(k) + '": ' + stringify(val[k], level + 1); - }).join(',\n'); - - return '{' + (body ? '\n' + body + '\n' + indent(level) : '') + '}'; - } - - if (Array.isArray(val)) { - var join = true; - var body = val.map(function(item, idx) { - var prefix = idx ? ' ' : ''; - - if (Array.isArray(item) && (!join || !item.some(Array.isArray))) { - prefix = '\n' + indent(level + 1); - join = false; - } - - return prefix + stringify(item, level + 1); - }).join(','); - - if (/\n/.test(body)) { - body = - '\n' + indent(level + 1) + - body + - '\n' + indent(level); - } - - return '[' + body + ']'; - } - - return String(val); -}; diff --git a/lib/utils/translate.js b/lib/utils/translate.js index 1bedf8c6..14f7d583 100644 --- a/lib/utils/translate.js +++ b/lib/utils/translate.js @@ -1,197 +1,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, - - 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(','); +function each(list) { + if (list.head === null) { + return ''; } -}; -function simple(token) { - buffer.push(token[useInfo + 1]); + if (list.head === list.tail) { + return translate(list.head.data); + } + + 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, ',') + ')'; + + case 'Braces': + return node.open + each(node.sequence) + node.close; + + case 'Argument': + case 'AtruleExpression': + return each(node.sequence); + + 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; -module.exports = function(tree, hasInfo) { - useInfo = hasInfo ? 1 : 0; - buffer = []; + case 'Dimension': + return node.value + node.unit; - translate(tree); + 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); + } +} - return buffer.join(''); -}; +module.exports = translate; diff --git a/lib/compressor/ast/translateWithSourceMap.js b/lib/utils/translateWithSourceMap.js similarity index 100% rename from lib/compressor/ast/translateWithSourceMap.js rename to lib/utils/translateWithSourceMap.js diff --git a/lib/compressor/ast/walk.js b/lib/utils/walk.js similarity index 100% rename from lib/compressor/ast/walk.js rename to lib/utils/walk.js diff --git a/lib/utils/walker.js b/lib/utils/walker.js deleted file mode 100644 index bc050071..00000000 --- a/lib/utils/walker.js +++ /dev/null @@ -1,88 +0,0 @@ -var offset; -var process; - -function walk(token, parent, stack) { - process(token, parent, stack); - stack.push(token); - - switch (token[offset + 0]) { - case 'simpleselector': - case 'dimension': - case 'selector': - case 'property': - case 'value': - case 'filterv': - case 'progid': - case 'ruleset': - case 'atruleb': - case 'atrulerq': - case 'atrulers': - case 'stylesheet': - case 'attrib': - case 'important': - case 'block': - case 'atrules': - case 'uri': - for (var i = offset + 1; i < token.length; i++) { - walk(token[i], token, stack); - } - break; - - case 'percentage': - case 'clazz': - case 'atkeyword': - case 'pseudoe': - case 'pseudoc': - walk(token[offset + 1], token, stack); - break; - - case 'declaration': - case 'filter': - walk(token[offset + 1], token, stack); - walk(token[offset + 2], token, stack); - break; - - case 'atruler': - walk(token[offset + 1], token, stack); - walk(token[offset + 2], token, stack); - walk(token[offset + 3], token, stack); - break; - - case 'braces': - for (var i = offset + 3; i < token.length; i++) { - walk(token[i], token, stack); - } - break; - - case 'nthselector': - process(token[offset + 1], token, stack); - for (var i = offset + 2; i < token.length; i++) { - walk(token[i], token, stack); - } - break; - - case 'funktion': - process(token[offset + 1], token, stack); - process(token[offset + 2], token, stack); - - token = token[offset + 2]; - stack.push(token); - for (var i = offset + 1; i < token.length; i++) { - walk(token[i], token, stack); - } - stack.pop(); - break; - } - - stack.pop(); -} - -module.exports = function(tree, fn, hasInfo) { - offset = hasInfo ? 1 : 0; - - if (typeof fn === 'function') { - process = fn; - - walk(tree, null, []); - } -}; diff --git a/test/ast.js b/test/ast.js index e359a0e8..165d594f 100644 --- a/test/ast.js +++ b/test/ast.js @@ -1,12 +1,11 @@ var assert = require('assert'); var csso = require('../lib/index.js'); -var internalWalkAll = require('../lib/compressor/ast/walk.js').all; -var internalWalkRules = require('../lib/compressor/ast/walk.js').rules; -var internalWalkRulesRight = require('../lib/compressor/ast/walk.js').rulesRight; -var internalTranslate = require('../lib/compressor/ast/translate.js'); +var internalWalkAll = require('../lib/utils/walk.js').all; +var internalWalkRules = require('../lib/utils/walk.js').rules; +var internalWalkRulesRight = require('../lib/utils/walk.js').rulesRight; +var internalTranslate = require('../lib/utils/translate.js'); var testFiles = require('./fixture/parse').tests; var forEachTest = require('./fixture/parse').forEachTest; -var stringifyInternalAST = require('./helpers/stringify'); function expectedInternalWalk(ast) { function walk(node) { diff --git a/test/common.js b/test/common.js index 0c07d3d9..e3c52408 100644 --- a/test/common.js +++ b/test/common.js @@ -33,7 +33,7 @@ describe('csso', function() { function visit(withInfo) { var visitedTypes = {}; - csso.internal.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet', withInfo), function(node) { + csso.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet', withInfo), function(node) { visitedTypes[node.type] = true; }, withInfo); diff --git a/test/compress.js b/test/compress.js index b1b3556c..db651671 100644 --- a/test/compress.js +++ b/test/compress.js @@ -1,7 +1,7 @@ var path = require('path'); var assert = require('assert'); var csso = require('../lib/index.js'); -var internalTranslate = require('../lib/compressor/ast/translate.js'); +var internalTranslate = require('../lib/utils/translate.js'); var tests = require('./fixture/compress'); function normalize(str) { diff --git a/test/parse.js b/test/parse.js index dbd60c75..408cea6a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -32,7 +32,7 @@ describe('parse', function() { assert.equal(stringify(ast), stringify(test.ast)); // translated AST should be equal to original source - assert.equal(csso.internal.translate(ast), 'translate' in test ? test.translate : test.source); + assert.equal(csso.translate(ast), 'translate' in test ? test.translate : test.source); }); }); }); @@ -59,7 +59,7 @@ describe('positions', function() { var ast = csso.parse('.foo.bar {\n property: value;\n}', null, true); var positions = []; - csso.internal.walk(ast, function(node) { + csso.walk(ast, function(node) { positions.unshift([node.info.line, node.info.column, node.type]); }, true); @@ -87,7 +87,7 @@ describe('positions', function() { }); var positions = []; - csso.internal.walk(ast, function(node) { + csso.walk(ast, function(node) { positions.unshift([node.info.line, node.info.column, node.type]); }, true); diff --git a/test/sourceMaps.js b/test/sourceMaps.js index 53ca0b1b..79e2a11f 100644 --- a/test/sourceMaps.js +++ b/test/sourceMaps.js @@ -2,7 +2,7 @@ var fs = require('fs'); var assert = require('assert'); var csso = require('../lib/index.js'); var SourceMapConsumer = require('source-map').SourceMapConsumer; -var internalTranslateWithSourceMap = require('../lib/compressor/ast/translateWithSourceMap.js'); +var internalTranslateWithSourceMap = require('../lib/utils/translateWithSourceMap.js'); var forEachTest = require('./fixture/parse').forEachTest; var css = '.a { color: #ff0000; }\n.b { display: block; float: left; }'; var minifiedCss = '.a{color:red}.b{display:block;float:left}'; From 65b05e79a697f1dd26cb6265888eb6953d24f8a9 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Sun, 3 Apr 2016 23:15:42 +0300 Subject: [PATCH 07/19] drop justDoIt() --- lib/index.js | 19 ++----------------- test/common.js | 21 --------------------- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/lib/index.js b/lib/index.js index 5078f679..f80f0b28 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,18 +3,6 @@ var compress = require('./compressor'); var translate = require('./utils/translate'); var translateWithSourceMap = require('./utils/translateWithSourceMap'); var walkers = require('./utils/walk'); -var stringify = require('./utils/stringify'); - -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 - }); - - return translate(compressed); -}; function debugOutput(name, options, startTime, data) { if (options.debug) { @@ -118,7 +106,7 @@ function minifyBlock(source, options) { module.exports = { version: require('../package.json').version, - // main method + // main methods minify: minifyStylesheet, minifyBlock: minifyBlock, @@ -130,8 +118,5 @@ module.exports = { walk: walkers.all, walkRules: walkers.rules, - walkRulesRight: walkers.rulesRight, - - // deprecated - justDoIt: justDoIt + walkRulesRight: walkers.rulesRight }; diff --git a/test/common.js b/test/common.js index e3c52408..1d49fd40 100644 --- a/test/common.js +++ b/test/common.js @@ -8,27 +8,6 @@ function normalize(str) { } describe('csso', function() { - it('justDoIt() should works until removed', function() { - var output = []; - var tmp = console.warn; - - try { - console.warn = function() { - output.push(Array.prototype.slice.call(arguments).join(' ')); - }; - - assert.equal( - csso.justDoIt('.foo { color: #ff0000 } .bar { color: rgb(255, 0, 0) }'), - '.bar,.foo{color:red}' - ); - } finally { - console.warn = tmp; - } - - assert.equal(output.length, 1); - assert(/method is deprecated/.test(String(output[0])), 'should contains `method is deprecated`'); - }); - it('walk', function() { function visit(withInfo) { var visitedTypes = {}; From 6edeb0e13fdd152c6c371a31e464d1c035156108 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Mon, 4 Apr 2016 00:13:57 +0300 Subject: [PATCH 08/19] fix isEqualLists deoptimisation --- lib/compressor/restructure/1-initialMergeRuleset.js | 2 +- lib/compressor/restructure/utils.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compressor/restructure/1-initialMergeRuleset.js b/lib/compressor/restructure/1-initialMergeRuleset.js index 0e9612e6..036a04b4 100644 --- a/lib/compressor/restructure/1-initialMergeRuleset.js +++ b/lib/compressor/restructure/1-initialMergeRuleset.js @@ -24,7 +24,7 @@ function processRuleset(node, item, list) { } // try to join by declarations - if (utils.isEqualLists(declarations, prevDeclarations)) { + if (utils.isEqualDeclarations(declarations, prevDeclarations)) { utils.addSelectors(prevSelectors, selectors); list.remove(item); return true; diff --git a/lib/compressor/restructure/utils.js b/lib/compressor/restructure/utils.js index c37f4098..70c92c51 100644 --- a/lib/compressor/restructure/utils.js +++ b/lib/compressor/restructure/utils.js @@ -4,7 +4,7 @@ function isEqualLists(a, b) { var cursor1 = a.head; var cursor2 = b.head; - while (cursor1 && cursor2 && cursor1.data.id === cursor2.data.id) { + while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { cursor1 = cursor1.next; cursor2 = cursor2.next; } @@ -16,7 +16,7 @@ function isEqualDeclarations(a, b) { var cursor1 = a.head; var cursor2 = b.head; - while (cursor1 && cursor2 && cursor1.data.id === cursor2.data.id) { + while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { cursor1 = cursor1.next; cursor2 = cursor2.next; } From fdd1a2b05687609e0cf9789d157b05e3e518373a Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Mon, 4 Apr 2016 00:18:25 +0300 Subject: [PATCH 09/19] remove unused --- lib/parser/const.js | 50 --------------------------------------------- lib/parser/index.js | 11 ---------- 2 files changed, 61 deletions(-) diff --git a/lib/parser/const.js b/lib/parser/const.js index c486e44e..4c2d4000 100644 --- a/lib/parser/const.js +++ b/lib/parser/const.js @@ -44,53 +44,3 @@ exports.TokenType = { // for (var key in exports.TokenType) { // exports.TokenType[key] = i++; // } - -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' -}; diff --git a/lib/parser/index.js b/lib/parser/index.js index 7069cc1b..a52873ff 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -1,7 +1,6 @@ 'use strict'; var TokenType = require('./const').TokenType; -var NodeType = require('./const').NodeType; var Scanner = require('./scanner'); var List = require('../utils/list'); var needPositions; @@ -1586,16 +1585,6 @@ function tryGetNumber() { return null; } -function getNumber() { - var number = tryGetNumber(); - - if (!number) { - parseError('Wrong number'); - } - - return number; -} - // '/' | '*' | ',' | ':' | '=' | '+' | '-' // TODO: remove '=' since it's wrong operator, but theat as operator // to make old things like `filter: alpha(opacity=0)` works From 1d73363ee782eecdd816e149d17bf3e5aec86f4a Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Mon, 4 Apr 2016 00:24:39 +0300 Subject: [PATCH 10/19] minify is always returns object now and some internal renaming --- lib/index.js | 7 ++++--- test/ast.js | 36 ++++++++++++++++++------------------ test/compress.js | 24 ++++++++++++------------ test/sourceMaps.js | 8 ++++---- test/usage.js | 16 ++++++++-------- 5 files changed, 46 insertions(+), 45 deletions(-) diff --git a/lib/index.js b/lib/index.js index f80f0b28..6798856b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -87,9 +87,10 @@ function minify(context, source, options) { return tmp; })()); } else { - result = debugOutput('translate', options, Date.now(), - translate(compressedAst) - ); + result = debugOutput('translate', options, Date.now(), { + css: translate(compressedAst), + map: null + }); } return result; diff --git a/test/ast.js b/test/ast.js index 165d594f..ea71c7f1 100644 --- a/test/ast.js +++ b/test/ast.js @@ -1,13 +1,13 @@ var assert = require('assert'); var csso = require('../lib/index.js'); -var internalWalkAll = require('../lib/utils/walk.js').all; -var internalWalkRules = require('../lib/utils/walk.js').rules; -var internalWalkRulesRight = require('../lib/utils/walk.js').rulesRight; -var internalTranslate = require('../lib/utils/translate.js'); +var walkAll = require('../lib/utils/walk.js').all; +var walkRules = require('../lib/utils/walk.js').rules; +var walkRulesRight = require('../lib/utils/walk.js').rulesRight; +var translate = require('../lib/utils/translate.js'); var testFiles = require('./fixture/parse').tests; var forEachTest = require('./fixture/parse').forEachTest; -function expectedInternalWalk(ast) { +function expectedWalk(ast) { function walk(node) { result.push(node.type); for (var key in node) { @@ -26,21 +26,21 @@ function expectedInternalWalk(ast) { return result; } -function createInternalWalkAllTest(name, test, scope) { +function createWalkAllTest(name, test, scope) { it(name, function() { - var internalAst = csso.parse(test.source, scope, true); + var ast = csso.parse(test.source, scope, true); var actual = []; - internalWalkAll(internalAst, function(node) { + walkAll(ast, function(node) { actual.push(node.type); }); // type arrays should be equal - assert.equal(actual.sort().join(','), expectedInternalWalk(test.ast).sort().join(',')); + assert.equal(actual.sort().join(','), expectedWalk(test.ast).sort().join(',')); }); } -function createInternalWalkRulesTest(name, test, scope, walker) { +function createWalkRulesTest(name, test, scope, walker) { it(name, function() { var ast = csso.parse(test.source, scope, true); var actual = []; @@ -52,25 +52,25 @@ function createInternalWalkRulesTest(name, test, scope, walker) { // type arrays should be equal assert.equal( actual.sort().join(','), - expectedInternalWalk(test.ast).filter(function(type) { + expectedWalk(test.ast).filter(function(type) { return type === 'Ruleset' || type === 'Atrule'; }).sort().join(',') ); }); } -function createInternalTranslateTest(name, test, scope) { +function createTranslateTest(name, test, scope) { it(name, function() { var ast = csso.parse(test.source, scope, true); // strings should be equal - assert.equal(internalTranslate(ast), 'translate' in test ? test.translate : test.source); + assert.equal(translate(ast), 'translate' in test ? test.translate : test.source); }); } describe('AST', function() { describe('walk all', function() { - forEachTest(createInternalWalkAllTest); + forEachTest(createWalkAllTest); }); describe('walk ruleset', function() { @@ -83,7 +83,7 @@ describe('AST', function() { filename === 'stylesheet.json' || filename === 'ruleset.json') { for (var name in file.tests) { - createInternalWalkRulesTest(file.locator.get(name), file.tests[name], file.scope, internalWalkRules); + createWalkRulesTest(file.locator.get(name), file.tests[name], file.scope, walkRules); } } }; @@ -99,17 +99,17 @@ describe('AST', function() { filename === 'stylesheet.json' || filename === 'ruleset.json') { for (var name in file.tests) { - createInternalWalkRulesTest(file.locator.get(name), file.tests[name], file.scope, internalWalkRulesRight); + createWalkRulesTest(file.locator.get(name), file.tests[name], file.scope, walkRulesRight); } } }; }); describe('translate', function() { - forEachTest(createInternalTranslateTest); + forEachTest(createTranslateTest); assert.throws(function() { - internalTranslate({ + translate({ type: 'xxx' }); }, /Unknown node type/); diff --git a/test/compress.js b/test/compress.js index db651671..9dc6a975 100644 --- a/test/compress.js +++ b/test/compress.js @@ -1,7 +1,7 @@ var path = require('path'); var assert = require('assert'); var csso = require('../lib/index.js'); -var internalTranslate = require('../lib/utils/translate.js'); +var translate = require('../lib/utils/translate.js'); var tests = require('./fixture/compress'); function normalize(str) { @@ -12,7 +12,7 @@ function createMinifyTest(name, test) { var testFn = function() { var compressed = csso.minify(test.source); - assert.equal(normalize(compressed), normalize(test.compressed)); + assert.equal(normalize(compressed.css), normalize(test.compressed)); }; if (path.basename(name)[0] === '_') { @@ -26,7 +26,7 @@ function createCompressTest(name, test) { var testFn = function() { var ast = csso.parse(test.source, 'stylesheet', true); var compressedAst = csso.compress(ast); - var css = internalTranslate(compressedAst); + var css = translate(compressedAst); assert.equal(normalize(css), normalize(test.compressed)); }; @@ -55,7 +55,7 @@ describe('compress', function() { it('should compress block', function() { var compressed = csso.minifyBlock('color: rgba(255, 0, 0, 1); width: 0px; color: #ff0000'); - assert.equal(compressed, 'width:0;color:red'); + assert.equal(compressed.css, 'width:0;color:red'); }); it('should not affect options', function() { @@ -71,22 +71,22 @@ describe('compress', function() { var css = '.a{color:red}.b{color:red}'; it('should apply `restructure` option', function() { - assert.equal(csso.minify(css, { restructure: false }), css); - assert.equal(csso.minify(css, { restructure: true }), '.a,.b{color:red}'); + assert.equal(csso.minify(css, { restructure: false }).css, css); + assert.equal(csso.minify(css, { restructure: true }).css, '.a,.b{color:red}'); }); it('`restructuring` is alias for `restructure`', function() { - assert.equal(csso.minify(css, { restructuring: false }), css); - assert.equal(csso.minify(css, { restructuring: true }), '.a,.b{color:red}'); + assert.equal(csso.minify(css, { restructuring: false }).css, css); + assert.equal(csso.minify(css, { restructuring: true }).css, '.a,.b{color:red}'); }); it('`restructure` option should has higher priority', function() { - assert.equal(csso.minify(css, { restructure: false, restructuring: true }), css); - assert.equal(csso.minify(css, { restructure: true, restructuring: false }), '.a,.b{color:red}'); + assert.equal(csso.minify(css, { restructure: false, restructuring: true }).css, css); + assert.equal(csso.minify(css, { restructure: true, restructuring: false }).css, '.a,.b{color:red}'); }); it('should restructure by default', function() { - assert.equal(csso.minify(css), '.a,.b{color:red}'); + assert.equal(csso.minify(css).css, '.a,.b{color:red}'); }); }); @@ -139,6 +139,6 @@ describe('compress', function() { }); it('should not fail if no ast passed', function() { - assert.equal(internalTranslate(csso.compress(), true), ''); + assert.equal(translate(csso.compress(), true), ''); }); }); diff --git a/test/sourceMaps.js b/test/sourceMaps.js index 79e2a11f..93149591 100644 --- a/test/sourceMaps.js +++ b/test/sourceMaps.js @@ -2,7 +2,7 @@ var fs = require('fs'); var assert = require('assert'); var csso = require('../lib/index.js'); var SourceMapConsumer = require('source-map').SourceMapConsumer; -var internalTranslateWithSourceMap = require('../lib/utils/translateWithSourceMap.js'); +var translateWithSourceMap = require('../lib/utils/translateWithSourceMap.js'); var forEachTest = require('./fixture/parse').forEachTest; var css = '.a { color: #ff0000; }\n.b { display: block; float: left; }'; var minifiedCss = '.a{color:red}.b{display:block;float:left}'; @@ -67,18 +67,18 @@ function extractSourceMap(source) { } } -function createInternalTranslateWidthSourceMapTest(name, test, scope) { +function createTranslateWidthSourceMapTest(name, test, scope) { it(name, function() { var ast = csso.parse(test.source, scope, true); // strings should be equal - assert.equal(internalTranslateWithSourceMap(ast).css, 'translate' in test ? test.translate : test.source); + assert.equal(translateWithSourceMap(ast).css, 'translate' in test ? test.translate : test.source); }); } describe('sourceMaps', function() { describe('translateWithSourceMap', function() { - forEachTest(createInternalTranslateWidthSourceMapTest); + forEachTest(createTranslateWidthSourceMapTest); }); it('should return object when sourceMap is true', function() { diff --git a/test/usage.js b/test/usage.js index b5316122..fd53c10f 100644 --- a/test/usage.js +++ b/test/usage.js @@ -12,7 +12,7 @@ function createCompressWithUsageTest(name, test) { usage: test.usage }); - assert.equal(normalize(compressed), normalize(test.compressed)); + assert.equal(normalize(compressed.css), normalize(test.compressed)); }); } @@ -30,7 +30,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, '*{p:1}'); + assert.equal(compressed.css, '*{p:1}'); }); it('should ignore wrong values', function() { @@ -42,7 +42,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, '#a,.a,a{p:1}'); + assert.equal(compressed.css, '#a,.a,a{p:1}'); }); it('should be case insensitive for tag names', function() { @@ -52,7 +52,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, 'A,b{p:1}'); + assert.equal(compressed.css, 'A,b{p:1}'); }); it('should be case sensitive for classes and ids', function() { @@ -63,7 +63,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, '#a,.A{p:1}'); + assert.equal(compressed.css, '#a,.A{p:1}'); }); describe('shouldn\'t affect classes whitelist', function() { @@ -75,7 +75,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, '.a{p:1}'); + assert.equal(compressed.css, '.a{p:1}'); }); it('when "classes" isn\'t defined', function() { @@ -85,7 +85,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, '.a,.b{p:1}'); + assert.equal(compressed.css, '.a,.b{p:1}'); }); }); @@ -108,7 +108,7 @@ describe('compress with usage', function() { } }); - assert.equal(compressed, '.foo .bar{p:1}'); + assert.equal(compressed.css, '.foo .bar{p:1}'); }); it('should throw exception when selector has classes from different scopes', function() { From 5e4106ee0d5e9978cb9bd0a213f7a6f7a0cfbb50 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Tue, 5 Apr 2016 23:31:51 +0300 Subject: [PATCH 11/19] compress() returns an object as result --- lib/compressor/index.js | 4 +++- lib/index.js | 6 +++--- test/compress.js | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/compressor/index.js b/lib/compressor/index.js index ff50d007..42fd300a 100644 --- a/lib/compressor/index.js +++ b/lib/compressor/index.js @@ -162,5 +162,7 @@ module.exports = function compress(ast, options) { }; } - return result; + return { + ast: result + }; }; diff --git a/lib/index.js b/lib/index.js index 6798856b..211a8800 100644 --- a/lib/index.js +++ b/lib/index.js @@ -74,21 +74,21 @@ function minify(context, source, options) { ); // compress - var compressedAst = debugOutput('compress', options, Date.now(), + var compressResult = debugOutput('compress', options, Date.now(), compress(ast, buildCompressOptions(options)) ); // translate if (options.sourceMap) { result = debugOutput('translateWithSourceMap', options, Date.now(), (function() { - var tmp = translateWithSourceMap(compressedAst); + var tmp = translateWithSourceMap(compressResult.ast); tmp.map._file = filename; // since other tools can relay on file in source map transform chain tmp.map.setSourceContent(filename, source); return tmp; })()); } else { result = debugOutput('translate', options, Date.now(), { - css: translate(compressedAst), + css: translate(compressResult.ast), map: null }); } diff --git a/test/compress.js b/test/compress.js index 9dc6a975..738c8396 100644 --- a/test/compress.js +++ b/test/compress.js @@ -25,7 +25,7 @@ function createMinifyTest(name, test) { function createCompressTest(name, test) { var testFn = function() { var ast = csso.parse(test.source, 'stylesheet', true); - var compressedAst = csso.compress(ast); + var compressedAst = csso.compress(ast).ast; var css = translate(compressedAst); assert.equal(normalize(css), normalize(test.compressed)); @@ -139,6 +139,6 @@ describe('compress', function() { }); it('should not fail if no ast passed', function() { - assert.equal(translate(csso.compress(), true), ''); + assert.equal(translate(csso.compress().ast, true), ''); }); }); From 751a8193b51b657be8d65ad1bbb2484e779ad682 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Tue, 5 Apr 2016 23:34:58 +0300 Subject: [PATCH 12/19] remove redundant in tests - walk() doesn't accept third argument anymore --- test/common.js | 6 +++--- test/parse.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/common.js b/test/common.js index 1d49fd40..76de58f3 100644 --- a/test/common.js +++ b/test/common.js @@ -9,12 +9,12 @@ function normalize(str) { describe('csso', function() { it('walk', function() { - function visit(withInfo) { + function visit() { var visitedTypes = {}; - csso.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet', withInfo), function(node) { + csso.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet'), function(node) { visitedTypes[node.type] = true; - }, withInfo); + }); return Object.keys(visitedTypes).sort(); } diff --git a/test/parse.js b/test/parse.js index 408cea6a..f18c3dff 100644 --- a/test/parse.js +++ b/test/parse.js @@ -61,7 +61,7 @@ describe('positions', function() { csso.walk(ast, function(node) { positions.unshift([node.info.line, node.info.column, node.type]); - }, true); + }); assert.deepEqual(positions, [ [1, 1, 'StyleSheet'], @@ -89,7 +89,7 @@ describe('positions', function() { csso.walk(ast, function(node) { positions.unshift([node.info.line, node.info.column, node.type]); - }, true); + }); assert.deepEqual(positions, [ [3, 5, 'StyleSheet'], From 4166948324406e842f066150e92bd5e065402132 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 00:07:40 +0300 Subject: [PATCH 13/19] drop old options for parse() - options should be an object, treats as empty object otherwise - drop needPositions (positions should be used instead) - drop needInfo --- lib/parser/index.js | 22 +++++++--------------- test/ast.js | 6 +++--- test/common.js | 6 ++++-- test/compress.js | 2 +- test/parse.js | 9 ++++++--- test/sourceMaps.js | 4 +++- test/specificity.js | 2 +- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/parser/index.js b/lib/parser/index.js index a52873ff..d429a70b 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -25,14 +25,13 @@ specialFunctions[SCOPE_VALUE] = { var: getVarFunction }; -var rules = { +var initialContext = { stylesheet: getStylesheet, atrule: getAtrule, atruleExpression: getAtruleExpression, ruleset: getRuleset, selector: getSelector, simpleSelector: getSimpleSelector, - simpleselector: getSimpleSelector, block: getBlock, declaration: getDeclaration, value: getValue @@ -1842,32 +1841,25 @@ function getVhash() { module.exports = function parse(source, context, options) { var ast; - options = options || {}; - if (options === true) { - options = { - positions: true, - needInfo: true - }; + throw new Error('!!'); } - if ('positions' in options) { - needPositions = options.positions || false; - } else { - // deprecated option but using for backward capability - needPositions = options.needPositions || false; + if (!options || typeof options !== 'object') { + options = {}; } + needPositions = 'positions' in options ? Boolean(options.positions) : false; filename = options.filename || ''; context = context || 'stylesheet'; - if (!rules.hasOwnProperty(context)) { + if (!initialContext.hasOwnProperty(context)) { throw new Error('Unknown context `' + context + '`'); } scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column); scanner.next(); - ast = rules[context](); + ast = initialContext[context](); scanner = null; diff --git a/test/ast.js b/test/ast.js index ea71c7f1..58caed5b 100644 --- a/test/ast.js +++ b/test/ast.js @@ -28,7 +28,7 @@ function expectedWalk(ast) { function createWalkAllTest(name, test, scope) { it(name, function() { - var ast = csso.parse(test.source, scope, true); + var ast = csso.parse(test.source, scope); var actual = []; walkAll(ast, function(node) { @@ -42,7 +42,7 @@ function createWalkAllTest(name, test, scope) { function createWalkRulesTest(name, test, scope, walker) { it(name, function() { - var ast = csso.parse(test.source, scope, true); + var ast = csso.parse(test.source, scope); var actual = []; walker(ast, function(node) { @@ -61,7 +61,7 @@ function createWalkRulesTest(name, test, scope, walker) { function createTranslateTest(name, test, scope) { it(name, function() { - var ast = csso.parse(test.source, scope, true); + var ast = csso.parse(test.source, scope); // strings should be equal assert.equal(translate(ast), 'translate' in test ? test.translate : test.source); diff --git a/test/common.js b/test/common.js index 76de58f3..730a2f3f 100644 --- a/test/common.js +++ b/test/common.js @@ -12,7 +12,7 @@ describe('csso', function() { function visit() { var visitedTypes = {}; - csso.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }', 'stylesheet'), function(node) { + csso.walk(csso.parse('@media (min-width: 200px) { .foo:nth-child(2n) { color: rgb(100%, 10%, 0%); width: calc(3px + 5%) } }'), function(node) { visitedTypes[node.type] = true; }); @@ -48,7 +48,9 @@ describe('csso', function() { it('JSON.strigify()', function() { assert.equal( - stringify(csso.parse('.a\n{\rcolor:\r\nred}', 'stylesheet', true), true), + stringify(csso.parse('.a\n{\rcolor:\r\nred}', 'stylesheet', { + positions: true + }), true), normalize(fs.readFileSync(__dirname + '/fixture/stringify.txt', 'utf-8').trim()) ); }); diff --git a/test/compress.js b/test/compress.js index 738c8396..1cf448cd 100644 --- a/test/compress.js +++ b/test/compress.js @@ -24,7 +24,7 @@ function createMinifyTest(name, test) { function createCompressTest(name, test) { var testFn = function() { - var ast = csso.parse(test.source, 'stylesheet', true); + var ast = csso.parse(test.source); var compressedAst = csso.compress(ast).ast; var css = translate(compressedAst); diff --git a/test/parse.js b/test/parse.js index f18c3dff..d17abbf1 100644 --- a/test/parse.js +++ b/test/parse.js @@ -50,13 +50,17 @@ describe('parse error', function() { tests.forEach(function(test) { createParseErrorTest(filename, test); - createParseErrorTest(filename + ' (with positions)', test, { needPositions: true }); + createParseErrorTest(filename + ' (with positions)', test, { + positions: true + }); }); }); describe('positions', function() { it('should start with line 1 column 1 by default', function() { - var ast = csso.parse('.foo.bar {\n property: value;\n}', null, true); + var ast = csso.parse('.foo.bar {\n property: value;\n}', null, { + positions: true + }); var positions = []; csso.walk(ast, function(node) { @@ -81,7 +85,6 @@ describe('positions', function() { it('should start with specified line and column', function() { var ast = csso.parse('.foo.bar {\n property: value;\n}', null, { positions: true, - needInfo: true, line: 3, column: 5 }); diff --git a/test/sourceMaps.js b/test/sourceMaps.js index 93149591..197413c7 100644 --- a/test/sourceMaps.js +++ b/test/sourceMaps.js @@ -69,7 +69,9 @@ function extractSourceMap(source) { function createTranslateWidthSourceMapTest(name, test, scope) { it(name, function() { - var ast = csso.parse(test.source, scope, true); + var ast = csso.parse(test.source, scope, { + positions: true + }); // strings should be equal assert.equal(translateWithSourceMap(ast).css, 'translate' in test ? test.translate : test.source); diff --git a/test/specificity.js b/test/specificity.js index 82fea3bd..fdf1213e 100644 --- a/test/specificity.js +++ b/test/specificity.js @@ -5,7 +5,7 @@ var specificity = require('../lib/compressor/restructure/prepare/specificity.js' function createSpecificityTest(test) { it(test.selector, function() { - var ast = csso.parse(test.selector, 'simpleselector', true); + var ast = csso.parse(test.selector, 'simpleSelector'); assert.equal(String(specificity(ast)), test.expected); }); From 83aaf9e2d3639bc229bfa9dab4cecf05f17776bc Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 00:22:41 +0300 Subject: [PATCH 14/19] move parse() context argument to options --- lib/index.js | 6 +++--- lib/parser/index.js | 10 +++------- test/ast.js | 18 ++++++++++++------ test/common.js | 2 +- test/parse.js | 12 +++++++----- test/sourceMaps.js | 5 +++-- test/specificity.js | 4 +++- 7 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lib/index.js b/lib/index.js index 211a8800..733c8f9a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -66,10 +66,10 @@ function minify(context, source, options) { // parse 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) }) ); diff --git a/lib/parser/index.js b/lib/parser/index.js index d429a70b..baa4220f 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -1838,20 +1838,16 @@ function getVhash() { }; } -module.exports = function parse(source, context, options) { +module.exports = function parse(source, options) { var ast; - if (options === true) { - throw new Error('!!'); - } - if (!options || typeof options !== 'object') { options = {}; } - needPositions = 'positions' in options ? Boolean(options.positions) : false; + var context = options.context || 'stylesheet'; + needPositions = Boolean(options.positions); filename = options.filename || ''; - context = context || 'stylesheet'; if (!initialContext.hasOwnProperty(context)) { throw new Error('Unknown context `' + context + '`'); diff --git a/test/ast.js b/test/ast.js index 58caed5b..d4b14ff4 100644 --- a/test/ast.js +++ b/test/ast.js @@ -26,10 +26,12 @@ function expectedWalk(ast) { return result; } -function createWalkAllTest(name, test, scope) { +function createWalkAllTest(name, test, context) { it(name, function() { - var ast = csso.parse(test.source, scope); var actual = []; + var ast = csso.parse(test.source, { + context: context + }); walkAll(ast, function(node) { actual.push(node.type); @@ -40,10 +42,12 @@ function createWalkAllTest(name, test, scope) { }); } -function createWalkRulesTest(name, test, scope, walker) { +function createWalkRulesTest(name, test, context, walker) { it(name, function() { - var ast = csso.parse(test.source, scope); var actual = []; + var ast = csso.parse(test.source, { + context: context + }); walker(ast, function(node) { actual.push(node.type); @@ -59,9 +63,11 @@ function createWalkRulesTest(name, test, scope, walker) { }); } -function createTranslateTest(name, test, scope) { +function createTranslateTest(name, test, context) { it(name, function() { - var ast = csso.parse(test.source, scope); + var ast = csso.parse(test.source, { + context: context + }); // strings should be equal assert.equal(translate(ast), 'translate' in test ? test.translate : test.source); diff --git a/test/common.js b/test/common.js index 730a2f3f..eed031e0 100644 --- a/test/common.js +++ b/test/common.js @@ -48,7 +48,7 @@ describe('csso', function() { it('JSON.strigify()', function() { assert.equal( - stringify(csso.parse('.a\n{\rcolor:\r\nred}', 'stylesheet', { + stringify(csso.parse('.a\n{\rcolor:\r\nred}', { positions: true }), true), normalize(fs.readFileSync(__dirname + '/fixture/stringify.txt', 'utf-8').trim()) diff --git a/test/parse.js b/test/parse.js index d17abbf1..5e86a846 100644 --- a/test/parse.js +++ b/test/parse.js @@ -10,7 +10,7 @@ function createParseErrorTest(location, test, options) { var error; assert.throws(function() { - csso.parse(test.css, null, options); + csso.parse(test.css, options); }, function(e) { error = e; if (e.parseError) { @@ -24,9 +24,11 @@ function createParseErrorTest(location, test, options) { } describe('parse', function() { - forEachParseTest(function createParseTest(name, test, scope) { + forEachParseTest(function createParseTest(name, test, context) { it(name, function() { - var ast = csso.parse(test.source, scope); + var ast = csso.parse(test.source, { + context: context + }); // AST should be equal assert.equal(stringify(ast), stringify(test.ast)); @@ -58,7 +60,7 @@ describe('parse error', function() { describe('positions', function() { it('should start with line 1 column 1 by default', function() { - var ast = csso.parse('.foo.bar {\n property: value;\n}', null, { + var ast = csso.parse('.foo.bar {\n property: value;\n}', { positions: true }); var positions = []; @@ -83,7 +85,7 @@ describe('positions', function() { }); it('should start with specified line and column', function() { - var ast = csso.parse('.foo.bar {\n property: value;\n}', null, { + var ast = csso.parse('.foo.bar {\n property: value;\n}', { positions: true, line: 3, column: 5 diff --git a/test/sourceMaps.js b/test/sourceMaps.js index 197413c7..560e36d7 100644 --- a/test/sourceMaps.js +++ b/test/sourceMaps.js @@ -67,9 +67,10 @@ function extractSourceMap(source) { } } -function createTranslateWidthSourceMapTest(name, test, scope) { +function createTranslateWidthSourceMapTest(name, test, context) { it(name, function() { - var ast = csso.parse(test.source, scope, { + var ast = csso.parse(test.source, { + context: context, positions: true }); diff --git a/test/specificity.js b/test/specificity.js index fdf1213e..acd50109 100644 --- a/test/specificity.js +++ b/test/specificity.js @@ -5,7 +5,9 @@ var specificity = require('../lib/compressor/restructure/prepare/specificity.js' function createSpecificityTest(test) { it(test.selector, function() { - var ast = csso.parse(test.selector, 'simpleSelector'); + var ast = csso.parse(test.selector, { + context: 'simpleSelector' + }); assert.equal(String(specificity(ast)), test.expected); }); From 080f97dd46719bac6b1569bcd4af6da83da48d74 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 00:45:05 +0300 Subject: [PATCH 15/19] describe API --- README.md | 170 +++++++++++++++++++++++++--------------------- docs/debugging.md | 94 +++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 77 deletions(-) create mode 100644 docs/debugging.md diff --git a/README.md b/README.md index 779bd24b..67430c4c 100644 --- a/README.md +++ b/README.md @@ -46,23 +46,15 @@ Options: Some examples: ``` -> csso in.css out.css - > csso in.css ...output result in stdout... +> csso in.css --output out.css + > echo '.test { color: #ff0000; }' | csso .test{color:red} > cat source1.css source2.css | csso | gzip -9 -c > production.css.gz - -> echo '.test { color: #ff0000 }' | csso --stat >/dev/null -File: -Original: 25 bytes -Compressed: 16 bytes (64.00%) -Saving: 9 bytes (36.00%) -Time: 12 ms -Memory: 0.346 MB ``` ### Source maps @@ -182,26 +174,18 @@ Currently the optimizer doesn't care about out-of-bounds selectors order changin ```js var csso = require('csso'); -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) -}); ``` You may minify CSS by yourself step by step: ```js 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); console.log(compressedCss); // .test{color:red} @@ -223,79 +207,111 @@ console.log(result.map.toString()); // '{ .. source map content .. }' ``` -### 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 - -.foo,.test{color:red} -``` -More details are provided when `--debug` flag has a number greater than `1`: +#### minifyBlock(source[, options]) + +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 ``` -> 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} +#### parse(source[, options]) + +Parse CSS to AST. -(0.001ms) compress - .test{color:green;color:red}.foo{color:red} +> NOTE: Currenly parser omit redundant separators, spaces and comments (except exlamation comments, i.e. `/*! comment */`) on AST build, since those things are removing by compressor anyway. -... +Options: -(0.002ms) restructBlock - .test{color:red}.foo{color:red} +- 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 (`` by default) -(0.001ms) rejoinRuleset - .foo,.test{color:red} +Contexts: -## compressing done in 13 ms +- `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) -.foo,.test{color:red} -``` +#### compress(ast[, options]) -Using `--debug` option adds stack trace to CSS parse error output. That can help to find out problem in parser. +Do the main task – compress AST. +Options: + +- 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 + +#### translate(ast) + +Converts AST to string. + +```js +var ast = csso.parse('.test { color: red }'); +console.log(csso.translate(ast)); +// > .test{color:red} ``` -> echo '.a { color }' | csso --debug - -Parse error : Colon is expected - 1 |.a { color } -------------------^ - 2 | - -/usr/local/lib/node_modules/csso/lib/cli.js:243 - throw e; - ^ - -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) - ... + +#### 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 {} } ``` +#### walk(ast, handler) + +#### walkRules(ast, handler) + +#### walkRulesRight(ast, handler) + +## More reading + +- [Debugging](docs/debugging.md) + ## License MIT diff --git a/docs/debugging.md b/docs/debugging.md new file mode 100644 index 00000000..b8ddcadd --- /dev/null +++ b/docs/debugging.md @@ -0,0 +1,94 @@ +# Debugging + +## CLI + +All debug information outputs to `stderr`. + +To get brief info about compression use `--stat` option. + +``` +> echo '.test { color: #ff0000 }' | csso --stat >/dev/null +File: +Original: 25 bytes +Compressed: 16 bytes (64.00%) +Saving: 9 bytes (36.00%) +Time: 12 ms +Memory: 0.346 MB +``` + +To get details about compression steps use `--debug` option. + +``` +> 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 + +.foo,.test{color:red} +``` + +More details are provided when `--debug` flag has a number greater than `1`: + +``` +> 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} + +(0.001ms) compress + .test{color:green;color:red}.foo{color:red} + +... + +(0.002ms) restructBlock + .test{color:red}.foo{color:red} + +(0.001ms) rejoinRuleset + .foo,.test{color:red} + +## compressing done in 13 ms + +.foo,.test{color:red} +``` + +Using `--debug` option adds stack trace to CSS parse error output. That can help to find out problem in parser. + +``` +> echo '.a { color }' | csso --debug + +Parse error : Colon is expected + 1 |.a { color } +------------------^ + 2 | + +/usr/local/lib/node_modules/csso/lib/cli.js:243 + throw e; + ^ + +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) + ... +``` + +## API + +[TODO] From 6ae44b3731b109dc3bb995b0b3bc84371c62f4b6 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 00:48:36 +0300 Subject: [PATCH 16/19] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67430c4c..519a7e04 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ All sections are optional. Value of `tags`, `ids` and `classes` should be array `tags`, `ids` and `classes` are using on clean stage to filter selectors that contains something that not in list. Selectors are filtering only by those kind of simple selector which white list is specified. For example, if only `tags` list is specified then type selectors are checking, and if selector hasn't any type selector (or even any type selector) it isn't filter. -> `ids` and `classes` comparison is case sensetive, `tags` – is not. +> `ids` and `classes` names are case sensitive, `tags` – is not. Input CSS: From 1a7eb5abb19d8a3ec87b819c790c75ba152b0288 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 04:32:07 +0300 Subject: [PATCH 17/19] remove unnecessary properties from AST nodes in parser --- lib/parser/index.js | 17 ++++------------- test/fixture/parse/atrule/stylesheet.json | 10 +++------- test/helpers/stringify.js | 5 +---- 3 files changed, 8 insertions(+), 24 deletions(-) diff --git a/lib/parser/index.js b/lib/parser/index.js index baa4220f..9f322257 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -133,9 +133,7 @@ function getStylesheet(nested) { var node = { type: 'StyleSheet', info: getInfo(), - rules: new List(), - avoidRulesMerge: false, - id: null + rules: new List() }; scan: @@ -220,8 +218,7 @@ function getAtruleExpression() { var node = { type: 'AtruleExpression', info: getInfo(), - sequence: new List(), - id: null + sequence: new List() }; scan: @@ -314,7 +311,6 @@ function getRuleset() { return { type: 'Ruleset', info: getInfo(), - pseudoSignature: null, selector: getSelector(), block: getBlockWithBrackets() }; @@ -376,9 +372,7 @@ function getSimpleSelector(nested) { var node = { type: 'SimpleSelector', info: getInfo(), - sequence: new List(), - id: null, - compareMarker: null + sequence: new List() }; scan: @@ -549,10 +543,7 @@ function getDeclaration(nested) { type: 'Declaration', info: info, property: property, - value: value, - id: null, - length: null, - fingerprint: null + value: value }; } diff --git a/test/fixture/parse/atrule/stylesheet.json b/test/fixture/parse/atrule/stylesheet.json index c284b026..7bc10be3 100644 --- a/test/fixture/parse/atrule/stylesheet.json +++ b/test/fixture/parse/atrule/stylesheet.json @@ -267,8 +267,7 @@ } ] } - ], - "id": null + ] }, "block": { "type": "StyleSheet", @@ -285,9 +284,7 @@ "type": "Identifier", "name": "s" } - ], - "id": null, - "compareMarker": null + ] } ] }, @@ -326,8 +323,7 @@ "name": "media", "expression": { "type": "AtruleExpression", - "sequence": [], - "id": null + "sequence": [] }, "block": { "type": "StyleSheet", diff --git a/test/helpers/stringify.js b/test/helpers/stringify.js index 2cc7089e..68896b11 100644 --- a/test/helpers/stringify.js +++ b/test/helpers/stringify.js @@ -11,10 +11,7 @@ module.exports = function stringify(ast, withInfo) { if (source && typeof source === 'object') { var result = {}; for (var key in source) { - if ((withInfo || key !== 'info') && - key !== 'id' && key !== 'length' && - key !== 'fingerprint' && key !== 'compareMarker' && - key !== 'pseudoSignature' && key !== 'avoidRulesMerge') { + if (withInfo || key !== 'info') { result[key] = clean(source[key]); } } From 8941f0047fa616f77a58ccd71f3cc1e250ef5ad5 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 04:34:41 +0300 Subject: [PATCH 18/19] complete API description --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 519a7e04..e9c572c6 100644 --- a/README.md +++ b/README.md @@ -249,13 +249,15 @@ console.log(result.css); Parse CSS to AST. -> NOTE: Currenly parser omit redundant separators, spaces and comments (except exlamation comments, i.e. `/*! comment */`) on AST build, since those things are removing by compressor anyway. +> 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 (`` 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: @@ -269,6 +271,17 @@ Contexts: - `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 +}); +``` + #### compress(ast[, options]) Do the main task – compress AST. @@ -304,10 +317,56 @@ console.log(csso.translateWithSourceMap(ast)); #### walk(ast, handler) +Visit all nodes of AST and call handler for each one. `handler` receives three arguments: + +- 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) From cea31c2a3cf49f57c5d50233c84e704ff2edc2c9 Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Wed, 6 Apr 2016 05:17:00 +0300 Subject: [PATCH 19/19] comments --- lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 733c8f9a..2a6b2aca 100644 --- a/lib/index.js +++ b/lib/index.js @@ -111,12 +111,13 @@ module.exports = { minify: minifyStylesheet, minifyBlock: minifyBlock, - // utils + // step by step parse: parse, compress: compress, translate: translate, translateWithSourceMap: translateWithSourceMap, + // walkers walk: walkers.all, walkRules: walkers.rules, walkRulesRight: walkers.rulesRight