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/.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/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 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/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 1c9d6aa..bb63ee8 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -1,12 +1,7 @@ "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"), - "g" -); +var uniRegexp = require("./uni-regexp"); +var identifierEscapeRegexp = new RegExp(uniRegexp.identifierEscapeRegexp, "g"); function escape(str, identifier) { if(str === "*") { @@ -15,7 +10,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"); } } @@ -53,7 +48,7 @@ function stringifyWithoutBeforeAfter(tree) { } -stringify = function stringify(tree) { +function stringify(tree) { var str = stringifyWithoutBeforeAfter(tree); if(tree.before) { str = tree.before + str; @@ -62,6 +57,6 @@ stringify = function stringify(tree) { str = str + tree.after; } return str; -}; +} module.exports = stringify; 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 6bcbe78..0634278 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { "name": "css-selector-tokenizer", - "version": "0.7.0", + "version": "0.8.0", "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", + "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" }, "repository": { @@ -31,17 +31,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" }, "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.1.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([