UNPKG

only-changed-jest-watch-plugin

Version:

Jest watch plugin for running either only the modified test (for TDD), or tests of dependant modules

1,320 lines (1,179 loc) 62 kB
/* * Copyright (C) 2007-2018 Diego Perini * All rights reserved. * * nwsapi.js - Fast CSS Selectors API Engine * * Author: Diego Perini <diego.perini at gmail com> * Version: 2.0.8 * Created: 20070722 * Release: 20180727 * * License: * http://javascript.nwbox.com/nwsapi/MIT-LICENSE * Download: * http://javascript.nwbox.com/nwsapi/nwsapi.js */ (function Export(global, factory) { 'use strict'; if (typeof module == 'object' && typeof exports == 'object') { module.exports = factory; } else if (typeof define == 'function' && define['amd']) { define(factory); } else { global.NW || (global.NW = { }); global.NW.Dom = factory(global, Export); } })(this, function Factory(global, Export) { var version = 'nwsapi-2.0.8', doc = global.document, root = doc.documentElement, WSP = '[\\x20\\t\\r\\n\\f]', CFG = { operators: '[~*^$|]=|=', combinators: '[\\x20\\t\\r\\n\\f>+~](?=[^>+~])' }, REX = { HasEscapes: RegExp('\\\\'), HexNumbers: RegExp('^[0-9a-fA-F]'), EscOrQuote: RegExp('^\\\\|[\\x22\\x27]'), RegExpChar: RegExp('(?:(?!\\\\)[\\\\^$.*+?()[\\]{}|\\/])' ,'g'), TrimSpaces: RegExp('[\\r\\n\\f]|^' + WSP + '+|' + WSP + '+$', 'g'), FixEscapes: RegExp('\\\\([0-9a-fA-F]{1,6}' + WSP + '?|.)|([\\x22\\x27])', 'g'), SplitGroup: RegExp(WSP + '*,' + WSP + '*(?![^\\[]*\\]|[^\\(]*\\)|[^\\{]*\\})', 'g') }, struct_1 = '(root|empty|(?:(?:first|last|only)(?:-child|-of-type)))\\b', struct_2 = '(nth(?:-last)?(?:-child|-of-type))(?:\\(\\s?(even|odd|(?:[-+]?\\d*)(?:n[-+]?\\d*)?)\\s?(?:\\)|$))', pseudo_1 = '(dir|lang)\\x28\\s?([-\\w]{2,})\\s?(?:\\x29|$)', pseudo_2 = ':?(after|before|first-letter|first-line|selection|backdrop|placeholder)\\b', noparm_1 = '(link|visited|target|scope|hover|active|focus|enabled|disabled|read-only|read-write|placeholder-shown)\\b', noparm_2 = '(default|checked|indeterminate|required|optional|valid|invalid|in-range|out-of-range)\\b', logicals = '(matches|not)\\x28\\s?([^()]*|[^\\x28]*\\x28[^\\x29]*\\x29)\\s?(?:\\x29|$)', Patterns = { // pseudo-classes struct_n: RegExp('^:(?:' + struct_1 + ')(.*)', 'i'), struct_p: RegExp('^:(?:' + struct_2 + ')(.*)', 'i'), hpseudos: RegExp('^:(?:' + pseudo_1 + ')(.*)', 'i'), epseudos: RegExp('^:(?:' + pseudo_2 + ')(.*)', 'i'), lpseudos: RegExp('^:(?:' + logicals + ')(.*)', 'i'), fpseudos: RegExp('^:(?:' + noparm_1 + ')(.*)', 'i'), ipseudos: RegExp('^:(?:' + noparm_2 + ')(.*)', 'i'), // combinators symbols children: RegExp('^' + WSP + '?\\>' + WSP + '?(.*)'), adjacent: RegExp('^' + WSP + '?\\+' + WSP + '?(.*)'), relative: RegExp('^' + WSP + '?\\~' + WSP + '?(.*)'), ancestor: RegExp('^' + WSP + '+(.*)'), // universal & namespace universal: RegExp('^\\*(.*)'), namespace: RegExp('^(\\w+|\\*)?\\|(.*)') }, // regexp to aproximate detection of RTL languages (Arabic) RTL = RegExp('^[\\u0591-\\u08ff\\ufb1d-\\ufdfd\\ufe70-\\ufefc ]+$'), // emulate firefox error strings qsNotArgs = 'Not enough arguments', qsInvalid = ' is not a valid selector', // detect structural pseudo-classes in selectors reNthElem = RegExp('(:nth(?:-last)?-child)', 'i'), reNthType = RegExp('(:nth(?:-last)?-of-type)', 'i'), // placeholder for global regexp reOptimizer, reSimpleNot, reValidator, // special handling configuration flags Config = { FASTCOMMA: true, IDS_DUPES: true, SIMPLENOT: true, LOGERRORS: true, VERBOSITY: true }, NAMESPACE, QUIRKS_MODE, HTML_DOCUMENT, ATTR_STD_OPS = { '=': 1, '^=': 1, '$=': 1, '|=': 1, '*=': 1, '~=': 1 }, HTML_TABLE = { 'accept': 1, 'accept-charset': 1, 'align': 1, 'alink': 1, 'axis': 1, 'bgcolor': 1, 'charset': 1, 'checked': 1, 'clear': 1, 'codetype': 1, 'color': 1, 'compact': 1, 'declare': 1, 'defer': 1, 'dir': 1, 'direction': 1, 'disabled': 1, 'enctype': 1, 'face': 1, 'frame': 1, 'hreflang': 1, 'http-equiv': 1, 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'method': 1, 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, 'readonly': 1, 'rel': 1, 'rev': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, 'shape': 1, 'target': 1, 'text': 1, 'type': 1, 'valign': 1, 'valuetype': 1, 'vlink': 1 }, Combinators = { }, Selectors = { }, Operators = { '=': { p1: '^', p2: '$', p3: 'true' }, '^=': { p1: '^', p2: '', p3: 'true' }, '$=': { p1: '', p2: '$', p3: 'true' }, '*=': { p1: '', p2: '', p3: 'true' }, '|=': { p1: '^', p2: '(-|$)', p3: 'true' }, '~=': { p1: '(^|\\s)', p2: '(\\s|$)', p3: 'true' } }, concatCall = function(nodes, callback) { var i = 0, l = nodes.length, list = Array(l); while (l > i) { if (false === callback(list[i] = nodes[i])) break; ++i; } return list; }, toArray = function(nodes) { var l = nodes.length, list = Array(l); while (l) { --l; list[l] = nodes[l]; } return list; }, switchContext = function(context, force) { var oldDoc = doc; doc = context.ownerDocument || context; if (force || oldDoc !== doc) { // force a new check for each document change // performed before the next select operation root = doc.documentElement; HTML_DOCUMENT = isHTML(doc); QUIRKS_MODE = HTML_DOCUMENT && doc.compatMode.indexOf('CSS') < 0; NAMESPACE = root && root.namespaceURI; Snapshot.doc = doc; Snapshot.root = root; } return (Snapshot.from = context); }, // convert single codepoint to UTF-16 encoding codePointToUTF16 = function(codePoint) { // out of range, use replacement character if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\\ufffd'; } // javascript strings are UTF-16 encoded if (codePoint < 0x10000) { var lowHex = '000' + codePoint.toString(16); return '\\u' + lowHex.substr(lowHex.length - 4); } // supplementary high + low surrogates return '\\u' + (((codePoint - 0x10000) >> 0x0a) + 0xd800).toString(16) + '\\u' + (((codePoint - 0x10000) % 0x400) + 0xdc00).toString(16); }, // convert single codepoint to string stringFromCodePoint = function(codePoint) { // out of range, use replacement character if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\ufffd'; } if (codePoint < 0x10000) { return String.fromCharCode(codePoint); } return String.fromCodePoint ? String.fromCodePoint(codePoint) : String.fromCharCode( ((codePoint - 0x10000) >> 0x0a) + 0xd800, ((codePoint - 0x10000) % 0x400) + 0xdc00); }, // convert escape sequence in a CSS string or identifier // to javascript string with javascript escape sequences convertEscapes = function(str) { return REX.HasEscapes.test(str) ? str.replace(REX.FixEscapes, function(substring, p1, p2) { // unescaped " or ' return p2 ? '\\' + p2 : // javascript strings are UTF-16 encoded REX.HexNumbers.test(p1) ? codePointToUTF16(parseInt(p1, 16)) : // \' \" REX.EscOrQuote.test(p1) ? substring : // \g \h \. \# etc p1; } ) : str; }, // convert escape sequence in a CSS string or identifier // to javascript string with characters representations unescapeIdentifier = function(str) { return REX.HasEscapes.test(str) ? str.replace(REX.FixEscapes, function(substring, p1, p2) { // unescaped " or ' return p2 ? p2 : // javascript strings are UTF-16 encoded REX.HexNumbers.test(p1) ? stringFromCodePoint(parseInt(p1, 16)) : // \' \" REX.EscOrQuote.test(p1) ? substring : // \g \h \. \# etc p1; } ) : str; }, method = { '#': 'getElementById', '*': 'getElementsByTagName', '.': 'getElementsByClassName' }, set_compat = function() { return !Config.FASTCOMMA ? { '#': function(c, n, z) { return function(e, f) { return byId(n, c); }; }, '*': function(c, n, z) { return function(e, f) { return byTag(n, c); }; }, '.': function(c, n, z) { return function(e, f) { return byClass(n, c); }; } } : { '#': function(c, n, z) { return function(e, f) { return validate(c, n, z, '#') ? z : (z = byId(n, c)); }; }, '*': function(c, n, z) { return function(e, f) { return validate(c, n, z, '*') ? z : (z = byTag(n, c)); }; }, '.': function(c, n, z) { return function(e, f) { return validate(c, n, z, '.') ? z : (z = byClass(n, c)); }; } }; }, compat = set_compat(), set_domapi = function() { var mapped = { '@': function(c, n, z) { return function(e, f) { return byId(n, c); }; }, '#': function(c, n, z) { return function(e, f) { if (e && z) return z; z = c.getElementById(n); return z = z ? [ z ] : none; };}, '*': function(c, n, z) { return function(e, f) { if (e && z) return z; z = c.getElementsByTagName(n); return f ? concatCall(z, f) : toArray(z); };}, '.': function(c, n, z) { return function(e, f) { if (e && z) return z; z = c.getElementsByClassName(n); return f ? concatCall(z, f) : toArray(z); };} }, natives = mapped; if (Config.IDS_DUPES) natives['#'] = mapped['@']; delete natives['@']; return natives; }, domapi = set_domapi(), // validate memoized HTMLCollections validate = function(context, ident, list, type) { var c, i, j, k, l, els, test; if (!list) { return false; } l = list.length; k = ident.length; for (i = 0; k > i; ++i) { els = compat[type](context, ident[i]); if (!els) continue; test = toArray(els); for (j = 0, c = 0; l > j; ++j) { if (list[j] === test[c]) { ++c; } } if (c === 0 || test.length !== c) { return false; } } return true; }, // iterative DOM LTR traversal, configurable by replacing // the conditional part (@) that accept returned elements walk = '"use strict"; return function(c) { var e = c, r = [ ], n = e.firstElementChild; while(e = n) {' + 'if (@) { r[r.length] = e; } if (n = e.firstElementChild || e.nextElementSibling) continue;' + 'while (!n && (e = e.parentElement) && e !== c) { n = e.nextElementSibling; } } return r; }', // getElementById from context byId = function(ids, context) { var e, elements, resolver, test, reIds, api = method['#']; if (typeof ids == 'string') { ids = [ ids ]; } if (!Config.IDS_DUPES && ids.length < 2 && context[api]) { return (e = context[api](ids[0])) ? [ e ] : none; } else if (Config.IDS_DUPES && ids.length < 2) { if ('all' in context) { if ((e = context.all[ids[0]])) { // bugfixing if (e.nodeType == 1) return e.getAttribute('id') != ids[0] ? [ ] : [ e ]; for (i = 0, l = e.length, elements = [ ]; l > i; ++i) { if (e[i].id == ids[0]) elements[elements.length] = e[i]; } return elements && elements.length ? elements : [ elements ]; } else return none; } } // multiple ids names if (ids.length > 1) { test = 't.test(e.getAttribute("id"))'; reIds = RegExp('^(?:' + ids.join('|') + ')$', 'i'); } else { test = 'e.getAttribute("id")==t'; } // build the resolver and execute it resolver = Function('t', walk.replace('@', test))(reIds || ids[0]); return resolver(context); }, // specialized getElementsByTagName // collect one or multiple tag names from context // @tag may be a tag name or an array of tag names byTag = function(tag, context) { var resolver, test, reTag; if (typeof tag == 'string') { tag = [ tag ]; } // if available use the DOM API to collect the nodes if (tag.length < 2 && method['*'] in context) { return context[method['*']](tag[0]); } // multiple tag names if (tag.includes('*')) { test = 'true'; } else if (tag.length > 1) { test = 't.test(e.nodeName)'; reTag = RegExp('^(?:' + tag.join('|') + ')$', 'i'); } else { test = 'e.nodeName==t'; } // build the resolver and execute it resolver = Function('t', walk.replace('@', test))(reTag || tag[0]); return resolver(context); }, // specialized getElementsByClassName // collect one or multiple class names from context // @cls may be a class name or an array of class names byClass = function(cls, context) { var resolver, test, reCls, cs; if (typeof cls == 'string') { cls = [ cls ]; } // if available use the DOM API to collect the nodes if (cls.length < 2 && method['.'] in context) { return context[method['.']](cls[0]); } // prepare the class name filter regexp cs = QUIRKS_MODE ? 'i' : ''; // multiple class names test = 't.test(e.getAttribute("class"))'; reCls = RegExp('(^|\\s)' + cls.join('|') + '(\\s|$)', cs); // build the resolver and execute it resolver = Function('t', walk.replace('@', test))(reCls || cls[0]); return resolver(context); }, // fast resolver for the :nth-child() and :nth-last-child() pseudo-classes nthElement = (function() { var idx = 0, len = 0, set = 0, parent = undefined, parents = Array(), nodes = Array(); return function(element, dir) { // ensure caches are emptied after each run, invoking with dir = 2 if (dir == 2) { idx = 0; len = 0; set = 0; nodes.length = 0; parents.length = 0; parent = undefined; return -1; } var e, i, j, k, l; if (parent === element.parentElement) { i = set; j = idx; l = len; } else { l = parents.length; parent = element.parentElement; for (i = -1, j = 0, k = l - 1; l > j; ++j, --k) { if (parents[j] === parent) { i = j; break; } if (parents[k] === parent) { i = k; break; } } if (i < 0) { parents[i = l] = parent; l = 0; nodes[i] = Array(); e = parent && parent.firstElementChild || element; while (e) { nodes[i][l] = e; if (e === element) j = l; e = e.nextElementSibling; ++l; } set = i; idx = 0; len = l; if (l < 2) return l; } else { l = nodes[i].length; set = i; } } if (element !== nodes[i][j] && element !== nodes[i][j = 0]) { for (j = 0, e = nodes[i], k = l - 1; l > j; ++j, --k) { if (e[j] === element) { break; } if (e[k] === element) { j = k; break; } } } idx = j + 1; len = l; return dir ? l - j : idx; }; })(), // fast resolver for the :nth-of-type() and :nth-last-of-type() pseudo-classes nthOfType = (function() { var idx = 0, len = 0, set = 0, parent = undefined, parents = Array(), nodes = Array(); return function(element, dir) { // ensure caches are emptied after each run, invoking with dir = 2 if (dir == 2) { idx = 0; len = 0; set = 0; nodes.length = 0; parents.length = 0; parent = undefined; return -1; } var e, i, j, k, l, name = element.nodeName; if (nodes[set] && nodes[set][name] && parent === element.parentElement) { i = set; j = idx; l = len; } else { l = parents.length; parent = element.parentElement; for (i = -1, j = 0, k = l - 1; l > j; ++j, --k) { if (parents[j] === parent) { i = j; break; } if (parents[k] === parent) { i = k; break; } } if (i < 0 || !nodes[i][name]) { parents[i = l] = parent; nodes[i] || (nodes[i] = Object()); l = 0; nodes[i][name] = Array(); e = parent && parent.firstElementChild || element; while (e) { if (e === element) j = l; if (e.nodeName == name) { nodes[i][name][l] = e; ++l; } e = e.nextElementSibling; } set = i; idx = j; len = l; if (l < 2) return l; } else { l = nodes[i][name].length; set = i; } } if (element !== nodes[i][name][j] && element !== nodes[i][name][j = 0]) { for (j = 0, e = nodes[i][name], k = l - 1; l > j; ++j, --k) { if (e[j] === element) { break; } if (e[k] === element) { j = k; break; } } } idx = j + 1; len = l; return dir ? l - j : idx; }; })(), // check if the document type is HTML isHTML = function(node) { var doc = node.ownerDocument || node; return doc.nodeType == 9 && doc.contentType.indexOf('/html') > 0 && doc.createElement('DiV').nodeName == 'DIV'; }, // configure the engine to use special handling configure = function(option) { if (typeof option == 'string') { return !!Config[option]; } if (typeof option != 'object') { return Config; } for (var i in option) { Config[i] = !!option[i]; if (i == 'SIMPLENOT') { matchResolvers = { }; selectResolvers = { }; } else if (i == 'FASTCOMMA') { compat = set_compat(); } else if (i == 'IDS_DUPES') { domapi = set_domapi(); } } setIdentifierSyntax(); return true; }, // centralized error and exceptions handling emit = function(message, proto) { var err; if (Config.VERBOSITY) { if (proto) { err = new proto(message); } else { err = new global.DOMException(message, 'SyntaxError'); } throw err; } if (Config.LOGERRORS && console && console.log) { console.log(message); } }, // execute the engine initialization code initialize = function(doc) { setIdentifierSyntax(); lastContext = switchContext(doc, true); }, // build validation regexps used by the engine setIdentifierSyntax = function() { // NOTE: SPECIAL CASES IN CSS SYNTAX PARSING RULES // // The <EOF-token> https://drafts.csswg.org/css-syntax/#typedef-eof-token // allow mangled|unclosed selector syntax at the end of selectors strings // // These are the non capturing representations of each character: ' " ] ) // // (?:\\"|$) - missing close double quotes // (?:\\'|$) - missing close single quote // (?:\\]|$) - missing close square bracket // (?:\\)|$) - missing close round parens // // use the above four patterns to find instances throughout the code var identifier = // doesn't start with a digit '(?=[^0-9])' + // can start with double dash '(?:-{2}' + // may include ascii chars '|[a-zA-Z0-9-_]' + // non-ascii chars '|[^\\x00-\\x9f]' + // escaped chars '|\\\\[^\\r\\n\\f0-9a-fA-F]' + // unicode chars '|\\\\[0-9a-fA-F]{1,6}(?:\\r\\n|\\s)?' + // any escaped chars '|\\\\.' + ')+', pseudoparms = '(?:[-+]?\\d*)(?:n[-+]?\\d*)', doublequote = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*(?:"|$)', singlequote = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*(?:'|$)", attrparser = identifier + '|' + doublequote + '|' + singlequote, attrvalues = '([\\x22\\x27]?)((?!\\3)*|(?:\\\\?.)*?)(?:\\3|$)', attributes = '\\[' + // attribute presence '(?:\\*\\|)?' + WSP + '?' + '(' + identifier + '(?::' + identifier + ')?)' + WSP + '?' + '(?:' + '(' + CFG.operators + ')' + WSP + '?' + '(?:' + attrparser + ')' + ')?' + // attribute case sensitivity WSP + '?' + '(i)?' + WSP + '?' + '(?:\\]|$)', attrmatcher = attributes.replace(attrparser, attrvalues), pseudoclass = '(?:\\(' + WSP + '*' + '(?:' + pseudoparms + '?)?|' + // universal * & // namespace *|* '(?:\\*|\\|)|' + '(?:' + '(?::' + identifier + '(?:\\(' + pseudoparms + '?(?:\\)|$))?|' + ')|' + '(?:[.#]?' + identifier + ')|' + '(?:' + attributes + ')' + ')+' + '(?:' + WSP + '*)(?:\\)|$))*', standardValidator = '(?=' + WSP + '?[^>+~(){}<>])' + '(?:' + // universal * & // namespace *|* '(?:\\*|\\|)|' + '(?:[.#]?' + identifier + ')+|' + '(?:' + attributes + ')+|' + '(?:::?' + identifier + pseudoclass + ')|' + '(?:' + WSP + '?' + CFG.combinators + WSP + '?)|' + '(?:' + WSP + '?,' + WSP + '?)' + ')+', extendedValidator = standardValidator.replace(pseudoclass, '.*'); reSimpleNot = RegExp( '^(' + // universal negation :not(*) & // namespace negation :not(*|*) '(?:\\*|\\*\\|\\*)|' + '(?!:not)' + '(?:' + WSP + '*[.:#]?' + '(?:' + identifier + WSP + '*)+|' + '(?:\\([^()]*(?:\\)|$))' + ')+|' + '(?:' + WSP + '*' + attributes + WSP + '*)+|' + ')$'); reOptimizer = RegExp('(?:([.:#*]?)(' + identifier + ')(?::[-\\w]+|\\[[^\\]]+(?:\\]|$)|\\([^\\)]+(?:\\)|$))*)$'); Patterns.id = RegExp('^#(' + identifier + ')(.*)'); Patterns.tagName = RegExp('^(' + identifier + ')(.*)'); Patterns.className = RegExp('^\\.(' + identifier + ')(.*)'); Patterns.attribute = RegExp('^(?:' + attrmatcher + ')(.*)'); reValidator = RegExp(Config.SIMPLENOT ? standardValidator : extendedValidator, 'g'); }, F_INIT = '"use strict";return function Resolver(c,f,x)', S_HEAD = 'var r=[],e,n,o,j=-1,k=-1', M_HEAD = 'var r=!1,e,n,o', S_LOOP = 'c=c(true);main:while(e=c[++k])', M_LOOP = 'e=c;', S_BODY = 'r[++j]=c[k];', M_BODY = '', S_TAIL = 'continue main;', M_TAIL = 'r=true;', S_TEST = 'if(f(c[k])){break main;}', M_TEST = 'f(c);', S_VARS = [ ], M_VARS = [ ], // compile groups or single selector strings into // executable functions for matching or selecting compile = function(groups, mode, callback) { var i, l, key, factory, selector, token, vars, res = '', head = '', loop = '', macro = '', source = '', seen = { }; // 'groups' may be a string, convert it to array if (typeof groups == 'string') groups = [groups]; selector = groups.join(', '); key = selector + '_' + (mode ? '1' : '0') + (callback ? '1' : '0'); // ensure 'mode' type is boolean // true = select / false = match switch (!!mode) { case true: if (selectLambdas[key]) { return selectLambdas[key]; } macro = S_BODY + (callback ? S_TEST : '') + S_TAIL; head = S_HEAD; loop = S_LOOP; break; case false: if (matchLambdas[key]) { return matchLambdas[key]; } macro = M_BODY + (callback ? M_TEST : '') + M_TAIL; head = M_HEAD; loop = M_LOOP; break; default: break; } if (groups.length > 1) { for (i = 0, l = groups.length; l > i; ++i) { token = groups[i]; if (!seen[token] && (seen[token] = true)) { source += compileSelector(token, macro, mode, callback, false); } } } else { source += compileSelector(groups[0], macro, mode, callback, false); } vars = S_VARS.join(',') || M_VARS.join(','); loop += mode ? '{' + source + '}' : source; if (mode && selector.indexOf(':nth') > -1) { loop += reNthElem.test(selector) ? 's.nthElement(null, 2);' : ''; loop += reNthType.test(selector) ? 's.nthOfType(null, 2);' : ''; } if (vars.length > 0) { S_VARS.length = 0; M_VARS.length = 0; vars = ',' + vars; } vars += ';'; factory = Function('s', F_INIT + '{' + head + vars + loop + 'return r;}')(Snapshot); return mode ? (selectLambdas[key] = factory) : (matchLambdas[key] = factory); }, // build code to check components of selector strings compileSelector = function(expression, source, mode, callback, not) { // N is the negation pseudo-class flag // D is the default inverted negation flag var a, b, n, f, name, NS, N = not ? '!' : '', D = not ? '' : '!', compat, expr, match, result, status, symbol, test, type, selector = expression, selector_string, vars; // the original 'select' or 'match' selector string // before normalization and optimization processing selector_string = mode ? lastSelected : lastMatched; // isolate selector combinators/components selector = selector.replace(/\s?([>+~])\s?/g, '$1'); while (selector) { // get namespace prefix if present or get first char of selector symbol = /^(?:\w+|\*)\|/.test(selector) ? '|' : selector[0]; switch (symbol) { // universal resolver case '*': match = selector.match(Patterns.universal); source = 'if(' + N + 'true' + '){' + source + '}'; break; // id resolver case '#': match = selector.match(Patterns.id); source = 'if(' + N + '(e.getAttribute("id")=="' + convertEscapes(match[1]) + '"' + ')){' + source + '}'; break; // class name resolver case '.': match = selector.match(Patterns.className); compat = (QUIRKS_MODE ? 'i' : '') + '.test(e.getAttribute("class"))'; source = 'if(' + N + '(/(^|\\s)' + match[1] + '(\\s|$)/' + compat + ')){' + source + '}'; break; // tag name resolver case (symbol.match(/[a-zA-Z]/) ? symbol : undefined): match = selector.match(Patterns.tagName); source = 'if(' + N + '(e.nodeName.toLowerCase()=="' + match[1].toLowerCase() + '"' + ')){' + source + '}'; break; // namespace resolver case '|': match = selector.match(Patterns.namespace); match.pop(); if (match[1] == '*') { source = 'if(' + N + 'true){' + source + '}'; } else if (!match[1]) { source = 'if(' + N + '(!e.namespaceURI)){' + source + '}'; } else if (typeof match[1] == 'string' && root.prefix == match[1]) { source = 'if(' + N + '(e.namespaceURI=="' + NAMESPACE + '")){' + source + '}'; } else { emit('\'' + selector_string + '\'' + qsInvalid); } break; // attributes resolver case '[': match = selector.match(Patterns.attribute); NS = match[0].match(/(\*|\w+)\|[-\w]+/); name = match[1]; expr = name.split(':'); expr = expr.length == 2 ? expr[1] : expr[0]; if (match[2] && !(test = Operators[match[2]])) { emit('\'' + selector_string + '\'' + qsInvalid); return ''; } if (match[4] === '') { test = match[2] == '~=' ? { p1: '^\\s', p2: '+$', p3: 'true' } : match[2] in ATTR_STD_OPS && match[2] != '~=' ? { p1: '^', p2: '$', p3: 'true' } : test; } else if (match[2] == '~=' && match[4].indexOf(' ') > -1) { // whitespace separated list but value contains space source = 'if(' + N + 'false){' + source + '}'; break; } else if (match[4]) { match[4] = convertEscapes(match[4]).replace(REX.RegExpChar, '\\$&'); } type = HTML_DOCUMENT && HTML_TABLE[expr.toLowerCase()] ? 'i' : ''; source = 'if(' + N + '(' + (!match[2] ? 'e.hasAttribute("' + name + '")' : !match[4] && ATTR_STD_OPS[match[2]] && match[2] != '~=' ? 'e.getAttribute("' + name + '")==""' : '(/' + test.p1 + match[4] + test.p2 + '/' + type + ').test(e.getAttribute("' + name + '"))==' + test.p3) + ')){' + source + '}'; break; // *** General sibling combinator // E ~ F (F relative sibling of E) case '~': match = selector.match(Patterns.relative); source = 'n=e;while((e=e.previousElementSibling)){' + source + '}e=n;'; break; // *** Adjacent sibling combinator // E + F (F adiacent sibling of E) case '+': match = selector.match(Patterns.adjacent); source = 'n=e;if((e=e.previousElementSibling)){' + source + '}e=n;'; break; // *** Descendant combinator // E F (E ancestor of F) case '\x09': case '\x20': match = selector.match(Patterns.ancestor); source = 'n=e;while((e=e.parentElement)){' + source + '}e=n;'; break; // *** Child combinator // E > F (F children of E) case '>': match = selector.match(Patterns.children); source = 'n=e;if((e=e.parentElement)){' + source + '}e=n;'; break; case (symbol in Combinators ? symbol : undefined): // for other registered combinators extensions match[match.length - 1] = '*'; source = Combinators[symbol](match) + source; break; // *** tree-structural pseudo-classes // :root, :empty // :first-child, :last-child, :only-child, // :first-of-type, :last-of-type, :only-of-type, case ':': if ((match = selector.match(Patterns.struct_n))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'root': // there can only be one :root element, so exit the loop once found source = 'if(' + N + '(e===s.root)){' + source + (mode ? 'break main;' : '') + '}'; break; case 'empty': // matches elements that don't contain elements or text nodes source = 'n=e.firstChild;while(n&&!(/1|3/).test(n.nodeType)){n=n.nextSibling}if(' + D + 'n){' + source + '}'; break; // *** child-indexed pseudo-classes // :first-child, :last-child, :only-child case 'only-child': source = 'if(' + N + '(!e.nextElementSibling&&!e.previousElementSibling)){' + source + '}'; break; case 'last-child': source = 'if(' + N + '(!e.nextElementSibling)){' + source + '}'; break; case 'first-child': source = 'if(' + N + '(!e.previousElementSibling)){' + source + '}'; break; // *** typed child-indexed pseudo-classes // :only-of-type, :last-of-type, :first-of-type case 'only-of-type': source = 'o=e.nodeName;' + 'n=e;while((n=n.nextElementSibling)&&n.nodeName!=o);if(!n){' + 'n=e;while((n=n.previousElementSibling)&&n.nodeName!=o);}if(' + D + 'n){' + source + '}'; break; case 'last-of-type': source = 'n=e;o=e.nodeName;while((n=n.nextElementSibling)&&n.nodeName!=o);if(' + D + 'n){' + source + '}'; break; case 'first-of-type': source = 'n=e;o=e.nodeName;while((n=n.previousElementSibling)&&n.nodeName!=o);if(' + D + 'n){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** child-indexed & typed child-indexed pseudo-classes // :nth-child, :nth-of-type, :nth-last-child, :nth-last-of-type // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) else if ((match = selector.match(Patterns.struct_p))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'nth-child': case 'nth-of-type': case 'nth-last-child': case 'nth-last-of-type': expr = /-of-type/i.test(match[1]); if (match[1] && match[2]) { type = /last/i.test(match[1]); if (match[2] == 'n') { source = 'if(' + N + 'true){' + source + '}'; break; } else if (match[2] == '1') { test = type ? 'next' : 'previous'; source = expr ? 'n=e;o=e.nodeName;' + 'while((n=n.' + test + 'ElementSibling)&&n.nodeName!=o);if(' + D + 'n){' + source + '}' : 'if(' + N + '!e.' + test + 'ElementSibling){' + source + '}'; break; } else if (match[2] == 'even' || match[2] == '2n0' || match[2] == '2n+0' || match[2] == '2n') { test = 'n%2==0'; } else if (match[2] == 'odd' || match[2] == '2n1' || match[2] == '2n+1') { test = 'n%2==1'; } else { f = /n/i.test(match[2]); n = match[2].split('n'); a = parseInt(n[0], 10) || 0; b = parseInt(n[1], 10) || 0; if (n[0] == '-') { a = -1; } if (n[0] == '+') { a = +1; } test = (b ? '(n' + (b > 0 ? '-' : '+') + Math.abs(b) + ')' : 'n') + '%' + a + '==0' ; test = a >= +1 ? (f ? 'n>' + (b - 1) + (Math.abs(a) != 1 ? '&&' + test : '') : 'n==' + a) : a <= -1 ? (f ? 'n<' + (b + 1) + (Math.abs(a) != 1 ? '&&' + test : '') : 'n==' + a) : a === 0 ? (n[0] ? 'n==' + b : 'n>' + (b - 1)) : 'false'; } expr = expr ? 'OfType' : 'Element'; type = type ? 'true' : 'false'; source = 'n=s.nth' + expr + '(e,' + type + ');if(' + N + '(' + test + ')){' + source + '}'; } else { emit('\'' + selector_string + '\'' + qsInvalid); } break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** logical combination pseudo-classes // :matches( s1, [ s2, ... ]), :not( s1, [ s2, ... ]) else if ((match = selector.match(Patterns.lpseudos))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'matches': expr = match[2].replace(REX.TrimSpaces, ''); source = 'if(s.match("' + expr.replace(/\x22/g, '\\"') + '",e,f)){' + source + '}'; break; case 'not': if (Config.SIMPLENOT && !reSimpleNot.test(match[2])) { emit('\'' + selector_string + '\'' + qsInvalid); return ''; } expr = match[2].replace(REX.TrimSpaces, ''); source = compileSelector(expr, source, false, callback, true); break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** linguistic pseudo-classes // :dir( ltr / rtl ), :lang( en ) else if ((match = selector.match(Patterns.hpseudos))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'dir': source = 'var p;if(' + N + '(' + '(/' + match[2] + '/i.test(e.dir))||(p=s.ancestor("[dir]", e))&&' + '(/' + match[2] + '/i.test(p.dir))||(e.dir==""||e.dir=="auto")&&' + '(' + (match[2] == 'ltr' ? '!':'')+ RTL +'.test(e.textContent)))' + '){' + source + '};'; break; case 'lang': expr = '(?:^|-)' + match[2] + '(?:-|$)'; source = 'var p;if(' + N + '(' + '(e.isConnected&&(e.lang==""&&(p=s.ancestor("[lang]",e)))&&' + '(p.lang=="' + match[2] + '")||/'+ expr +'/i.test(e.lang)))' + '){' + source + '};'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** location, user actiond and input pseudo-classes else if ((match = selector.match(Patterns.fpseudos))) { match[1] = match[1].toLowerCase(); switch (match[1]) { // *** location pseudo-classes // :link, :visited, :target, :scope case 'link': source = 'if(' + N + '(/^a|area|link$/i.test(e.nodeName)&&e.hasAttribute("href"))){' + source + '}'; break; case 'visited': source = 'if(' + N + '(/^a|area|link$/i.test(e.nodeName)&&e.hasAttribute("href")&&e.visited)){' + source + '}'; break; case 'target': source = 'if(' + N + '((s.doc.compareDocumentPosition(e)&16)&&s.doc.location.hash&&e.id==s.doc.location.hash.slice(1))){' + source + '}'; break; case 'scope': source = 'if((s.from.compareDocumentPosition(e)&20)==20){' + source + '}'; break; // *** user actions pseudo-classes // :hover, :active, :focus case 'hover': source = 'hasFocus' in doc && doc.hasFocus() ? 'if(' + N + '(e===s.doc.hoverElement)){' + source + '}' : 'if(' + D + 'true){' + source + '}'; break; case 'active': source = 'hasFocus' in doc && doc.hasFocus() ? 'if(' + N + '(e===s.doc.activeElement)){' + source + '}' : 'if(' + D + 'true){' + source + '}'; break; case 'focus': source = 'hasFocus' in doc ? 'if(' + N + '(e===s.doc.activeElement&&s.doc.hasFocus()&&(e.type||e.href||typeof e.tabIndex=="number"))){' + source + '}' : 'if(' + N + '(e===s.doc.activeElement&&(e.type||e.href))){' + source + '}'; break; // *** user interface and form pseudo-classes // :enabled, :disabled, :read-only, :read-write, :placeholder-shown case 'enabled': source = 'if(' + N + '(("form" in e||/^optgroup$/i.test(e.nodeName))&&"disabled" in e &&e.disabled===false' + ')){' + source + '}'; break; case 'disabled': // https://www.w3.org/TR/html5/forms.html#enabling-and-disabling-form-controls:-the-disabled-attribute source = 'if(' + N + '(("form" in e||/^optgroup$/i.test(e.nodeName))&&"disabled" in e&&' + '(e.disabled===true||(n=s.ancestor("fieldset",e))&&(n=s.first("legend",n))&&!n.contains(e))' + ')){' + source + '}'; break; case 'read-only': source = 'if(' + N + '(' + '(/^textarea$/i.test(e.nodeName)&&(e.readOnly||e.disabled))||' + '("|password|text|".includes("|"+e.type+"|")&&e.readOnly)' + ')){' + source + '}'; break; case 'read-write': source = 'if(' + N + '(' + '((/^textarea$/i.test(e.nodeName)&&!e.readOnly&&!e.disabled)||' + '("|password|text|".includes("|"+e.type+"|")&&!e.readOnly&&!e.disabled))||' + '(e.hasAttribute("contenteditable")||(s.doc.designMode=="on"))' + ')){' + source + '}'; break; case 'placeholder-shown': source = 'if(' + N + '(' + '(/^input|textarea$/i.test(e.nodeName))&&e.hasAttribute("placeholder")&&' + '("|textarea|password|number|search|email|text|tel|url|".includes("|"+e.type+"|"))&&' + '(!s.match(":focus",e))' + ')){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // *** input pseudo-classes for form validation (was web-forms) // :default, :checked, :indeterminate, :valid, :invalid // :in-range, :out-of-range, :required, :optional else if ((match = selector.match(Patterns.ipseudos))) { match[1] = match[1].toLowerCase(); switch (match[1]) { case 'default': source = 'if(' + N + '("form" in e && e.form)){' + 'var x=0;n=[];' + 'if(e.type=="image")n=e.form.getElementsByTagName("input");' + 'if(e.type=="submit")n=e.form.elements;' + 'while(n[x]&&e!==n[x]){' + 'if(n[x].type=="image")break;' + 'if(n[x].type=="submit")break;' + 'x++;' + '}' + '}' + 'if(' + N + '(e.form&&(e===n[x]&&"|image|submit|".includes("|"+e.type+"|"))||' + '((/^option$/i.test(e.nodeName))&&e.defaultSelected)||' + '(("|radio|checkbox|".includes("|"+e.type+"|"))&&e.defaultChecked)' + ')){' + source + '}'; break; case 'checked': source = 'if(' + N + '(/^input$/i.test(e.nodeName)&&' + '("|radio|checkbox|".includes("|"+e.type+"|")&&e.checked)||' + '(/^option$/i.test(e.nodeName)&&(e.selected||e.checked))' + ')){' + source + '}'; break; case 'indeterminate': source = 'if(' + N + '(/^progress$/i.test(e.nodeName)&&!e.hasAttribute("value"))||' + '(/^input$/i.test(e.nodeName)&&("checkbox"==e.type&&e.indeterminate)||' + '("radio"==e.type&&e.name&&!s.first("input[name="+e.name+"]:checked",e.form))' + ')){' + source + '}'; break; case 'required': source = 'if(' + N + '(/^input|select|textarea$/i.test(e.nodeName)&&e.required)' + '){' + source + '}'; break; case 'optional': source = 'if(' + N + '(/^input|select|textarea$/i.test(e.nodeName)&&!e.required)' + '){' + source + '}'; break; case 'invalid': source = 'if(' + N + '((' + '(/^form$/i.test(e.nodeName)&&!e.noValidate)||' + '(e.willValidate&&!e.formNoValidate))&&!e.checkValidity())||' + '(/^fieldset$/i.test(e.nodeName)&&s.first(":invalid",e))' + '){' + source + '}'; break; case 'valid': source = 'if(' + N + '((' + '(/^form$/i.test(e.nodeName)&&!e.noValidate)||' + '(e.willValidate&&!e.formNoValidate))&&e.checkValidity())||' + '(/^fieldset$/i.test(e.nodeName)&&s.first(":valid",e))' + '){' + source + '}'; break; case 'in-range': source = 'if(' + N + '(/^input$/i.test(e.nodeName))&&' + '(e.willValidate&&!e.formNoValidate)&&' + '(!e.validity.rangeUnderflow&&!e.validity.rangeOverflow)&&' + '("|date|datetime-local|month|number|range|time|week|".includes("|"+e.type+"|"))&&' + '("range"==e.type||e.getAttribute("min")||e.getAttribute("max"))' + '){' + source + '}'; break; case 'out-of-range': source = 'if(' + N + '(/^input$/i.test(e.nodeName))&&' + '(e.willValidate&&!e.formNoValidate)&&' + '(e.validity.rangeUnderflow||e.validity.rangeOverflow)&&' + '("|date|datetime-local|month|number|range|time|week|".includes("|"+e.type+"|"))&&' + '("range"==e.type||e.getAttribute("min")||e.getAttribute("max"))' + '){' + source + '}'; break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } } // allow pseudo-elements as :after/:before (single or double colon) else if ((match = selector.match(Patterns.epseudos))) { source = 'if(' + D + '(/1|11/).test(e.nodeType)){' + source + '}'; } else { // reset expr = false; status = false; // process registered selector extensions for (expr in Selectors) { if ((match = selector.match(Selectors[expr].Expression))) { result = Selectors[expr].Callback(match, source, mode, callback); if ('match' in result) { match = result.match; } vars = result.modvar; if (mode) { // add extra needed variables to the selector resolver vars && S_VARS.indexOf(vars) < 0 && (S_VARS[S_VARS.length] = vars); } else { // add extra needed variables to the matcher resolver vars && M_VARS.indexOf(vars) < 0 && (M_VARS[M_VARS.length] = vars); } // extension source code source = result.source; // extension status code status = result.status; // break on status error if (status) { break; } } } if (!status) { emit('unknown pseudo-class selector \'' + selector + '\''); return ''; } if (!expr) { emit('unknown token in selector \'' + selector + '\''); return ''; } } break; default: emit('\'' + selector_string + '\'' + qsInvalid); break; } // end of switch symbol if (!match) { emit('\'' + selector_string + '\'' + qsInvalid); return ''; } // pop last component selector = match.pop(); } // end of while selector return source; }, // parse selector groups in an array parseGroup = function(selector) { var i, l, groups = selector. replace(/,\s?,/g, ','). replace(/\\,/g, '\ufffd'). split(REX.SplitGroup); for (i = 0, l = groups.length; l > i; ++i) { groups[i] = groups[i].replace(/\ufffd/g, '\\,'); } return groups; }, // equivalent of w3c 'closest' method ancestor = function _closest(selector, element, callback) { while (element) { if (match(selector, element)) break; element = element.parentElement; } return element; }, // equivalent of w3c 'matches' method match = function _matches(selector, element, callback) { var groups; if (element && matchResolvers[selector]) { return !!matchResolvers[selector](element, callback); } lastMatched = selector; // arguments validation if (arguments.length === 0) { emit(qsNotArgs, TypeError); return Config.VERBOSITY ? undefined : false; } else if (arguments[1] === '') { emit('\'\'' + qsInvalid); return Config.VERBOSITY ? undefined : false; } // selector NULL or UNDEFINED if (typeof selector != 'string') { selector = '' + selector; } // normalize selector selector = selector. replace(/\x00|\\$/g, '\ufffd'). replace(REX.TrimSpaces, ''); // parse, validate and split possible selector groups if ((groups = selector.match(reValidator)) && groups.join('') == selector) { groups = /\,/.test(selector) ? parseGroup(selector) : [selector];