pax_global_header00006660000000000000000000000064140554504460014521gustar00rootroot0000000000000052 comment=7af8f43a93e1949046510da3c0e47e42ed6cf122 css-selector-tokenizer-0.8.0/000077500000000000000000000000001405545044600161445ustar00rootroot00000000000000css-selector-tokenizer-0.8.0/.eslintrc.json000066400000000000000000000003221405545044600207350ustar00rootroot00000000000000{ "env": { "browser": true, "commonjs": true, "es6": true, "node": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 2018 } }css-selector-tokenizer-0.8.0/.gitignore000066400000000000000000000000641405545044600201340ustar00rootroot00000000000000node_modules coverage .nyc_output package-lock.json css-selector-tokenizer-0.8.0/.travis.yml000066400000000000000000000002211405545044600202500ustar00rootroot00000000000000language: node_js node_js: - 10 - 12 - 13 script: npm run cover after_success: - npm run report:coveralls - npm run report:codecov css-selector-tokenizer-0.8.0/LICENSE000066400000000000000000000020361405545044600171520ustar00rootroot00000000000000Copyright 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. css-selector-tokenizer-0.8.0/README.md000066400000000000000000000045151405545044600174300ustar00rootroot00000000000000# 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. ``` js import Tokenizer from "css-selector-tokenizer"; let input = "a#content.active > div::first-line [data-content], a:not(:visited)"; Tokenizer.parse(input); // === expected let expected = { type: "selectors", nodes: [ { type: "selector", nodes: [ { type: "element", name: "a" }, { type: "id", name: "content" }, { type: "class", name: "active" }, { type: "operator", operator: ">", before: " ", after: " " }, { type: "element", name: "div" }, { type: "pseudo-element", name: "first-line" }, { type: "spacing", value: " " }, { type: "attribute", content: "data-content" }, ] }, { type: "selector", nodes: [ { type: "element", name: "a" }, { type: "nested-pseudo-class", name: "not", nodes: [ { type: "selector", nodes: [ { type: "pseudo-class", name: "visited" } ] } ] } ], before: " " } ] } Tokenizer.stringify(expected) // === input // * => { type: "universal" } // foo|element = { type: "element", name: "element", namespace: "foo" } // *|* = { type: "universal", namespace: "*" } // :has(h1, h2) => { type: "nested-pseudo-class", name: "has", nodes: [ // { // type: "selector", // nodes: [ // { type: "element", name: "h1" } // ] // }, // { // type: "selector", // nodes: [ // { type: "element", name: "h2" } // ], // before: " " // } // ] } ``` ## Building ``` npm install npm test ``` ## Development - `npm test -- -w` will watch `lib` and `test` for changes and retest ## License MIT ## With thanks - Mark Dalgleish - Glen Maddern - Guy Bedford --- Tobias Koppers, 2015. css-selector-tokenizer-0.8.0/lib/000077500000000000000000000000001405545044600167125ustar00rootroot00000000000000css-selector-tokenizer-0.8.0/lib/index.js000066400000000000000000000002701405545044600203560ustar00rootroot00000000000000exports.parse = require("./parse"); exports.stringify = require("./stringify"); exports.parseValues = require("./parseValues"); exports.stringifyValues = require("./stringifyValues"); css-selector-tokenizer-0.8.0/lib/parse.js000066400000000000000000000122011405545044600203560ustar00rootroot00000000000000"use strict"; var Parser = require("fastparse"); var uniRegexp = require("./uni-regexp"); function unescape(str) { return str.replace(/\\(.)/g, "$1"); } function commentMatch(match, content) { this.selector.nodes.push({ type: "comment", content: content }); } function typeMatch(type) { return function(match, name) { this.selector.nodes.push({ type: type, name: unescape(name) }); }; } function pseudoClassStartMatch(match, name) { var newToken = { type: "pseudo-class", name: unescape(name), content: "" }; this.selector.nodes.push(newToken); this.token = newToken; this.brackets = 1; return "inBrackets"; } function nestedPseudoClassStartMatch(match, name, after) { var newSelector = { type: "selector", nodes: [] }; var newToken = { type: "nested-pseudo-class", name: unescape(name), nodes: [newSelector] }; if(after) { newSelector.before = after; } this.selector.nodes.push(newToken); this.stack.push(this.root); this.root = newToken; this.selector = newSelector; } function nestedEnd(match, before) { if(this.stack.length > 0) { if(before) { this.selector.after = before; } this.root = this.stack.pop(); this.selector = this.root.nodes[this.root.nodes.length - 1]; } else { this.selector.nodes.push({ type: "invalid", value: match }); } } function operatorMatch(match, before, operator, after) { var token = { type: "operator", operator: operator }; if(before) { token.before = before; } if(after) { token.after = after; } this.selector.nodes.push(token); } function spacingMatch(match) { this.selector.nodes.push({ type: "spacing", value: match }); } function elementMatch(match, namespace, name) { var newToken = { type: "element", name: unescape(name) }; if(namespace) { newToken.namespace = unescape(namespace.substr(0, namespace.length - 1)); } this.selector.nodes.push(newToken); } function universalMatch(match, namespace) { var newToken = { type: "universal" }; if(namespace) { newToken.namespace = unescape(namespace.substr(0, namespace.length - 1)); } this.selector.nodes.push(newToken); } function attributeMatch(match, content) { this.selector.nodes.push({ type: "attribute", content: content }); } function invalidMatch(match) { this.selector.nodes.push({ type: "invalid", value: match }); } function irrelevantSpacingStartMatch(match) { this.selector.before = match; } function irrelevantSpacingEndMatch(match) { this.selector.after = match; } function nextSelectorMatch(match, before, after) { var newSelector = { type: "selector", nodes: [] }; if(before) { this.selector.after = before; } if(after) { newSelector.before = after; } this.root.nodes.push(newSelector); this.selector = newSelector; } function addToCurrent(match) { this.token.content += match; } function bracketStart(match) { this.token.content += match; this.brackets++; } function bracketEnd(match) { if(--this.brackets === 0) { return "selector"; } this.token.content += match; } 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[uniRegexp.typeMatchClass] = typeMatch("class"); selectors[uniRegexp.typeMatchId] = typeMatch("id"); var selectorsSecondHalf = { ":(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"), "(\\*\\|)((?:\\\\.|[A-Za-z_\\-0-9])+)": elementMatch, "(\\*\\|)\\*": universalMatch, "((?:\\\\.|[A-Za-z_\\-0-9])*\\|)?\\*": universalMatch, "((?:\\\\.|[A-Za-z_\\-0-9])*\\|)?((?:\\\\.|[A-Za-z_\\-])(?:\\\\.|[A-Za-z_\\-0-9])*)": elementMatch, "\\[([^\\]]+)\\]": attributeMatch, "(\\s*)\\)": nestedEnd, "(\\s*)((?:\\|\\|)|(?:>>)|[>+~])(\\s*)": operatorMatch, "(\\s*),(\\s*)": nextSelectorMatch, "\\s+$": irrelevantSpacingEndMatch, "^\\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, "'([^\\\\']|\\\\.)*'": addToCurrent, "[^()'\"/]+": addToCurrent, "\\(": bracketStart, "\\)": bracketEnd, ".": addToCurrent } }); function parse(str) { var selectorNode = { type: "selector", nodes: [] }; var rootNode = { type: "selectors", nodes: [ selectorNode ] }; parser.parse("selector", str, { stack: [], root: rootNode, selector: selectorNode }); return rootNode; } module.exports = parse; css-selector-tokenizer-0.8.0/lib/parseValues.js000066400000000000000000000067141405545044600215520ustar00rootroot00000000000000"use strict"; var Parser = require("fastparse"); function commentMatch(match, content) { this.value.nodes.push({ type: "comment", content: content }); } function spacingMatch(match) { var item = this.value.nodes[this.value.nodes.length - 1]; item.after = (item.after || "") + match; } function initialSpacingMatch(match) { this.value.before = match; } function endSpacingMatch(match) { this.value.after = match; } function unescapeString(content) { 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 String.fromCharCode(Math.floor((C - 0x10000) / 0x400) + 0xD800) + String.fromCharCode((C - 0x10000) % 0x400 + 0xDC00); } }); } function stringMatch(match, content) { var value = unescapeString(content); this.value.nodes.push({ type: "string", value: value, stringType: match[0] }); } function commaMatch(match, spacing) { var newValue = { type: "value", nodes: [] }; if(spacing) { newValue.before = spacing; } this.root.nodes.push(newValue); this.value = newValue; } function itemMatch(match) { this.value.nodes.push({ type: "item", name: match }); } function nestedItemMatch(match, name, spacing) { this.stack.push(this.root); this.root = { type: "nested-item", name: name, nodes: [ { type: "value", nodes: [] } ] }; if(spacing) { this.root.nodes[0].before = spacing; } this.value.nodes.push(this.root); this.value = this.root.nodes[0]; } function nestedItemEndMatch(match, spacing, remaining) { if(this.stack.length === 0) { if(spacing) { var item = this.value.nodes[this.value.nodes.length - 1]; item.after = (item.after || "") + spacing; } this.value.nodes.push({ type: "invalid", value: remaining }); } else { if(spacing) { this.value.after = spacing; } this.root = this.stack.pop(); this.value = this.root.nodes[this.root.nodes.length - 1]; } } function urlMatch(match, innerSpacingBefore, content, innerSpacingAfter) { var item = { type: "url" }; if(innerSpacingBefore) { item.innerSpacingBefore = innerSpacingBefore; } if(innerSpacingAfter) { item.innerSpacingAfter = innerSpacingAfter; } switch(content[0]) { case "\"": item.stringType = "\""; item.url = unescapeString(content.substr(1, content.length - 2)); break; case "'": item.stringType = "'"; item.url = unescapeString(content.substr(1, content.length - 2)); break; default: item.url = unescapeString(content); break; } this.value.nodes.push(item); } var parser = new Parser({ decl: { "^\\s+": initialSpacingMatch, "/\\*([\\s\\S]*?)\\*/": commentMatch, "\"((?:[^\\\\\"]|\\\\.)*)\"": stringMatch, "'((?:[^\\\\']|\\\\.)*)'": stringMatch, "url\\((\\s*)(\"(?:[^\\\\\"]|\\\\.)*\")(\\s*)\\)": urlMatch, "url\\((\\s*)('(?:[^\\\\']|\\\\.)*')(\\s*)\\)": urlMatch, "url\\((\\s*)((?:[^\\\\)'\"]|\\\\.)*)(\\s*)\\)": urlMatch, "([\\w-]+)\\((\\s*)": nestedItemMatch, "(\\s*)(\\))": nestedItemEndMatch, ",(\\s*)": commaMatch, "\\s+$": endSpacingMatch, "\\s+": spacingMatch, "[^\\s,)]+": itemMatch } }); function parseValues(str) { var valueNode = { type: "value", nodes: [] }; var rootNode = { type: "values", nodes: [ valueNode ] }; parser.parse("decl", str, { stack: [], root: rootNode, value: valueNode }); return rootNode; } module.exports = parseValues; css-selector-tokenizer-0.8.0/lib/stringify.js000066400000000000000000000031511405545044600212660ustar00rootroot00000000000000"use strict"; var uniRegexp = require("./uni-regexp"); var identifierEscapeRegexp = new RegExp(uniRegexp.identifierEscapeRegexp, "g"); function escape(str, identifier) { if(str === "*") { return "*"; } if (identifier) { return str.replace(identifierEscapeRegexp, "\\$1"); } else { return str.replace(/(^[^A-Za-z_\\-]|^--|[^A-Za-z_0-9\\-])/g, "\\$1"); } } function stringifyWithoutBeforeAfter(tree) { switch(tree.type) { case "selectors": return tree.nodes.map(stringify).join(","); case "selector": return tree.nodes.map(stringify).join(""); case "element": return (typeof tree.namespace === "string" ? escape(tree.namespace) + "|" : "") + escape(tree.name); case "class": return "." + escape(tree.name, true); case "id": return "#" + escape(tree.name, true); case "attribute": return "[" + tree.content + "]"; case "spacing": return tree.value; case "pseudo-class": return ":" + escape(tree.name) + (typeof tree.content === "string" ? "(" + tree.content + ")" : ""); case "nested-pseudo-class": return ":" + escape(tree.name) + "(" + tree.nodes.map(stringify).join(",") + ")"; case "pseudo-element": return "::" + escape(tree.name); case "universal": return (typeof tree.namespace === "string" ? escape(tree.namespace) + "|" : "") + "*"; case "operator": return tree.operator; case "comment": return "/*" + tree.content + "*/"; case "invalid": return tree.value; } } function stringify(tree) { var str = stringifyWithoutBeforeAfter(tree); if(tree.before) { str = tree.before + str; } if(tree.after) { str = str + tree.after; } return str; } module.exports = stringify; css-selector-tokenizer-0.8.0/lib/stringifyValues.js000066400000000000000000000027331405545044600224530ustar00rootroot00000000000000"use strict"; var cssesc = require("cssesc"); var stringify; function escape(str, stringType) { return cssesc(str, { quotes: stringType === "\"" ? "double" : "single" }); } function stringifyWithoutBeforeAfter(tree) { switch(tree.type) { case "values": return tree.nodes.map(stringify).join(","); case "value": return tree.nodes.map(stringify).join(""); case "item": return tree.name; case "nested-item": return tree.name + "(" + tree.nodes.map(stringify).join(",") + ")"; case "invalid": return tree.value; case "comment": return "/*" + tree.content + "*/"; case "string": switch(tree.stringType) { case "'": return "'" + escape(tree.value, "'") + "'"; case "\"": return "\"" + escape(tree.value, "\"") + "\""; } /* istanbul ignore next */ throw new Error("Invalid stringType"); case "url": var start = "url(" + (tree.innerSpacingBefore || ""); var end = (tree.innerSpacingAfter || "") + ")"; switch(tree.stringType) { case "'": return start + "'" + tree.url.replace(/(\\)/g, "\\$1").replace(/'/g, "\\'") + "'" + end; case "\"": return start + "\"" + tree.url.replace(/(\\)/g, "\\$1").replace(/"/g, "\\\"") + "\"" + end; default: return start + tree.url.replace(/("|'|\)|\\)/g, "\\$1") + end; } } } stringify = function stringify(tree) { var str = stringifyWithoutBeforeAfter(tree); if(tree.before) { str = tree.before + str; } if(tree.after) { str = str + tree.after; } return str; }; module.exports = stringify; css-selector-tokenizer-0.8.0/lib/uni-regexp.js000066400000000000000000000031261405545044600213350ustar00rootroot00000000000000/* 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])" } css-selector-tokenizer-0.8.0/package.json000066400000000000000000000023771405545044600204430ustar00rootroot00000000000000{ "name": "css-selector-tokenizer", "version": "0.8.0", "description": "Parses and stringifies CSS selectors", "main": "lib/index.js", "scripts": { "lint": "eslint .", "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" }, "repository": { "type": "git", "url": "https://github.com/css-modules/css-selector-tokenizer.git" }, "keywords": [ "css-modules", "selectors" ], "files": [ "lib" ], "author": "Tobias Koppers @sokra", "license": "MIT", "bugs": { "url": "https://github.com/css-modules/css-selector-tokenizer/issues" }, "homepage": "https://github.com/css-modules/css-selector-tokenizer", "dependencies": { "cssesc": "^3.0.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", "regexpu-core": "^4.6.0" }, "directories": { "test": "test" } } css-selector-tokenizer-0.8.0/scripts/000077500000000000000000000000001405545044600176335ustar00rootroot00000000000000css-selector-tokenizer-0.8.0/scripts/build-regexpu.js000066400000000000000000000013751405545044600227530ustar00rootroot00000000000000var 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); css-selector-tokenizer-0.8.0/test/000077500000000000000000000000001405545044600171235ustar00rootroot00000000000000css-selector-tokenizer-0.8.0/test/parse.js000066400000000000000000000006551405545044600206010ustar00rootroot00000000000000"use strict"; /*globals describe it */ var assert = require("assert"); var Tokenizer = require("../"); describe("parse", function() { var testCases = require("./test-cases"); Object.keys(testCases).forEach(function(testCase) { it("should parse " + testCase, function() { var input = testCases[testCase][0]; var expected = testCases[testCase][1]; assert.deepEqual(Tokenizer.parse(input), expected); }); }); }); css-selector-tokenizer-0.8.0/test/parseValues.js000066400000000000000000000007071405545044600217570ustar00rootroot00000000000000"use strict"; /*globals describe it */ var assert = require("assert"); var Tokenizer = require("../"); describe("parseValues", function() { var testCases = require("./test-cases-values"); Object.keys(testCases).forEach(function(testCase) { it("should parse values " + testCase, function() { var input = testCases[testCase][0]; var expected = testCases[testCase][1]; assert.deepEqual(Tokenizer.parseValues(input), expected); }); }); }); css-selector-tokenizer-0.8.0/test/stringify.js000066400000000000000000000006711405545044600215030ustar00rootroot00000000000000"use strict"; /*globals describe it */ var assert = require("assert"); var Tokenizer = require("../"); describe("stringify", function() { var testCases = require("./test-cases"); Object.keys(testCases).forEach(function(testCase) { it("should stringify " + testCase, function() { var input = testCases[testCase][1]; var expected = testCases[testCase][0]; assert.deepEqual(Tokenizer.stringify(input), expected); }); }); }); css-selector-tokenizer-0.8.0/test/stringifyValues.js000066400000000000000000000007551405545044600226660ustar00rootroot00000000000000"use strict"; /*globals describe it */ var assert = require("assert"); var Tokenizer = require("../"); describe("stringifyValues", function() { var testCases = require("./test-cases-values"); Object.keys(testCases).forEach(function(testCase) { it("should stringify values " + testCase, function() { var input = testCases[testCase][1]; var expected = testCases[testCase][2] || testCases[testCase][0]; assert.deepEqual(Tokenizer.stringifyValues(input), expected); }); }); }); css-selector-tokenizer-0.8.0/test/test-cases-values.js000066400000000000000000000116501405545044600230340ustar00rootroot00000000000000"use strict"; function singleValue(nodes) { return { type: "values", nodes: [ { type: "value", nodes: nodes } ] }; } module.exports = { "item": [ "item", singleValue([ { type: "item", name: "item" } ]) ], "items": [ "item other-item", singleValue([ { type: "item", name: "item", after: " " }, { type: "item", name: "other-item" } ]) ], "multiple values": [ "item other-item, second-value 3rd-value ,item", { type: "values", nodes: [ { type: "value", nodes: [ { type: "item", name: "item", after: " " }, { type: "item", name: "other-item" } ] }, { type: "value", nodes: [ { type: "item", name: "second-value", after: " " }, { type: "item", name: "3rd-value", after: " " } ], before: " " }, { type: "value", nodes: [ { type: "item", name: "item" } ] } ] } ], "strings": [ "'ab\\'\"c d' \"e\\\" f\"", singleValue([ { type: "string", value: "ab'\"c d", stringType: "'", after: " " }, { type: "string", value: "e\" f", stringType: "\"" } ]) ], "comment": [ "item /* hello world */ item", singleValue([ { type: "item", name: "item", after: " " }, { type: "comment", content: " hello world ", after: " " }, { type: "item", name: "item" } ]) ], "urls": [ "url('ab\\'\"c d') url( \"e\\\" f\" ) url( ghi\\)j\\\"k)", singleValue([ { type: "url", url: "ab'\"c d", stringType: "'", after: " " }, { type: "url", url: "e\" f", stringType: "\"", innerSpacingBefore: " ", innerSpacingAfter: " ", after: " " }, { 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([ { type: "nested-item", name: "format", nodes: [ { type: "value", nodes: [ { type: "string", stringType: "'", value: "woff"} ]} ] } ]) ], "nested-item-difficult": [ "format('woff'), format( \"a b, c\" )", { type: "values", nodes: [ { type: "value", nodes: [ { type: "nested-item", name: "format", nodes: [ { type: "value", nodes: [ { type: "string", stringType: "'", value: "woff"} ]} ] } ] }, { type: "value", nodes: [ { type: "nested-item", name: "format", nodes: [ { type: "value", nodes: [ { type: "string", stringType: "\"", value: "a b, c"} ], before: " ", after: " " } ] } ], before: " " } ] } ], "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": [ " ) ) ", { type: "values", nodes: [ { type: "value", nodes: [ { type: "invalid", value: ")", after: " " }, { type: "invalid", value: ")" } ], before: " ", after: " " } ] } ], "spacing": [ " hello\n\t world\t", { type: "values", nodes: [ { type: "value", nodes: [ { type: "item", name: "hello", after: "\n\t " }, { type: "item", name: "world" } ], before: " ", after: "\t" } ] } ], "escaped unicode": [ "'\\F0E3\\\\\\'\"'", singleValue([ { type: "string", stringType: "'", value: "\uf0e3\\'\"" } ]) ], "escaped unicode 2": [ "\"\\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" } ]), ], "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%", singleValue([ { type: "nested-item", name: "linear-gradient", nodes: [ { type: "value", nodes: [ { type: "item", name: "45deg"} ]} ], after: " " }, { type: "item", name: "25%" } ]) ] }; css-selector-tokenizer-0.8.0/test/test-cases.js000066400000000000000000000250201405545044600215330ustar00rootroot00000000000000"use strict"; function singleSelector(nodes) { return { type: "selectors", nodes: [ { type: "selector", nodes: nodes } ] }; } module.exports = { "element": [ "body", singleSelector([ { type: "element", name: "body" } ]) ], "element with namespace": [ "foo|h1", singleSelector([ { type: "element", name: "h1", namespace: "foo" } ]) ], "element with any namespace": [ "*|h1", singleSelector([ { type: "element", name: "h1", namespace: "*" } ]) ], "element without namespace": [ "|h1", singleSelector([ { type: "element", name: "h1", namespace: "" } ]) ], "class name": [ ".className", singleSelector([ { type: "class", name: "className" } ]) ], "complex class name": [ ".class\\.Name", singleSelector([ { type: "class", name: "class.Name" } ]) ], "class name starting with number or dash": [ ".\\5\\#-\\.5 .\\--name.-name", singleSelector([ { type: "class", name: "5#-.5" }, { type: "spacing", value: " " }, { type: "class", name: "--name" }, { type: "class", name: "-name" } ]) ], "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([ { type: "id", name: "idName" } ]) ], "id name starting with number": [ "#\\5\\#-\\.5", singleSelector([ { type: "id", name: "5#-.5" } ]) ], "id name with latin-1 character": [ "#ยก", singleSelector([ { type: "id", name: "ยก" } ]) ], "id name with complex emoji": [ ".๐Ÿ––๐Ÿผ", singleSelector([ { type: "class", name: "๐Ÿ––๐Ÿผ" } ]) ], "pseudo class": [ ":before", singleSelector([ { type: "pseudo-class", name: "before" } ]) ], "pseudo class with escaping": [ ":be\\:fo\\#r\\.e", singleSelector([ { type: "pseudo-class", name: "be:fo#r.e" } ]) ], "pseudo class with content": [ ":abc(.className)", singleSelector([ { type: "pseudo-class", name: "abc", content: ".className" } ]) ], "pseudo class with content and escaping": [ ":a\\:b\\.c(.className)", singleSelector([ { type: "pseudo-class", name: "a:b.c", content: ".className" } ]) ], "nested pseudo class with content": [ ":not(.className)", singleSelector([ { type: "nested-pseudo-class", name: "not", nodes: [ { type: "selector", nodes: [ { type: "class", name: "className" } ] } ] } ]) ], "pseudo element": [ "::first-line", singleSelector([ { type: "pseudo-element", name: "first-line" } ]) ], "pseudo element with escaping": [ "::fir\\:\\:st\\.li\\#ne", singleSelector([ { type: "pseudo-element", name: "fir::st.li#ne" } ]) ], "universal": [ "*", singleSelector([ { type: "universal" } ]) ], "universal with namespace": [ "foo|*", singleSelector([ { type: "universal", namespace: "foo" } ]) ], "universal with namespace and escaping": [ "f\\|o\\.o|*", singleSelector([ { type: "universal", namespace: "f|o.o" } ]) ], "universal with any namespace": [ "*|*", singleSelector([ { type: "universal", namespace: "*" } ]) ], "universal without namespace": [ "|*", singleSelector([ { type: "universal", namespace: "" } ]) ], "attribute": [ "a[href=\"#xyz\"]", singleSelector([ { type: "element", name: "a" }, { type: "attribute", content: "href=\"#xyz\"" } ]) ], "comment": [ "/*** Hello *** World ***/", singleSelector([ { type: "comment", content: "** Hello *** World **" } ]) ], "operators": [ "a > .class-name~.x123+ div >> col || td", singleSelector([ { type: "element", name: "a" }, { type: "operator", operator: ">", before: " ", after: " " }, { type: "class", name: "class-name" }, { type: "operator", operator: "~" }, { type: "class", name: "x123" }, { type: "operator", operator: "+", after: " " }, { type: "element", name: "div" }, { type: "operator", operator: ">>", before: " ", after: " " }, { type: "element", name: "col" }, { type: "operator", operator: "||", before: " ", after: " " }, { type: "element", name: "td" } ]) ], "spacing": [ "\ta b\n\tc ", { type: "selectors", nodes: [ { type: "selector", nodes: [ { type: "element", name: "a" }, { type: "spacing", value: " " }, { type: "element", name: "b" }, { type: "spacing", value: "\n\t" }, { type: "element", name: "c" } ], before: "\t", after: " " } ] } ], "mulitple-selectors": [ "a.class, #classB ,c div .class", { type: "selectors", nodes: [ { type: "selector", nodes: [ { type: "element", name: "a" }, { type: "class", name: "class" } ] }, { type: "selector", nodes: [ { type: "id", name: "classB" } ], before: " ", after: " " }, { type: "selector", nodes: [ { type: "element", name: "c" }, { type: "spacing", value: " " }, { type: "element", name: "div" }, { type: "spacing", value: " " }, { type: "class", name: "class" } ] } ] } ], "pseudo class with difficult content": [ ":\\--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\"" } ]) ], "import": [ ":import(\"./module.css\")", singleSelector([ { type: "pseudo-class", name: "import", content: "\"./module.css\"" } ]) ], "export": [ ":export", singleSelector([ { type: "pseudo-class", name: "export" } ]) ], "local and global": [ ":global :local :global(.className a[href]):local( #idName )", singleSelector([ { type: "pseudo-class", name: "global" }, { type: "spacing", value: " " }, { type: "pseudo-class", name: "local" }, { type: "spacing", value: " " }, { type: "nested-pseudo-class", name: "global", nodes: [ { type: "selector", nodes: [ { type: "class", name: "className" }, { type: "spacing", value: " " }, { type: "element", name: "a" }, { type: "attribute", content: "href" } ] } ] }, { type: "nested-pseudo-class", name: "local", nodes: [ { type: "selector", nodes: [ { type: "id", name: "idName" } ], before: " ", after: " " } ] } ]) ], "nested pseudo class with multiple selectors": [ ":has( h1, h2 )", singleSelector([ { type: "nested-pseudo-class", name: "has", 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: " ", }, ], }, ]), ], "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([ { 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([ { type: "nested-pseudo-class", name: "has", nodes: [ { type: "selector", nodes: [ { type: "element", name: "h1" }, { type: "nested-pseudo-class", name: "not", nodes: [ { type: "selector", nodes: [ { type: "nested-pseudo-class", name: "has", nodes: [ { type: "selector", nodes: [ { type: "pseudo-class", name: "visited" } ] } ] } ] } ] } ] } ] } ]) ], "invalid chars": [ "a'b/c\"d[e", singleSelector([ { type: "element", name: "a" }, { type: "invalid", value: "'" }, { type: "element", name: "b" }, { type: "invalid", value: "/" }, { type: "element", name: "c" }, { type: "invalid", value: "\"" }, { type: "element", name: "d" }, { type: "invalid", value: "[" }, { type: "element", name: "e" } ]) ], "invalid nesting": [ "a ) b", singleSelector([ { type: "element", name: "a" }, { type: "invalid", value: " )" }, { type: "spacing", value: " " }, { type: "element", name: "b" } ]) ], "invalid number": [ "0%", singleSelector([ { type: "invalid", value: "0" }, { type: "invalid", value: "%" } ]) ], "invalid class name": [ ".10a0", singleSelector([ { type: "invalid", value: "." }, { type: "invalid", value: "1" }, { type: "invalid", value: "0" }, { type: "element", name: "a0" } ]) ] };