diff --git a/lib/parse.js b/lib/parse.js index 692373b..2c3a855 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,10 +175,10 @@ 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, + ":(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/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..a60623d --- /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])" +} diff --git a/package.json b/package.json index 7554b83..0634278 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-selector-tokenizer", - "version": "0.7.2", + "version": "0.8.0", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { @@ -8,6 +8,7 @@ "pretest": "npm run lint", "test": "mocha", "cover": "nyc npm test", + "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" @@ -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..2838f50 --- /dev/null +++ b/scripts/build-regexpu.js @@ -0,0 +1,29 @@ +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) + + "\n" +); + +console.log("Done building " + targetFile); diff --git a/test/test-cases.js b/test/test-cases.js index 86d48a5..f74a557 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -361,7 +361,106 @@ 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 (: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([