From 5c8584009766eea9d03f8b3d676ece2d93ac06de Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 19 Jun 2015 15:30:47 +0200 Subject: [PATCH 01/31] Better matching and escpaing --- lib/parseValues.js | 11 ++++++++--- lib/stringifyValues.js | 25 +++++++++++++++++++++++-- test/test-cases-values.js | 23 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/lib/parseValues.js b/lib/parseValues.js index cc4dc53..eb55a07 100644 --- a/lib/parseValues.js +++ b/lib/parseValues.js @@ -23,8 +23,12 @@ function endSpacingMatch(match) { } function unescapeString(content) { - return content.replace(/\\./g, function(escaped) { - return escaped.substr(1); + return content.replace(/\\([a-fA-F0-9]{4}|.)/g, function(escaped) { + if(escaped.length > 2) { + return String.fromCharCode(parseInt(escaped.substr(1), 16)); + } else { + return escaped.substr(1); + } }); } @@ -87,6 +91,7 @@ function nestedItemEndMatch(match, spacing, remaining) { this.value.after = spacing; } this.root = this.stack.pop(); + this.value = this.root.nodes[this.root.nodes.length - 1]; } } @@ -125,7 +130,7 @@ var parser = new Parser({ "url\\((\\s*)(\"(?:[^\\\\\"]|\\\\.)*\")(\\s*)\\)": urlMatch, "url\\((\\s*)('(?:[^\\\\']|\\\\.)*')(\\s*)\\)": urlMatch, "url\\((\\s*)((?:[^\\\\)'\"]|\\\\.)*)(\\s*)\\)": urlMatch, - "(\\w+)\\((\\s*)": nestedItemMatch, + "([\\w\-]+)\\((\\s*)": nestedItemMatch, "(\\s*)(\\))": nestedItemEndMatch, ",(\\s*)": commaMatch, "\\s+$": endSpacingMatch, diff --git a/lib/stringifyValues.js b/lib/stringifyValues.js index 54af10b..c9934fb 100644 --- a/lib/stringifyValues.js +++ b/lib/stringifyValues.js @@ -2,6 +2,27 @@ var stringify; +function escape(str, stringType) { + return str.replace(/["'\\\x80-\uFFFF]/g, function(match) { + switch(match) { + case "\"": + if(stringType === "\"") { + return "\\\""; + } + return match; + case "'": + if(stringType === "'") { + return "\\'"; + } + return match; + case "\\": + return "\\\\"; + default: + return "\\" + (0x10000 + match.charCodeAt(0)).toString(16).substr(1); + } + }); +} + function stringifyWithoutBeforeAfter(tree) { switch(tree.type) { case "values": @@ -19,9 +40,9 @@ function stringifyWithoutBeforeAfter(tree) { case "string": switch(tree.stringType) { case "'": - return "'" + tree.value.replace(/'/g, "\\'") + "'"; + return "'" + escape(tree.value, "'") + "'"; case "\"": - return "\"" + tree.value.replace(/"/g, "\\\"") + "\""; + return "\"" + escape(tree.value, "\"") + "\""; } /* istanbul ignore next */ throw new Error("Invalid stringType"); diff --git a/test/test-cases-values.js b/test/test-cases-values.js index a4775a2..a8cbb88 100644 --- a/test/test-cases-values.js +++ b/test/test-cases-values.js @@ -123,5 +123,28 @@ module.exports = { ], before: " ", after: "\t" } ] } + ], + "escaped unicode": [ + "'\\f0e3\\\\\\'\"'", + singleValue([ + { type: "string", stringType: "'", value: "\uf0e3\\'\"" } + ]) + ], + "escaped unicode 2": [ + "\"\\f0e3\\\\'\\\"\"", + singleValue([ + { type: "string", stringType: "\"", value: "\uf0e3\\'\"" } + ]) + ], + "nested-item-with append": [ + "linear-gradient(45deg) 25%", + singleValue([ + { type: "nested-item", name: "linear-gradient", nodes: [ + { type: "value", nodes: [ + { type: "item", name: "45deg"} + ]} + ], after: " " }, + { type: "item", name: "25%" } + ]) ] }; From ea9ca39886f2bdace78ed6913ff4dfa3b3bad969 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 19 Jun 2015 15:31:04 +0200 Subject: [PATCH 02/31] 0.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d91e669..7bb2fd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.5.1", + "version": "0.5.2", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 5aa6180c27e1444d4f9601ec5f7ad6b3e894b587 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Sat, 18 Jul 2015 13:12:17 +0200 Subject: [PATCH 03/31] fixes issues with escaping of chars fixes webpack/css-loader#87 fixes #5 --- lib/parseValues.js | 10 ++++++++-- lib/stringifyValues.js | 21 ++++----------------- package.json | 1 + test/test-cases-values.js | 16 ++++++++++++++-- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/parseValues.js b/lib/parseValues.js index eb55a07..316c6f3 100644 --- a/lib/parseValues.js +++ b/lib/parseValues.js @@ -23,9 +23,15 @@ function endSpacingMatch(match) { } function unescapeString(content) { - return content.replace(/\\([a-fA-F0-9]{4}|.)/g, function(escaped) { + return content.replace(/\\([a-fA-F0-9]{2,5}|.)/g, function(escaped) { if(escaped.length > 2) { - return String.fromCharCode(parseInt(escaped.substr(1), 16)); + var C = parseInt(escaped.substr(1), 16); + if(C < 0x10000) { + return String.fromCharCode(C); + } else { + return String.fromCharCode(Math.floor((C - 0x10000) / 0x400) + 0xD800) + + String.fromCharCode((C - 0x10000) % 0x400 + 0xDC00); + } } else { return escaped.substr(1); } diff --git a/lib/stringifyValues.js b/lib/stringifyValues.js index c9934fb..976af12 100644 --- a/lib/stringifyValues.js +++ b/lib/stringifyValues.js @@ -1,25 +1,12 @@ "use strict"; +var cssesc = require("cssesc"); + var stringify; function escape(str, stringType) { - return str.replace(/["'\\\x80-\uFFFF]/g, function(match) { - switch(match) { - case "\"": - if(stringType === "\"") { - return "\\\""; - } - return match; - case "'": - if(stringType === "'") { - return "\\'"; - } - return match; - case "\\": - return "\\\\"; - default: - return "\\" + (0x10000 + match.charCodeAt(0)).toString(16).substr(1); - } + return cssesc(str, { + quotes: stringType === "\"" ? "double" : "single" }); } diff --git a/package.json b/package.json index 7bb2fd9..7753fd1 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "homepage": "https://github.com/css-modules/css-selector-tokenizer", "dependencies": { + "cssesc": "^0.1.0", "fastparse": "^1.1.1" }, "devDependencies": { diff --git a/test/test-cases-values.js b/test/test-cases-values.js index a8cbb88..43c1aa1 100644 --- a/test/test-cases-values.js +++ b/test/test-cases-values.js @@ -125,17 +125,29 @@ module.exports = { } ], "escaped unicode": [ - "'\\f0e3\\\\\\'\"'", + "'\\F0E3\\\\\\'\"'", singleValue([ { type: "string", stringType: "'", value: "\uf0e3\\'\"" } ]) ], "escaped unicode 2": [ - "\"\\f0e3\\\\'\\\"\"", + "\"\\F0E3\\\\'\\\"\"", singleValue([ { type: "string", stringType: "\"", value: "\uf0e3\\'\"" } ]) ], + "escaped unicode 3 (short)": [ + "\"\\10\"", + singleValue([ + { type: "string", stringType: "\"", value: "\u0010" } + ]) + ], + "escaped unicode 4 (surrogate pair)": [ + "\"\\1F50E\"", + singleValue([ + { type: "string", stringType: "\"", value: "\ud83d\udd0e" } + ]) + ], "nested-item-with append": [ "linear-gradient(45deg) 25%", singleValue([ From 035af4512f6dacd16d4c7485549581ae7531347a Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Sat, 18 Jul 2015 13:12:42 +0200 Subject: [PATCH 04/31] 0.5.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7753fd1..94d73c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.5.2", + "version": "0.5.3", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 04adab286ccee0139d503cf2a6736977888e683b Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Sun, 26 Jul 2015 22:47:32 +0200 Subject: [PATCH 05/31] fixes some parsing issues fixes webpack/css-loader#106 fixed #9 fixes #8 fixes #7 --- lib/parse.js | 4 ++-- lib/stringify.js | 2 +- test/test-cases.js | 33 +++++++++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index c0c5dfa..9841107 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -169,7 +169,7 @@ var parser = new Parser({ "/\\*([\\s\\S]*?)\\*/": commentMatch, "\\.((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": typeMatch("class"), "#((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": typeMatch("id"), - ":(not|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, + ":(not|matches|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)\\(": pseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-class"), "::((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-element"), @@ -179,7 +179,7 @@ var parser = new Parser({ "((?:\\\\.|[A-Za-z_\\-0-9])*\\|)?((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": elementMatch, "\\[([^\\]]+)\\]": attributeMatch, "(\\s*)\\)": nestedEnd, - "(\\s*)([>+~])(\\s*)": operatorMatch, + "(\\s*)((?:\\|\\|)|(?:>>)|[>+~])(\\s*)": operatorMatch, "(\\s*),(\\s*)": nextSelectorMatch, "\\s+$": irrelevantSpacingEndMatch, "^\\s+": irrelevantSpacingStartMatch, diff --git a/lib/stringify.js b/lib/stringify.js index de02670..b97710a 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -6,7 +6,7 @@ function escape(str) { if(str === "*") { return "*"; } - return str.replace(/(^[^A-Za-z_\\-]|[^A-Za-z_0-9\\-])/g, "\\$1"); + return str.replace(/(^[^A-Za-z_\\-]|^\-\-|[^A-Za-z_0-9\\-])/g, "\\$1"); } function stringifyWithoutBeforeAfter(tree) { diff --git a/test/test-cases.js b/test/test-cases.js index c3055bb..61a4fe4 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -55,10 +55,13 @@ module.exports = { ]) ], - "class name starting with number": [ - ".\\5\\#-\\.5", + "class name starting with number or dash": [ + ".\\5\\#-\\.5 .\\--name.-name", singleSelector([ - { type: "class", name: "5#-.5" } + { type: "class", name: "5#-.5" }, + { type: "spacing", value: " " }, + { type: "class", name: "--name" }, + { type: "class", name: "-name" } ]) ], @@ -251,7 +254,7 @@ module.exports = { ], "pseudo class with difficult content": [ - ":--anything-new(/* here is difficult ')][ .content */\nurl('Hello)World'), \"Hello)\\\".World\")", + ":\\--anything-new(/* here is difficult ')][ .content */\nurl('Hello)World'), \"Hello)\\\".World\")", singleSelector([ { type: "pseudo-class", name: "--anything-new", content: "/* here is difficult ')][ .content */\nurl('Hello)World'), \"Hello)\\\".World\"" } ]) @@ -320,6 +323,28 @@ module.exports = { ]) ], + "available nested pseudo classes": [ + ":not(:active):matches(:focus)", + singleSelector([ + { type: "nested-pseudo-class", name: "not", nodes: [ + { + type: "selector", + nodes: [ + { type: "pseudo-class", name: "active" } + ] + } + ] }, + { type: "nested-pseudo-class", name: "matches", nodes: [ + { + type: "selector", + nodes: [ + { type: "pseudo-class", name: "focus" } + ] + } + ] } + ]) + ], + "nested pseudo class with nested selectors": [ ":has(h1:not(:has(:visited)))", singleSelector([ From 62ea904e60b11e38443012752ae154e4426313a3 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Sun, 26 Jul 2015 22:48:32 +0200 Subject: [PATCH 06/31] added test cases --- test/test-cases.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test-cases.js b/test/test-cases.js index 61a4fe4..dd21bd6 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -186,7 +186,7 @@ module.exports = { ], "operators": [ - "a > .class-name~.x123+ div", + "a > .class-name~.x123+ div >> col || td", singleSelector([ { type: "element", name: "a" }, { type: "operator", operator: ">", before: " ", after: " " }, @@ -194,7 +194,11 @@ module.exports = { { type: "operator", operator: "~" }, { type: "class", name: "x123" }, { type: "operator", operator: "+", after: " " }, - { type: "element", name: "div" } + { type: "element", name: "div" }, + { type: "operator", operator: ">>", before: " ", after: " " }, + { type: "element", name: "col" }, + { type: "operator", operator: "||", before: " ", after: " " }, + { type: "element", name: "td" } ]) ], From 013091aab7f67988a6ea966bb94062cd00213201 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Sun, 26 Jul 2015 22:48:39 +0200 Subject: [PATCH 07/31] 0.5.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94d73c3..dd888ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.5.3", + "version": "0.5.4", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From db76c764f9ca266e90c1194fd01c6e1ad9b3d56d Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 22 Dec 2015 10:06:03 +0100 Subject: [PATCH 08/31] added an test for image-set --- test/test-cases-values.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/test-cases-values.js b/test/test-cases-values.js index 43c1aa1..6d4fbb2 100644 --- a/test/test-cases-values.js +++ b/test/test-cases-values.js @@ -100,6 +100,35 @@ module.exports = { ] } ], + "nested-item image-set": [ + "image-set(url(a) 1x, url('b') 2x), -webkit-image-set(url(\"a\") 1x, url(b) 2x)", + { type: "values", nodes: [ + { type: "value", nodes: [ + { type: "nested-item", name: "image-set", nodes: [ + { type: "value", nodes: [ + { type: "url", url: "a", after: " " }, + { type: "item", name: "1x" } + ] }, + { type: "value", nodes: [ + { type: "url", stringType: "'", url: "b", after: " " }, + { type: "item", name: "2x" } + ], before: " " } + ] } + ] }, + { type: "value", nodes: [ + { type: "nested-item", name: "-webkit-image-set", nodes: [ + { type: "value", nodes: [ + { type: "url", stringType: "\"", url: "a", after: " " }, + { type: "item", name: "1x" } + ] }, + { type: "value", nodes: [ + { type: "url", url: "b", after: " " }, + { type: "item", name: "2x" } + ], before: " " } + ] } + ], before: " " } + ] } + ], "invalid": [ " ) ) ", { From 90613f2ae72993ac94a0f72890ba09715463830a Mon Sep 17 00:00:00 2001 From: Pieter de Bie Date: Wed, 13 Apr 2016 17:44:29 +0200 Subject: [PATCH 09/31] Properly parse unicode characters in strings This fixes the parsing of unicode strings that are 1 or 6 hex characters long. Extends the tests to support a third element in examples, which would be the re-stringified example, since a unicode character might be stringified in a different way than the original code, and still be valid. fixes webpack/css-loader#133 fixes #12 --- lib/parseValues.js | 20 ++++++++++---------- test/stringifyValues.js | 2 +- test/test-cases-values.js | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/parseValues.js b/lib/parseValues.js index 316c6f3..97be01b 100644 --- a/lib/parseValues.js +++ b/lib/parseValues.js @@ -23,17 +23,17 @@ function endSpacingMatch(match) { } function unescapeString(content) { - return content.replace(/\\([a-fA-F0-9]{2,5}|.)/g, function(escaped) { - if(escaped.length > 2) { - var C = parseInt(escaped.substr(1), 16); - if(C < 0x10000) { - return String.fromCharCode(C); - } else { - return String.fromCharCode(Math.floor((C - 0x10000) / 0x400) + 0xD800) + - String.fromCharCode((C - 0x10000) % 0x400 + 0xDC00); - } + return content.replace(/\\(?:([a-fA-F0-9]{1,6})|(.))/g, function(all, unicode, otherCharacter) { + if (otherCharacter) { + return otherCharacter; + } + + var C = parseInt(unicode, 16); + if(C < 0x10000) { + return String.fromCharCode(C); } else { - return escaped.substr(1); + return String.fromCharCode(Math.floor((C - 0x10000) / 0x400) + 0xD800) + + String.fromCharCode((C - 0x10000) % 0x400 + 0xDC00); } }); } diff --git a/test/stringifyValues.js b/test/stringifyValues.js index b48acb2..8f1bbb5 100644 --- a/test/stringifyValues.js +++ b/test/stringifyValues.js @@ -10,7 +10,7 @@ describe("stringifyValues", function() { Object.keys(testCases).forEach(function(testCase) { it("should stringify values " + testCase, function() { var input = testCases[testCase][1]; - var expected = testCases[testCase][0]; + var expected = testCases[testCase][2] || testCases[testCase][0]; assert.deepEqual(Tokenizer.stringifyValues(input), expected); }); }); diff --git a/test/test-cases-values.js b/test/test-cases-values.js index 6d4fbb2..1d49bf1 100644 --- a/test/test-cases-values.js +++ b/test/test-cases-values.js @@ -175,7 +175,20 @@ module.exports = { "\"\\1F50E\"", singleValue([ { type: "string", stringType: "\"", value: "\ud83d\udd0e" } - ]) + ]), + ], + "escaped unicode 5 (extra short)": [ + "\"\\A\"", + singleValue([ + { type: "string", stringType: "\"", value: "\u000A" } + ]), + ], + "escaped unicode 6 (full length)": [ + "\"\\00000A\"", + singleValue([ + { type: "string", stringType: "\"", value: "\u000A" } + ]), + "\"\\A\"" ], "nested-item-with append": [ "linear-gradient(45deg) 25%", From 2555f5782d40d9aa645a73951608fdf09234875e Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Wed, 6 Jul 2016 19:16:31 +0900 Subject: [PATCH 10/31] Support unicode identifiers --- lib/parse.js | 32 ++++++++++++++++++++++++++------ lib/stringify.js | 18 ++++++++++++++---- package.json | 3 ++- test/test-cases.js | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 9841107..692373b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,6 +1,7 @@ "use strict"; var Parser = require("fastparse"); +var regexpu = require("regexpu-core"); function unescape(str) { return str.replace(/\\(.)/g, "$1"); @@ -164,11 +165,19 @@ function bracketEnd(match) { this.token.content += match; } -var parser = new Parser({ - selector: { - "/\\*([\\s\\S]*?)\\*/": commentMatch, - "\\.((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": typeMatch("class"), - "#((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": typeMatch("id"), +function getSelectors() { + // The assignment here is split to preserve the property enumeration order. + var selectors = { + "/\\*([\\s\\S]*?)\\*/": commentMatch + }; + // https://www.w3.org/TR/CSS21/syndata.html#characters + // 4.1.3: identifiers (...) can contain only the characters [a-zA-Z0-9] and + // ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_) + // + // 10ffff is the maximum allowed in current Unicode + selectors[regexpu("\\.((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", "u")] = typeMatch("class"); + selectors[regexpu("#((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", "u")] = typeMatch("id"); + var selectorsSecondHalf = { ":(not|matches|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)\\(": pseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-class"), @@ -185,7 +194,18 @@ var parser = new Parser({ "^\\s+": irrelevantSpacingStartMatch, "\\s+": spacingMatch, ".": invalidMatch - }, + }; + var selector; + for (selector in selectorsSecondHalf) { + if (Object.prototype.hasOwnProperty.call(selectorsSecondHalf, selector)) { + selectors[selector] = selectorsSecondHalf[selector]; + } + } + return selectors; +} + +var parser = new Parser({ + selector: getSelectors(), inBrackets: { "/\\*[\\s\\S]*?\\*/": addToCurrent, "\"([^\\\\\"]|\\\\.)*\"": addToCurrent, diff --git a/lib/stringify.js b/lib/stringify.js index b97710a..1c9d6aa 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -2,11 +2,21 @@ var stringify; -function escape(str) { +var regexpu = require("regexpu-core"); +var identifierEscapeRegexp = new RegExp( + regexpu("(^[^A-Za-z_\\-\\u{00a0}-\\u{10ffff}]|^\\-\\-|[^A-Za-z_0-9\\-\\u{00a0}-\\u{10ffff}])", "ug"), + "g" +); + +function escape(str, identifier) { if(str === "*") { return "*"; } - return str.replace(/(^[^A-Za-z_\\-]|^\-\-|[^A-Za-z_0-9\\-])/g, "\\$1"); + if (identifier) { + return str.replace(identifierEscapeRegexp, "\\$1"); + } else { + return str.replace(/(^[^A-Za-z_\\-]|^\-\-|[^A-Za-z_0-9\\-])/g, "\\$1"); + } } function stringifyWithoutBeforeAfter(tree) { @@ -18,9 +28,9 @@ function stringifyWithoutBeforeAfter(tree) { case "element": return (typeof tree.namespace === "string" ? escape(tree.namespace) + "|" : "") + escape(tree.name); case "class": - return "." + escape(tree.name); + return "." + escape(tree.name, true); case "id": - return "#" + escape(tree.name); + return "#" + escape(tree.name, true); case "attribute": return "[" + tree.content + "]"; case "spacing": diff --git a/package.json b/package.json index dd888ca..ec4afc0 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "homepage": "https://github.com/css-modules/css-selector-tokenizer", "dependencies": { "cssesc": "^0.1.0", - "fastparse": "^1.1.1" + "fastparse": "^1.1.1", + "regexpu-core": "^3.2.0" }, "devDependencies": { "chokidar-cli": "^0.2.1", diff --git a/test/test-cases.js b/test/test-cases.js index dd21bd6..86d48a5 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -65,6 +65,27 @@ module.exports = { ]) ], + "class name with high BMP character": [ + ".ๅญ—", + singleSelector([ + { type: "class", name: "ๅญ—" } + ]) + ], + + "class name with emoji": [ + ".๐Ÿค”", + singleSelector([ + { type: "class", name: "๐Ÿค”" } + ]) + ], + + "class name with multiple emoji": [ + ".๐Ÿ‘๐Ÿ‘Œ", + singleSelector([ + { type: "class", name: "๐Ÿ‘๐Ÿ‘Œ" } + ]) + ], + "id name": [ "#idName", singleSelector([ @@ -79,6 +100,20 @@ module.exports = { ]) ], + "id name with latin-1 character": [ + "#ยก", + singleSelector([ + { type: "id", name: "ยก" } + ]) + ], + + "id name with complex emoji": [ + ".๐Ÿ––๐Ÿผ", + singleSelector([ + { type: "class", name: "๐Ÿ––๐Ÿผ" } + ]) + ], + "pseudo class": [ ":before", singleSelector([ From 17379f6d08b0ec66567701d1f558bc0646ca6fdb Mon Sep 17 00:00:00 2001 From: "Diogo Franco (Kovensky)" Date: Wed, 6 Jul 2016 19:29:50 +0900 Subject: [PATCH 11/31] Downgrade to regexpu-core 1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ec4afc0..2823a8c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dependencies": { "cssesc": "^0.1.0", "fastparse": "^1.1.1", - "regexpu-core": "^3.2.0" + "regexpu-core": "^1.0.0" }, "devDependencies": { "chokidar-cli": "^0.2.1", From 89f9d09f51070842996e10d3b504ff1d912acbd2 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 20 Jul 2016 20:14:29 +1000 Subject: [PATCH 12/31] 0.5.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2823a8c..0bd55f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.5.4", + "version": "0.5.5", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 6fa054d971196fde29914249899c6c69d1335647 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 20 Jul 2016 20:15:27 +1000 Subject: [PATCH 13/31] 0.6.0 (Fixes #11 and #12) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0bd55f3..3c8b61f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.5.5", + "version": "0.6.0", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 200385a966c0a8992ab553ba4bfe6c8f36d2e9d2 Mon Sep 17 00:00:00 2001 From: Benjamin Hanes Date: Wed, 31 Aug 2016 11:20:57 -0400 Subject: [PATCH 14/31] Fix for stringifying Windows paths --- lib/stringifyValues.js | 6 +++--- test/test-cases-values.js | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/stringifyValues.js b/lib/stringifyValues.js index 976af12..a61d8d5 100644 --- a/lib/stringifyValues.js +++ b/lib/stringifyValues.js @@ -38,11 +38,11 @@ function stringifyWithoutBeforeAfter(tree) { var end = (tree.innerSpacingAfter || "") + ")"; switch(tree.stringType) { case "'": - return start + "'" + tree.url.replace(/'/g, "\\'") + "'" + end; + return start + "'" + tree.url.replace(/(\\)/g, "\\$1").replace(/'/g, "\\'") + "'" + end; case "\"": - return start + "\"" + tree.url.replace(/"/g, "\\\"") + "\"" + end; + return start + "\"" + tree.url.replace(/(\\)/g, "\\$1").replace(/"/g, "\\\"") + "\"" + end; default: - return start + tree.url.replace(/("|'|\))/g, "\\$1") + end; + return start + tree.url.replace(/("|'|\)|\\)/g, "\\$1") + end; } } } diff --git a/test/test-cases-values.js b/test/test-cases-values.js index 1d49bf1..105b00f 100644 --- a/test/test-cases-values.js +++ b/test/test-cases-values.js @@ -68,6 +68,12 @@ module.exports = { { type: "url", url: "ghi)j\"k", innerSpacingBefore: " " } ]) ], + "windows-urls": [ + "url('C:\\\\Users\\\\Test\\\\test.png')", + singleValue([ + { type: "url", url: "C:\\Users\\Test\\test.png", stringType: "'"} + ]) + ], "nested-item": [ "format('woff')", singleValue([ From d315d8d2a77cab97d90dcac5ee9f36c1d7688245 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 6 Sep 2016 08:47:37 +1000 Subject: [PATCH 15/31] 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c8b61f..6bcbe78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.6.0", + "version": "0.7.0", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 43dbcfc7e97b7b1445b3f98d641b74690a1722b9 Mon Sep 17 00:00:00 2001 From: Stefan Warman Date: Mon, 8 Oct 2018 15:19:21 +0200 Subject: [PATCH 16/31] Create LICENSE --- LICENSE | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..abdebcd --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2015 Tobias Koppers + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 81477c2ec013b69f54101f7dfa6bbc0d2fbe3f23 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Tue, 30 Oct 2018 14:29:34 +0100 Subject: [PATCH 17/31] 0.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6bcbe78..2eba535 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.7.0", + "version": "0.7.1", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 0700f97898a5db1c119928b56f827962f0a8a221 Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Sat, 22 Feb 2020 17:20:56 +0200 Subject: [PATCH 18/31] chore(deps): upgrade to latest stable - cleaned up travis.yml. it now runs node 10/12/13. - cleaner coverage report scripts for each service. - replaced deprecated instanbul with nyc. - removed autotest. mocha has its own watcher (npm test -- -w) --- .gitignore | 2 ++ .travis.yml | 18 ++++++++---------- lib/stringify.js | 6 ++---- package.json | 26 ++++++++++++-------------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index ba2a97b..3e3c0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules coverage +.nyc_output +package-lock.json diff --git a/.travis.yml b/.travis.yml index 48895d4..8892eee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,13 @@ -sudo: false language: node_js + node_js: - - "0.10" - - "0.12" - - "iojs" -script: npm run travis + - 10 + - 12 + - 13 -before_install: - - '[ "${TRAVIS_NODE_VERSION}" != "0.10" ] || npm install -g npm' +script: npm run cover after_success: - - cat ./coverage/lcov.info | node_modules/.bin/coveralls --verbose - - cat ./coverage/coverage.json | node_modules/codecov.io/bin/codecov.io.js - - rm -rf ./coverage + - npm run report:coveralls + - npm run report:codecov + diff --git a/lib/stringify.js b/lib/stringify.js index 1c9d6aa..f96662b 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -1,10 +1,8 @@ "use strict"; -var stringify; - var regexpu = require("regexpu-core"); var identifierEscapeRegexp = new RegExp( - regexpu("(^[^A-Za-z_\\-\\u{00a0}-\\u{10ffff}]|^\\-\\-|[^A-Za-z_0-9\\-\\u{00a0}-\\u{10ffff}])", "ug"), + regexpu("(^[^A-Za-z_\\-\\u{00a0}-\\u{10ffff}]|^--|[^A-Za-z_0-9\\-\\u{00a0}-\\u{10ffff}])", "ug"), "g" ); @@ -53,7 +51,7 @@ function stringifyWithoutBeforeAfter(tree) { } -stringify = function stringify(tree) { +function stringify(tree) { var str = stringifyWithoutBeforeAfter(tree); if(tree.before) { str = tree.before + str; diff --git a/package.json b/package.json index 2eba535..3bdec08 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,12 @@ "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { - "lint": "eslint lib", + "lint": "eslint .", "pretest": "npm run lint", "test": "mocha", - "autotest": "chokidar lib test -c 'npm test'", - "precover": "npm run lint", - "cover": "istanbul cover node_modules/mocha/bin/_mocha", - "travis": "npm run cover -- --report lcovonly", + "cover": "nyc npm test", + "report:coveralls": "nyc report --reporter=text-lcov | coveralls", + "report:codecov": "nyc report --reporter=text-lcov | codecov --pipe", "publish-patch": "npm test && npm version patch && git push && git push --tags && npm publish" }, "repository": { @@ -31,17 +30,16 @@ }, "homepage": "https://github.com/css-modules/css-selector-tokenizer", "dependencies": { - "cssesc": "^0.1.0", - "fastparse": "^1.1.1", - "regexpu-core": "^1.0.0" + "cssesc": "^3.0.0", + "fastparse": "^1.1.2", + "regexpu-core": "^4.6.0" }, "devDependencies": { - "chokidar-cli": "^0.2.1", - "codecov.io": "^0.1.2", - "coveralls": "^2.11.2", - "eslint": "^0.21.2", - "istanbul": "^0.3.14", - "mocha": "^2.2.5" + "codecov": "^3.6.5", + "coveralls": "^3.0.9", + "eslint": "^6.8.0", + "mocha": "^7.0.1", + "nyc": "^15.0.0" }, "directories": { "test": "test" From aa5fbbb33c4f4a0ebf237b61385a14ffe026b0b1 Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Sat, 22 Feb 2020 17:48:51 +0200 Subject: [PATCH 19/31] config(lint): stricter eslint configuration and several newly-found unneeded regex escapes. --- .eslintrc | 7 ------- .eslintrc.json | 12 ++++++++++++ lib/parseValues.js | 4 ++-- lib/stringify.js | 4 ++-- 4 files changed, 16 insertions(+), 11 deletions(-) delete mode 100644 .eslintrc create mode 100644 .eslintrc.json diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 89b84db..0000000 --- a/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { - "node": true - }, - "rules": { - } -} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..217c525 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,12 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2018 + } +} \ No newline at end of file diff --git a/lib/parseValues.js b/lib/parseValues.js index 97be01b..690c093 100644 --- a/lib/parseValues.js +++ b/lib/parseValues.js @@ -136,12 +136,12 @@ var parser = new Parser({ "url\\((\\s*)(\"(?:[^\\\\\"]|\\\\.)*\")(\\s*)\\)": urlMatch, "url\\((\\s*)('(?:[^\\\\']|\\\\.)*')(\\s*)\\)": urlMatch, "url\\((\\s*)((?:[^\\\\)'\"]|\\\\.)*)(\\s*)\\)": urlMatch, - "([\\w\-]+)\\((\\s*)": nestedItemMatch, + "([\\w-]+)\\((\\s*)": nestedItemMatch, "(\\s*)(\\))": nestedItemEndMatch, ",(\\s*)": commaMatch, "\\s+$": endSpacingMatch, "\\s+": spacingMatch, - "[^\\s,\)]+": itemMatch + "[^\\s,)]+": itemMatch } }); diff --git a/lib/stringify.js b/lib/stringify.js index f96662b..4673a8b 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -13,7 +13,7 @@ function escape(str, identifier) { if (identifier) { return str.replace(identifierEscapeRegexp, "\\$1"); } else { - return str.replace(/(^[^A-Za-z_\\-]|^\-\-|[^A-Za-z_0-9\\-])/g, "\\$1"); + return str.replace(/(^[^A-Za-z_\\-]|^--|[^A-Za-z_0-9\\-])/g, "\\$1"); } } @@ -60,6 +60,6 @@ function stringify(tree) { str = str + tree.after; } return str; -}; +} module.exports = stringify; From d45d1473cade579f38d9f566ae3ad331c0ba9747 Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Sat, 22 Feb 2020 17:58:45 +0200 Subject: [PATCH 20/31] README: update watch command also moved badges to top, and ensured https is used for codecov. --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5d80690..91976c9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# CSS Modules: CSS selector Tokenizer +# CSS Modules: css-selector-tokenizer +[![Build Status](https://travis-ci.org/css-modules/css-selector-tokenizer.svg?branch=master)](https://travis-ci.org/css-modules/css-selector-tokenizer) +[![coveralls.io](https://coveralls.io/repos/css-modules/css-selector-tokenizer/badge.svg?branch=master)](https://coveralls.io/r/css-modules/css-selector-tokenizer?branch=master) +[![codecov.io](https://codecov.io/github/css-modules/css-selector-tokenizer/coverage.svg?branch=master)](https://codecov.io/github/css-modules/css-selector-tokenizer?branch=master) Parses and stringifies CSS selectors. @@ -71,14 +74,9 @@ npm install npm test ``` -[![Build Status](https://travis-ci.org/css-modules/css-selector-tokenizer.svg?branch=master)](https://travis-ci.org/css-modules/css-selector-tokenizer) - -* Lines: [![Coverage Status](https://coveralls.io/repos/css-modules/css-selector-tokenizer/badge.svg?branch=master)](https://coveralls.io/r/css-modules/css-selector-tokenizer?branch=master) -* Statements: [![codecov.io](http://codecov.io/github/css-modules/css-selector-tokenizer/coverage.svg?branch=master)](http://codecov.io/github/css-modules/css-selector-tokenizer?branch=master) - ## Development -- `npm autotest` will watch `lib` and `test` for changes and retest +- `npm test -- -w` will watch `lib` and `test` for changes and retest ## License From 857d498d608f68e120bbd0e96f1e4c1670c561ad Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Sat, 29 Feb 2020 19:48:24 +0200 Subject: [PATCH 21/31] chore(deps): mocha@7.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bdec08..8e05ab4 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "codecov": "^3.6.5", "coveralls": "^3.0.9", "eslint": "^6.8.0", - "mocha": "^7.0.1", + "mocha": "^7.1.0", "nyc": "^15.0.0" }, "directories": { From 2a6cb09181152200f336e9756959d1c0d639b675 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Tue, 3 Mar 2020 13:55:29 +0300 Subject: [PATCH 22/31] chore(release): 0.7.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e05ab4..7554b83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.7.1", + "version": "0.7.2", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From a51c06c523c55be4aeb147ad60c1532403dc611d Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 28 May 2020 02:10:17 +0300 Subject: [PATCH 23/31] Add build to avoid using regexpu-core in runtime --- lib/parse.js | 6 +++--- lib/stringify.js | 7 ++----- lib/uni-regexp.js | 6 ++++++ package.json | 9 +++++---- scripts/build-regexpu.js | 26 ++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 lib/uni-regexp.js create mode 100644 scripts/build-regexpu.js diff --git a/lib/parse.js b/lib/parse.js index 692373b..632bdfd 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,7 +1,7 @@ "use strict"; var Parser = require("fastparse"); -var regexpu = require("regexpu-core"); +var uniRegexp = require("./uni-regexp"); function unescape(str) { return str.replace(/\\(.)/g, "$1"); @@ -175,8 +175,8 @@ function getSelectors() { // ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_) // // 10ffff is the maximum allowed in current Unicode - selectors[regexpu("\\.((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", "u")] = typeMatch("class"); - selectors[regexpu("#((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", "u")] = typeMatch("id"); + selectors[uniRegexp.typeMatchClass] = typeMatch("class"); + selectors[uniRegexp.typeMatchId] = typeMatch("id"); var selectorsSecondHalf = { ":(not|matches|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)\\(": pseudoClassStartMatch, diff --git a/lib/stringify.js b/lib/stringify.js index 4673a8b..bb63ee8 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -1,10 +1,7 @@ "use strict"; -var regexpu = require("regexpu-core"); -var identifierEscapeRegexp = new RegExp( - regexpu("(^[^A-Za-z_\\-\\u{00a0}-\\u{10ffff}]|^--|[^A-Za-z_0-9\\-\\u{00a0}-\\u{10ffff}])", "ug"), - "g" -); +var uniRegexp = require("./uni-regexp"); +var identifierEscapeRegexp = new RegExp(uniRegexp.identifierEscapeRegexp, "g"); function escape(str, identifier) { if(str === "*") { diff --git a/lib/uni-regexp.js b/lib/uni-regexp.js new file mode 100644 index 0000000..8133b6f --- /dev/null +++ b/lib/uni-regexp.js @@ -0,0 +1,6 @@ +/* AUTO GENERATED */ +module.exports = { + "typeMatchClass": "\\.((?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2DA-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))(?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2D0-9A-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))*)", + "typeMatchId": "#((?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2DA-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))(?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2D0-9A-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))*)", + "identifierEscapeRegexp": "(^[\\0-,\\.-@\\[-\\^`\\{-\\x9F]|^\\x2D\\x2D|[\\0-,\\.\\/:-@\\[-\\^`\\{-\\x9F])" +} \ No newline at end of file diff --git a/package.json b/package.json index 7554b83..f740571 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,10 @@ "main": "lib/index.js", "scripts": { "lint": "eslint .", - "pretest": "npm run lint", + "pretest": "npm run build && npm run lint", "test": "mocha", "cover": "nyc npm test", + "build": "node scripts/build-regexpu.js", "report:coveralls": "nyc report --reporter=text-lcov | coveralls", "report:codecov": "nyc report --reporter=text-lcov | codecov --pipe", "publish-patch": "npm test && npm version patch && git push && git push --tags && npm publish" @@ -31,15 +32,15 @@ "homepage": "https://github.com/css-modules/css-selector-tokenizer", "dependencies": { "cssesc": "^3.0.0", - "fastparse": "^1.1.2", - "regexpu-core": "^4.6.0" + "fastparse": "^1.1.2" }, "devDependencies": { "codecov": "^3.6.5", "coveralls": "^3.0.9", "eslint": "^6.8.0", "mocha": "^7.1.0", - "nyc": "^15.0.0" + "nyc": "^15.0.0", + "regexpu-core": "^4.6.0" }, "directories": { "test": "test" diff --git a/scripts/build-regexpu.js b/scripts/build-regexpu.js new file mode 100644 index 0000000..be784f4 --- /dev/null +++ b/scripts/build-regexpu.js @@ -0,0 +1,26 @@ +var fs = require("fs"); +var path = require("path"); +var regexpu = require("regexpu-core"); + +var uniReg = { + typeMatchClass: regexpu( + "\\.((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", + "u" + ), + typeMatchId: regexpu( + "#((?:\\\\.|[A-Za-z_\\-\\u{00a0}-\\u{10ffff}])(?:\\\\.|[A-Za-z_\\-0-9\\u{00a0}-\\u{10ffff}])*)", + "u" + ), + identifierEscapeRegexp: regexpu( + "(^[^A-Za-z_\\-\\u{00a0}-\\u{10ffff}]|^--|[^A-Za-z_0-9\\-\\u{00a0}-\\u{10ffff}])", + "ug" + ), +}; + +var targetFile = path.join(__dirname, "../lib/uni-regexp.js"); + +fs.writeFileSync( + targetFile, + "/* AUTO GENERATED */\nmodule.exports = " + JSON.stringify(uniReg, null, 4) +); +console.log('Done building ' + targetFile) \ No newline at end of file From 5f438f930959daa5b68d41c4e948fda9640ca5fe Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Thu, 28 May 2020 02:12:08 +0300 Subject: [PATCH 24/31] add newline in generated file --- lib/uni-regexp.js | 2 +- scripts/build-regexpu.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/uni-regexp.js b/lib/uni-regexp.js index 8133b6f..a60623d 100644 --- a/lib/uni-regexp.js +++ b/lib/uni-regexp.js @@ -3,4 +3,4 @@ module.exports = { "typeMatchClass": "\\.((?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2DA-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))(?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2D0-9A-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))*)", "typeMatchId": "#((?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2DA-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))(?:\\\\(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])|(?:[\\x2D0-9A-Z_a-z\\xA0-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))*)", "identifierEscapeRegexp": "(^[\\0-,\\.-@\\[-\\^`\\{-\\x9F]|^\\x2D\\x2D|[\\0-,\\.\\/:-@\\[-\\^`\\{-\\x9F])" -} \ No newline at end of file +} diff --git a/scripts/build-regexpu.js b/scripts/build-regexpu.js index be784f4..32fc29e 100644 --- a/scripts/build-regexpu.js +++ b/scripts/build-regexpu.js @@ -21,6 +21,6 @@ var targetFile = path.join(__dirname, "../lib/uni-regexp.js"); fs.writeFileSync( targetFile, - "/* AUTO GENERATED */\nmodule.exports = " + JSON.stringify(uniReg, null, 4) + "/* AUTO GENERATED */\nmodule.exports = " + JSON.stringify(uniReg, null, 4) + '\n' ); console.log('Done building ' + targetFile) \ No newline at end of file From 3f803f11d45f8c89c1da1e3438831f24ef3d8f80 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Mon, 1 Jun 2020 02:17:09 +0300 Subject: [PATCH 25/31] rename build and remove from test flow --- package.json | 4 ++-- scripts/build-regexpu.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f740571..df38891 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "main": "lib/index.js", "scripts": { "lint": "eslint .", - "pretest": "npm run build && npm run lint", + "pretest": "npm run lint", "test": "mocha", "cover": "nyc npm test", - "build": "node scripts/build-regexpu.js", + "build-regexpu": "node scripts/build-regexpu.js", "report:coveralls": "nyc report --reporter=text-lcov | coveralls", "report:codecov": "nyc report --reporter=text-lcov | codecov --pipe", "publish-patch": "npm test && npm version patch && git push && git push --tags && npm publish" diff --git a/scripts/build-regexpu.js b/scripts/build-regexpu.js index 32fc29e..0db18c8 100644 --- a/scripts/build-regexpu.js +++ b/scripts/build-regexpu.js @@ -23,4 +23,5 @@ fs.writeFileSync( targetFile, "/* AUTO GENERATED */\nmodule.exports = " + JSON.stringify(uniReg, null, 4) + '\n' ); + console.log('Done building ' + targetFile) \ No newline at end of file From c9b823712671c75130069253aaeee0d6b589b72c Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Mon, 1 Jun 2020 02:18:57 +0300 Subject: [PATCH 26/31] format --- scripts/build-regexpu.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/build-regexpu.js b/scripts/build-regexpu.js index 0db18c8..2838f50 100644 --- a/scripts/build-regexpu.js +++ b/scripts/build-regexpu.js @@ -21,7 +21,9 @@ var targetFile = path.join(__dirname, "../lib/uni-regexp.js"); fs.writeFileSync( targetFile, - "/* AUTO GENERATED */\nmodule.exports = " + JSON.stringify(uniReg, null, 4) + '\n' + "/* AUTO GENERATED */\nmodule.exports = " + + JSON.stringify(uniReg, null, 4) + + "\n" ); -console.log('Done building ' + targetFile) \ No newline at end of file +console.log("Done building " + targetFile); From b903b126bf6ed18db35bd03f941e46335b075ddc Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Tue, 21 Jul 2020 15:35:14 +0300 Subject: [PATCH 27/31] chore(release): 0.7.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df38891..3e61900 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.7.2", + "version": "0.7.3", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { From 113b7dee5878644e79ec9fc656827698a17f064f Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Wed, 26 May 2021 15:42:24 +0300 Subject: [PATCH 28/31] feat: parse :is and :where as nested-pseudo-classes --- lib/parse.js | 2 +- test/test-cases.js | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index 632bdfd..2d058e9 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -178,7 +178,7 @@ function getSelectors() { selectors[uniRegexp.typeMatchClass] = typeMatch("class"); selectors[uniRegexp.typeMatchId] = typeMatch("id"); var selectorsSecondHalf = { - ":(not|matches|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, + ":(not|matches|is|where|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)\\(": pseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-class"), "::((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-element"), diff --git a/test/test-cases.js b/test/test-cases.js index 86d48a5..c4160c9 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -361,7 +361,50 @@ module.exports = { ] } ]) ], - + "nested pseudo class with multiple selectors (:is)": [ + ":is( h1, h2 )", + singleSelector([ + { + type: "nested-pseudo-class", + name: "is", + nodes: [ + { + type: "selector", + nodes: [{ type: "element", name: "h1" }], + before: " ", + }, + { + type: "selector", + nodes: [{ type: "element", name: "h2" }], + before: " ", + after: " ", + }, + ], + }, + ]), + ], + "nested pseudo class with multiple selectors (:where)": [ + ":where( h1, h2 )", + singleSelector([ + { + type: "nested-pseudo-class", + name: "where", + nodes: [ + { + type: "selector", + nodes: [{ type: "element", name: "h1" }], + before: " ", + }, + { + type: "selector", + nodes: [{ type: "element", name: "h2" }], + before: " ", + after: " ", + }, + ], + }, + ]), + ], "available nested pseudo classes": [ ":not(:active):matches(:focus)", singleSelector([ From 36cee5dc8ddb93aa4c6d46363afafbc310cbaf72 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Wed, 26 May 2021 15:49:21 +0300 Subject: [PATCH 29/31] use tabs --- test/test-cases.js | 94 ++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/test/test-cases.js b/test/test-cases.js index c4160c9..efc3242 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -361,50 +361,56 @@ module.exports = { ] } ]) ], - "nested pseudo class with multiple selectors (:is)": [ - ":is( h1, h2 )", - singleSelector([ - { - type: "nested-pseudo-class", - name: "is", - nodes: [ - { - type: "selector", - nodes: [{ type: "element", name: "h1" }], - before: " ", - }, - { - type: "selector", - nodes: [{ type: "element", name: "h2" }], - before: " ", - after: " ", - }, - ], - }, - ]), - ], - "nested pseudo class with multiple selectors (:where)": [ - ":where( h1, h2 )", - singleSelector([ - { - type: "nested-pseudo-class", - name: "where", - nodes: [ - { - type: "selector", - nodes: [{ type: "element", name: "h1" }], - before: " ", - }, - { - type: "selector", - nodes: [{ type: "element", name: "h2" }], - before: " ", - after: " ", - }, - ], - }, - ]), - ], + "nested pseudo class with multiple selectors (:is)": [ + ":is( h1, h2 )", + singleSelector([{ + type: "nested-pseudo-class", + name: "is", + nodes: [{ + type: "selector", + nodes: [{ + type: "element", + name: "h1" + }], + before: " ", + }, + { + type: "selector", + nodes: [{ + type: "element", + name: "h2" + }], + before: " ", + after: " ", + }, + ], + }, ]), + ], + "nested pseudo class with multiple selectors (:where)": [ + ":where( h1, h2 )", + singleSelector([{ + type: "nested-pseudo-class", + name: "where", + nodes: [{ + type: "selector", + nodes: [{ + type: "element", + name: "h1" + }], + before: " ", + }, + { + type: "selector", + nodes: [{ + type: "element", + name: "h2" + }], + before: " ", + after: " ", + }, + ], + }, ]), + ], "available nested pseudo classes": [ ":not(:active):matches(:focus)", singleSelector([ From cac8248477695021afe15e5e2c8c20f5dcbd42e4 Mon Sep 17 00:00:00 2001 From: Barak Igal Date: Tue, 1 Jun 2021 17:17:26 +0300 Subject: [PATCH 30/31] added support for :any and :-vendor-any --- lib/parse.js | 2 +- test/test-cases.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/parse.js b/lib/parse.js index 2d058e9..2c3a855 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -178,7 +178,7 @@ function getSelectors() { selectors[uniRegexp.typeMatchClass] = typeMatch("class"); selectors[uniRegexp.typeMatchId] = typeMatch("id"); var selectorsSecondHalf = { - ":(not|matches|is|where|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, + ":(not|any|-\\w+?-any|matches|is|where|has|local|global)\\((\\s*)": nestedPseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)\\(": pseudoClassStartMatch, ":((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-class"), "::((?:\\\\.|[A-Za-z_\\-0-9])+)": typeMatch("pseudo-element"), diff --git a/test/test-cases.js b/test/test-cases.js index efc3242..f74a557 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -411,6 +411,56 @@ module.exports = { ], }, ]), ], + "nested pseudo class with multiple selectors (:any)": [ + ":any( h1, h2 )", + singleSelector([{ + type: "nested-pseudo-class", + name: "any", + nodes: [{ + type: "selector", + nodes: [{ + type: "element", + name: "h1" + }], + before: " ", + }, + { + type: "selector", + nodes: [{ + type: "element", + name: "h2" + }], + before: " ", + after: " ", + }, + ], + }, ]), + ], + "nested pseudo class with multiple selectors (:-vendor-any)": [ + ":-vendor-any( h1, h2 )", + singleSelector([{ + type: "nested-pseudo-class", + name: "-vendor-any", + nodes: [{ + type: "selector", + nodes: [{ + type: "element", + name: "h1" + }], + before: " ", + }, + { + type: "selector", + nodes: [{ + type: "element", + name: "h2" + }], + before: " ", + after: " ", + }, + ], + }, ]), + ], "available nested pseudo classes": [ ":not(:active):matches(:focus)", singleSelector([ From 7af8f43a93e1949046510da3c0e47e42ed6cf122 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Tue, 1 Jun 2021 18:24:22 +0300 Subject: [PATCH 31/31] chore(release): 0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e61900..0634278 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.7.3", + "version": "0.8.0", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": {