From 70d3578f4a8c7c4597d516dad5370651bb72090d Mon Sep 17 00:00:00 2001 From: Roman Dvornov Date: Sat, 27 Feb 2016 13:08:53 +0300 Subject: [PATCH] add support for flags in attribute selector (fixes #270) --- lib/compressor/ast/gonzalesToInternal.js | 24 +- lib/compressor/ast/internalToGonzales.js | 17 +- lib/compressor/ast/translate.js | 20 +- lib/compressor/ast/translateWithSourceMap.js | 20 +- lib/parser/index.js | 12 + lib/utils/translate.js | 1 + test/fixture/compress/attrib.string/1.css | 4 + test/fixture/compress/attrib.string/1.min.css | 2 +- test/fixture/compress/attrib.string/2.css | 4 + test/fixture/compress/attrib.string/2.min.css | 2 +- test/fixture/compress/attrib.string/3.css | 3 +- test/fixture/compress/attrib.string/3.min.css | 2 +- test/fixture/internal/attrib.json | 118 +++++-- test/fixture/internal/simpleselector.json | 320 +++++++++--------- test/fixture/internal/stylesheet.json | 3 +- test/fixture/parse/attrib.json | 40 +++ 16 files changed, 385 insertions(+), 207 deletions(-) diff --git a/lib/compressor/ast/gonzalesToInternal.js b/lib/compressor/ast/gonzalesToInternal.js index 8610a756..fa5b9ef1 100644 --- a/lib/compressor/ast/gonzalesToInternal.js +++ b/lib/compressor/ast/gonzalesToInternal.js @@ -174,22 +174,36 @@ var types = { }, attrib: function(token) { var offset = 2; + var name; + var operator = null; + var value = null; + var flags = null; offset = skipSC(token, 2); - var name = convertToInternal(token[offset]); + name = convertToInternal(token[offset]); offset = skipSC(token, offset + 1); - var operator = token[offset] ? token[offset][2] : null; + if (offset < token.length) { + operator = token[offset][2]; - offset = skipSC(token, offset + 1); - var value = convertToInternal(token[offset]); + 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 + value: value, + flags: flags }; }, attrselector: false, diff --git a/lib/compressor/ast/internalToGonzales.js b/lib/compressor/ast/internalToGonzales.js index 83e57942..1a873d51 100644 --- a/lib/compressor/ast/internalToGonzales.js +++ b/lib/compressor/ast/internalToGonzales.js @@ -126,11 +126,20 @@ function toGonzales(node) { result.push([{}, 'ident', node.name.name]); - if (node.operator) { + if (node.operator !== null) { result.push([{}, 'attrselector', node.operator]); - } - if (node.value) { - result.push(toGonzales(node.value)); + + 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; diff --git a/lib/compressor/ast/translate.js b/lib/compressor/ast/translate.js index a5abe510..2dee9525 100644 --- a/lib/compressor/ast/translate.js +++ b/lib/compressor/ast/translate.js @@ -53,11 +53,21 @@ function translate(node) { : each(node.sequence); case 'Attribute': - return '[' + - translate(node.name) + - (node.operator !== null ? node.operator : '') + - (node.value ? translate(node.value) : '') + - ']'; + 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, ',') + ')'; diff --git a/lib/compressor/ast/translateWithSourceMap.js b/lib/compressor/ast/translateWithSourceMap.js index 6bd30288..e27c81f3 100644 --- a/lib/compressor/ast/translateWithSourceMap.js +++ b/lib/compressor/ast/translateWithSourceMap.js @@ -164,11 +164,21 @@ function translate(node) { : each(node.sequence); case 'Attribute': - return '[' + - translate(node.name) + - (node.operator !== null ? node.operator : '') + - (node.value ? translate(node.value) : '') + - ']'; + 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, ',') + ')'; diff --git a/lib/parser/index.js b/lib/parser/index.js index e16a30d1..78cfe011 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -571,6 +571,8 @@ function getAny() { return ident; } +// '[' S* attrib_name ']' +// '[' S* attrib_name attrib_match [ IDENT | STRING ] S* attrib_flags? ']' function getAttrib() { var node = [getInfo(pos), NodeType.AttribType]; @@ -593,6 +595,16 @@ function getAttrib() { } readSC(node); + + // attribute flags + if (pos < tokens.length && tokens[pos].type === TokenType.Identifier) { + node.push([ + getInfo(pos), + 'attribFlags', + tokens[pos++].value + ]); + readSC(node); + } } eat(TokenType.RightSquareBracket); diff --git a/lib/utils/translate.js b/lib/utils/translate.js index 4b6daf78..1bedf8c6 100644 --- a/lib/utils/translate.js +++ b/lib/utils/translate.js @@ -12,6 +12,7 @@ var typeHandlers = { operator: simple, raw: simple, unknown: simple, + attribFlags: simple, simpleselector: composite, dimension: composite, diff --git a/test/fixture/compress/attrib.string/1.css b/test/fixture/compress/attrib.string/1.css index 894fbc1c..a380fed5 100644 --- a/test/fixture/compress/attrib.string/1.css +++ b/test/fixture/compress/attrib.string/1.css @@ -1,6 +1,10 @@ [title="test"], +[title="test"i], +[title="test" i ], [title="123"], [title="test test"], +[title="test test"i], +[title="test test" i ], [title="-"], [title=""] { diff --git a/test/fixture/compress/attrib.string/1.min.css b/test/fixture/compress/attrib.string/1.min.css index 6c93b1f2..1a8ffab6 100644 --- a/test/fixture/compress/attrib.string/1.min.css +++ b/test/fixture/compress/attrib.string/1.min.css @@ -1 +1 @@ -[title=""],[title="-"],[title="123"],[title="test test"],[title=test]{p:v} +[title=""],[title="-"],[title="123"],[title="test test"],[title="test test"i],[title=test i],[title=test]{p:v} diff --git a/test/fixture/compress/attrib.string/2.css b/test/fixture/compress/attrib.string/2.css index 0393d463..6acbcc78 100644 --- a/test/fixture/compress/attrib.string/2.css +++ b/test/fixture/compress/attrib.string/2.css @@ -1,6 +1,10 @@ [title='test'], +[title='test'i], +[title='test' i ], [title='123'], [title='test test'], +[title='test test'i], +[title='test test' i ], [title='-'], [title=''] { diff --git a/test/fixture/compress/attrib.string/2.min.css b/test/fixture/compress/attrib.string/2.min.css index 7e7e838a..6b0ee1d7 100644 --- a/test/fixture/compress/attrib.string/2.min.css +++ b/test/fixture/compress/attrib.string/2.min.css @@ -1 +1 @@ -[title=''],[title='-'],[title='123'],[title='test test'],[title=test]{p:v} +[title=''],[title='-'],[title='123'],[title='test test'],[title='test test'i],[title=test i],[title=test]{p:v} diff --git a/test/fixture/compress/attrib.string/3.css b/test/fixture/compress/attrib.string/3.css index e384681a..4cecbf34 100644 --- a/test/fixture/compress/attrib.string/3.css +++ b/test/fixture/compress/attrib.string/3.css @@ -1,4 +1,5 @@ -[name=test] +[name=test], +[name=test i] { p: v; } diff --git a/test/fixture/compress/attrib.string/3.min.css b/test/fixture/compress/attrib.string/3.min.css index cad8b2c6..6bfe5d6c 100644 --- a/test/fixture/compress/attrib.string/3.min.css +++ b/test/fixture/compress/attrib.string/3.min.css @@ -1 +1 @@ -[name=test]{p:v} +[name=test i],[name=test]{p:v} diff --git a/test/fixture/internal/attrib.json b/test/fixture/internal/attrib.json index 00d385e3..98eeeb74 100644 --- a/test/fixture/internal/attrib.json +++ b/test/fixture/internal/attrib.json @@ -12,7 +12,8 @@ "value": { "type": "Identifier", "name": "b" - } + }, + "flags": null } }, "attrib.1": { @@ -28,7 +29,25 @@ "value": { "type": "String", "value": "'b'" - } + }, + "flags": null + } + }, + "attrib.1 with flags": { + "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.2": { @@ -41,7 +60,8 @@ "name": "b" }, "operator": null, - "value": null + "value": null, + "flags": null } }, "attrib.3": { @@ -54,7 +74,8 @@ "name": "ng:cloak" }, "operator": null, - "value": null + "value": null, + "flags": null } }, "attrib.4": { @@ -70,11 +91,12 @@ "value": { "type": "Identifier", "name": "x" - } + }, + "flags": null } }, - "attrib.c.0": { - "source": "[/*test*/a/*test*/=/*test*/b/*test*/]", + "attrib.s.0": { + "source": "[ a = b ]", "translate": "[a=b]", "ast": { "type": "Attribute", @@ -86,11 +108,12 @@ "value": { "type": "Identifier", "name": "b" - } + }, + "flags": null } }, - "attrib.c.1": { - "source": "[/*test*/a/*test*/=/*test*/'b'/*test*/]", + "attrib.s.1": { + "source": "[ a = 'b' ]", "translate": "[a='b']", "ast": { "type": "Attribute", @@ -102,11 +125,29 @@ "value": { "type": "String", "value": "'b'" - } + }, + "flags": null } }, - "attrib.s.0": { - "source": "[ a = b ]", + "attrib.1 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.c.0": { + "source": "[/*test*/a/*test*/=/*test*/b/*test*/]", "translate": "[a=b]", "ast": { "type": "Attribute", @@ -118,11 +159,12 @@ "value": { "type": "Identifier", "name": "b" - } + }, + "flags": null } }, - "attrib.s.1": { - "source": "[ a = 'b' ]", + "attrib.c.1": { + "source": "[/*test*/a/*test*/=/*test*/'b'/*test*/]", "translate": "[a='b']", "ast": { "type": "Attribute", @@ -134,7 +176,25 @@ "value": { "type": "String", "value": "'b'" - } + }, + "flags": null + } + }, + "attrib.c.1 with flags and comments": { + "source": "[/*test*/a/*test*/=/*test*/'b'/*test*/i/*test*/]", + "translate": "[a='b'i]", + "ast": { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "a" + }, + "operator": "=", + "value": { + "type": "String", + "value": "'b'" + }, + "flags": "i" } }, "namespace": { @@ -150,7 +210,8 @@ "value": { "type": "String", "value": "\"#test\"" - } + }, + "flags": null } }, "namespace unversal": { @@ -166,7 +227,8 @@ "value": { "type": "String", "value": "\"#test\"" - } + }, + "flags": null } }, "namespace w/o attrselector": { @@ -179,7 +241,8 @@ "name": "xlink|href" }, "operator": null, - "value": null + "value": null, + "flags": null } }, "namespace unversal w/o attrselector": { @@ -192,7 +255,8 @@ "name": "*|href" }, "operator": null, - "value": null + "value": null, + "flags": null } }, "attrib with dashmatch": { @@ -208,7 +272,8 @@ "value": { "type": "String", "value": "'b'" - } + }, + "flags": null } }, "attrib with namespace and dashmatch": { @@ -224,7 +289,8 @@ "value": { "type": "String", "value": "'c'" - } + }, + "flags": null } }, "attrib with dashmatch and spaces": { @@ -240,7 +306,8 @@ "value": { "type": "String", "value": "'b'" - } + }, + "flags": null } }, "attrib with namespace, dashmatch and spaces": { @@ -256,7 +323,8 @@ "value": { "type": "String", "value": "'c'" - } + }, + "flags": null } } } diff --git a/test/fixture/internal/simpleselector.json b/test/fixture/internal/simpleselector.json index b9fa7863..6346b2a5 100644 --- a/test/fixture/internal/simpleselector.json +++ b/test/fixture/internal/simpleselector.json @@ -25,161 +25,6 @@ ] } }, - "simpleselector.10": { - "source": "a b + c > d ~ e", - "translate": "a b+c>d~e", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "a" - }, - { - "type": "Combinator", - "name": " " - }, - { - "type": "Identifier", - "name": "b" - }, - { - "type": "Combinator", - "name": "+" - }, - { - "type": "Identifier", - "name": "c" - }, - { - "type": "Combinator", - "name": ">" - }, - { - "type": "Identifier", - "name": "d" - }, - { - "type": "Combinator", - "name": "~" - }, - { - "type": "Identifier", - "name": "e" - } - ] - } - }, - "simpleselector.11": { - "source": "*|*:not(*)", - "translate": "*|*:not(*)", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "*|*" - }, - { - "type": "Negation", - "sequence": [ - { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "*" - } - ] - } - ] - } - ] - } - }, - "simpleselector.12": { - "source": "x:not([ABC])", - "translate": "x:not([ABC])", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "x" - }, - { - "type": "Negation", - "sequence": [ - { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Attribute", - "name": { - "type": "Identifier", - "name": "ABC" - }, - "operator": null, - "value": null - } - ] - } - ] - } - ] - } - }, - "simpleselector.13": { - "source": ":not(el.class-postfix)", - "translate": ":not(el.class-postfix)", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Negation", - "sequence": [ - { - "type": "SimpleSelector", - "sequence": [ - { - "type": "Identifier", - "name": "el" - }, - { - "type": "Class", - "name": "class-postfix" - } - ] - } - ] - } - ] - } - }, - "simpleselector.14": { - "source": ":lang(nl-be)", - "translate": ":lang(nl-be)", - "ast": { - "type": "SimpleSelector", - "sequence": [ - { - "type": "FunctionalPseudo", - "name": "lang", - "arguments": [ - { - "type": "Argument", - "sequence": [ - { - "type": "Identifier", - "name": "nl-be" - } - ] - } - ] - } - ] - } - }, "simpleselector.2": { "source": "#test", "translate": "#test", @@ -209,7 +54,8 @@ "value": { "type": "Identifier", "name": "b" - } + }, + "flags": null } ] } @@ -230,7 +76,8 @@ "value": { "type": "Identifier", "name": "b" - } + }, + "flags": null }, { "type": "Attribute", @@ -242,7 +89,8 @@ "value": { "type": "String", "value": "'d'" - } + }, + "flags": null } ] } @@ -367,6 +215,162 @@ ] } }, + "simpleselector.10": { + "source": "a b + c > d ~ e", + "translate": "a b+c>d~e", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "a" + }, + { + "type": "Combinator", + "name": " " + }, + { + "type": "Identifier", + "name": "b" + }, + { + "type": "Combinator", + "name": "+" + }, + { + "type": "Identifier", + "name": "c" + }, + { + "type": "Combinator", + "name": ">" + }, + { + "type": "Identifier", + "name": "d" + }, + { + "type": "Combinator", + "name": "~" + }, + { + "type": "Identifier", + "name": "e" + } + ] + } + }, + "simpleselector.11": { + "source": "*|*:not(*)", + "translate": "*|*:not(*)", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "*|*" + }, + { + "type": "Negation", + "sequence": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "*" + } + ] + } + ] + } + ] + } + }, + "simpleselector.12": { + "source": "x:not([ABC])", + "translate": "x:not([ABC])", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "x" + }, + { + "type": "Negation", + "sequence": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Attribute", + "name": { + "type": "Identifier", + "name": "ABC" + }, + "operator": null, + "value": null, + "flags": null + } + ] + } + ] + } + ] + } + }, + "simpleselector.13": { + "source": ":not(el.class-postfix)", + "translate": ":not(el.class-postfix)", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Negation", + "sequence": [ + { + "type": "SimpleSelector", + "sequence": [ + { + "type": "Identifier", + "name": "el" + }, + { + "type": "Class", + "name": "class-postfix" + } + ] + } + ] + } + ] + } + }, + "simpleselector.14": { + "source": ":lang(nl-be)", + "translate": ":lang(nl-be)", + "ast": { + "type": "SimpleSelector", + "sequence": [ + { + "type": "FunctionalPseudo", + "name": "lang", + "arguments": [ + { + "type": "Argument", + "sequence": [ + { + "type": "Identifier", + "name": "nl-be" + } + ] + } + ] + } + ] + } + }, "simpleselector.c.0": { "source": "a/*test*/ /*test*/b", "translate": "a b", diff --git a/test/fixture/internal/stylesheet.json b/test/fixture/internal/stylesheet.json index b95a34f0..71d651ba 100644 --- a/test/fixture/internal/stylesheet.json +++ b/test/fixture/internal/stylesheet.json @@ -30,7 +30,8 @@ "name": "x|x" }, "operator": null, - "value": null + "value": null, + "flags": null } ] } diff --git a/test/fixture/parse/attrib.json b/test/fixture/parse/attrib.json index 25188494..7cea57b0 100644 --- a/test/fixture/parse/attrib.json +++ b/test/fixture/parse/attrib.json @@ -17,6 +17,16 @@ ["string", "'b'"] ] }, + "attrib with flags": { + "source": "[a='b'i]", + "ast": [ + "attrib", + ["ident", "a"], + ["attrselector", "="], + ["string", "'b'"], + ["attribFlags", "i"] + ] + }, "attrib.2": { "source": "[b]", "ast": [ @@ -92,6 +102,36 @@ ["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": [