From df594aa2e5287447153905560e9997e67f55bb1e Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 19 May 2022 09:46:30 -0400 Subject: [PATCH 01/17] feat: add npm query cmd --- lib/commands/query.js | 31 + lib/utils/cmd-list.js | 1 + node_modules/cssesc/LICENSE-MIT.txt | 20 + node_modules/cssesc/bin/cssesc | 116 ++ node_modules/cssesc/cssesc.js | 110 ++ node_modules/cssesc/man/cssesc.1 | 70 + node_modules/cssesc/package.json | 51 + node_modules/postcss-selector-parser/API.md | 873 ++++++++++++ .../postcss-selector-parser/LICENSE-MIT | 22 + .../postcss-selector-parser/dist/index.js | 24 + .../postcss-selector-parser/dist/parser.js | 1243 +++++++++++++++++ .../postcss-selector-parser/dist/processor.js | 206 +++ .../dist/selectors/attribute.js | 515 +++++++ .../dist/selectors/className.js | 69 + .../dist/selectors/combinator.js | 31 + .../dist/selectors/comment.js | 31 + .../dist/selectors/constructors.js | 102 ++ .../dist/selectors/container.js | 395 ++++++ .../dist/selectors/guards.js | 64 + .../dist/selectors/id.js | 37 + .../dist/selectors/index.js | 27 + .../dist/selectors/namespace.js | 101 ++ .../dist/selectors/nesting.js | 32 + .../dist/selectors/node.js | 239 ++++ .../dist/selectors/pseudo.js | 38 + .../dist/selectors/root.js | 60 + .../dist/selectors/selector.js | 31 + .../dist/selectors/string.js | 31 + .../dist/selectors/tag.js | 31 + .../dist/selectors/types.js | 28 + .../dist/selectors/universal.js | 32 + .../dist/sortAscending.js | 13 + .../dist/tokenTypes.js | 95 ++ .../postcss-selector-parser/dist/tokenize.js | 271 ++++ .../dist/util/ensureObject.js | 22 + .../dist/util/getProp.js | 24 + .../dist/util/index.js | 22 + .../dist/util/stripComments.js | 27 + .../dist/util/unesc.js | 93 ++ .../postcss-selector-parser/package.json | 78 ++ .../postcss-selector-parser.d.ts | 555 ++++++++ package-lock.json | 39 + workspaces/arborist/lib/node.js | 8 + .../lib/query-selector-all-response.js | 29 + workspaces/arborist/lib/query-selector-all.js | 334 +++++ workspaces/arborist/package.json | 1 + .../arborist/test/query-selector-all.js | 352 +++++ 47 files changed, 6624 insertions(+) create mode 100644 lib/commands/query.js create mode 100644 node_modules/cssesc/LICENSE-MIT.txt create mode 100755 node_modules/cssesc/bin/cssesc create mode 100644 node_modules/cssesc/cssesc.js create mode 100644 node_modules/cssesc/man/cssesc.1 create mode 100644 node_modules/cssesc/package.json create mode 100644 node_modules/postcss-selector-parser/API.md create mode 100644 node_modules/postcss-selector-parser/LICENSE-MIT create mode 100644 node_modules/postcss-selector-parser/dist/index.js create mode 100644 node_modules/postcss-selector-parser/dist/parser.js create mode 100644 node_modules/postcss-selector-parser/dist/processor.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/attribute.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/className.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/combinator.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/comment.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/constructors.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/container.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/guards.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/id.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/index.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/namespace.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/nesting.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/node.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/pseudo.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/root.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/selector.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/string.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/tag.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/types.js create mode 100644 node_modules/postcss-selector-parser/dist/selectors/universal.js create mode 100644 node_modules/postcss-selector-parser/dist/sortAscending.js create mode 100644 node_modules/postcss-selector-parser/dist/tokenTypes.js create mode 100644 node_modules/postcss-selector-parser/dist/tokenize.js create mode 100644 node_modules/postcss-selector-parser/dist/util/ensureObject.js create mode 100644 node_modules/postcss-selector-parser/dist/util/getProp.js create mode 100644 node_modules/postcss-selector-parser/dist/util/index.js create mode 100644 node_modules/postcss-selector-parser/dist/util/stripComments.js create mode 100644 node_modules/postcss-selector-parser/dist/util/unesc.js create mode 100644 node_modules/postcss-selector-parser/package.json create mode 100644 node_modules/postcss-selector-parser/postcss-selector-parser.d.ts create mode 100644 workspaces/arborist/lib/query-selector-all-response.js create mode 100644 workspaces/arborist/lib/query-selector-all.js create mode 100644 workspaces/arborist/test/query-selector-all.js diff --git a/lib/commands/query.js b/lib/commands/query.js new file mode 100644 index 0000000000000..42cd26cc7afaa --- /dev/null +++ b/lib/commands/query.js @@ -0,0 +1,31 @@ +'use strict' + +const { resolve } = require('path') +const Arborist = require('@npmcli/arborist') +const BaseCommand = require('../base-command.js') + +class Query extends BaseCommand { + static description = 'Retrieve a filtered list of packages' + static name = 'query' + static usage = [ + 'query ', + ] + + static params = [] + + async exec (args) { + const globalTop = resolve(this.npm.globalDir, '..') + const where = this.npm.config.get('global') ? globalTop : this.npm.prefix + const opts = { + ...this.npm.flatOptions, + path: where, + } + const arb = new Arborist(opts) + const tree = await arb.loadActual(opts) + const res = JSON.stringify(tree.querySelectorAll(args[0]), null, 2) + + return this.npm.output(res) + } +} + +module.exports = Query diff --git a/lib/utils/cmd-list.js b/lib/utils/cmd-list.js index c1d20186a82a6..38439542a21f9 100644 --- a/lib/utils/cmd-list.js +++ b/lib/utils/cmd-list.js @@ -114,6 +114,7 @@ const cmdList = [ 'profile', 'prune', 'publish', + 'query', 'rebuild', 'repo', 'restart', diff --git a/node_modules/cssesc/LICENSE-MIT.txt b/node_modules/cssesc/LICENSE-MIT.txt new file mode 100644 index 0000000000000..a41e0a7ef970e --- /dev/null +++ b/node_modules/cssesc/LICENSE-MIT.txt @@ -0,0 +1,20 @@ +Copyright Mathias Bynens + +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. diff --git a/node_modules/cssesc/bin/cssesc b/node_modules/cssesc/bin/cssesc new file mode 100755 index 0000000000000..188c034ffe948 --- /dev/null +++ b/node_modules/cssesc/bin/cssesc @@ -0,0 +1,116 @@ +#!/usr/bin/env node +const fs = require('fs'); +const cssesc = require('../cssesc.js'); +const strings = process.argv.splice(2); +const stdin = process.stdin; +const options = {}; +const log = console.log; + +const main = function() { + const option = strings[0]; + + if (/^(?:-h|--help|undefined)$/.test(option)) { + log( + 'cssesc v%s - https://mths.be/cssesc', + cssesc.version + ); + log([ + '\nUsage:\n', + '\tcssesc [string]', + '\tcssesc [-i | --identifier] [string]', + '\tcssesc [-s | --single-quotes] [string]', + '\tcssesc [-d | --double-quotes] [string]', + '\tcssesc [-w | --wrap] [string]', + '\tcssesc [-e | --escape-everything] [string]', + '\tcssesc [-v | --version]', + '\tcssesc [-h | --help]', + '\nExamples:\n', + '\tcssesc \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'', + '\tcssesc --identifier \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'', + '\tcssesc --escape-everything \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'', + '\tcssesc --double-quotes --wrap \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'', + '\techo \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\' | cssesc' + ].join('\n')); + return process.exit(1); + } + + if (/^(?:-v|--version)$/.test(option)) { + log('v%s', cssesc.version); + return process.exit(1); + } + + strings.forEach(function(string) { + // Process options + if (/^(?:-i|--identifier)$/.test(string)) { + options.isIdentifier = true; + return; + } + if (/^(?:-s|--single-quotes)$/.test(string)) { + options.quotes = 'single'; + return; + } + if (/^(?:-d|--double-quotes)$/.test(string)) { + options.quotes = 'double'; + return; + } + if (/^(?:-w|--wrap)$/.test(string)) { + options.wrap = true; + return; + } + if (/^(?:-e|--escape-everything)$/.test(string)) { + options.escapeEverything = true; + return; + } + + // Process string(s) + let result; + try { + result = cssesc(string, options); + log(result); + } catch (exception) { + log(exception.message + '\n'); + log('Error: failed to escape.'); + log('If you think this is a bug in cssesc, please report it:'); + log('https://github.com/mathiasbynens/cssesc/issues/new'); + log( + '\nStack trace using cssesc@%s:\n', + cssesc.version + ); + log(exception.stack); + return process.exit(1); + } + }); + // Return with exit status 0 outside of the `forEach` loop, in case + // multiple strings were passed in. + return process.exit(0); + +}; + +if (stdin.isTTY) { + // handle shell arguments + main(); +} else { + let timeout; + // Either the script is called from within a non-TTY context, or `stdin` + // content is being piped in. + if (!process.stdout.isTTY) { + // The script was called from a non-TTY context. This is a rather uncommon + // use case we don’t actively support. However, we don’t want the script + // to wait forever in such cases, so… + timeout = setTimeout(function() { + // …if no piped data arrived after a whole minute, handle shell + // arguments instead. + main(); + }, 60000); + } + let data = ''; + stdin.on('data', function(chunk) { + clearTimeout(timeout); + data += chunk; + }); + stdin.on('end', function() { + strings.push(data.trim()); + main(); + }); + stdin.resume(); +} diff --git a/node_modules/cssesc/cssesc.js b/node_modules/cssesc/cssesc.js new file mode 100644 index 0000000000000..1c0928e45667c --- /dev/null +++ b/node_modules/cssesc/cssesc.js @@ -0,0 +1,110 @@ +/*! https://mths.be/cssesc v3.0.0 by @mathias */ +'use strict'; + +var object = {}; +var hasOwnProperty = object.hasOwnProperty; +var merge = function merge(options, defaults) { + if (!options) { + return defaults; + } + var result = {}; + for (var key in defaults) { + // `if (defaults.hasOwnProperty(key) { … }` is not needed here, since + // only recognized option names are used. + result[key] = hasOwnProperty.call(options, key) ? options[key] : defaults[key]; + } + return result; +}; + +var regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/; +var regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/; +var regexAlwaysEscape = /['"\\]/; +var regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g; + +// https://mathiasbynens.be/notes/css-escapes#css +var cssesc = function cssesc(string, options) { + options = merge(options, cssesc.options); + if (options.quotes != 'single' && options.quotes != 'double') { + options.quotes = 'single'; + } + var quote = options.quotes == 'double' ? '"' : '\''; + var isIdentifier = options.isIdentifier; + + var firstChar = string.charAt(0); + var output = ''; + var counter = 0; + var length = string.length; + while (counter < length) { + var character = string.charAt(counter++); + var codePoint = character.charCodeAt(); + var value = void 0; + // If it’s not a printable ASCII character… + if (codePoint < 0x20 || codePoint > 0x7E) { + if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) { + // It’s a high surrogate, and there is a next character. + var extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { + // next character is low surrogate + codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000; + } else { + // It’s an unmatched surrogate; only append this code unit, in case + // the next code unit is the high surrogate of a surrogate pair. + counter--; + } + } + value = '\\' + codePoint.toString(16).toUpperCase() + ' '; + } else { + if (options.escapeEverything) { + if (regexAnySingleEscape.test(character)) { + value = '\\' + character; + } else { + value = '\\' + codePoint.toString(16).toUpperCase() + ' '; + } + } else if (/[\t\n\f\r\x0B]/.test(character)) { + value = '\\' + codePoint.toString(16).toUpperCase() + ' '; + } else if (character == '\\' || !isIdentifier && (character == '"' && quote == character || character == '\'' && quote == character) || isIdentifier && regexSingleEscape.test(character)) { + value = '\\' + character; + } else { + value = character; + } + } + output += value; + } + + if (isIdentifier) { + if (/^-[-\d]/.test(output)) { + output = '\\-' + output.slice(1); + } else if (/\d/.test(firstChar)) { + output = '\\3' + firstChar + ' ' + output.slice(1); + } + } + + // Remove spaces after `\HEX` escapes that are not followed by a hex digit, + // since they’re redundant. Note that this is only possible if the escape + // sequence isn’t preceded by an odd number of backslashes. + output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) { + if ($1 && $1.length % 2) { + // It’s not safe to remove the space, so don’t. + return $0; + } + // Strip the space. + return ($1 || '') + $2; + }); + + if (!isIdentifier && options.wrap) { + return quote + output + quote; + } + return output; +}; + +// Expose default options (so they can be overridden globally). +cssesc.options = { + 'escapeEverything': false, + 'isIdentifier': false, + 'quotes': 'single', + 'wrap': false +}; + +cssesc.version = '3.0.0'; + +module.exports = cssesc; diff --git a/node_modules/cssesc/man/cssesc.1 b/node_modules/cssesc/man/cssesc.1 new file mode 100644 index 0000000000000..eee4996daf543 --- /dev/null +++ b/node_modules/cssesc/man/cssesc.1 @@ -0,0 +1,70 @@ +.Dd August 9, 2013 +.Dt cssesc 1 +.Sh NAME +.Nm cssesc +.Nd escape text for use in CSS string literals or identifiers +.Sh SYNOPSIS +.Nm +.Op Fl i | -identifier Ar string +.br +.Op Fl s | -single-quotes Ar string +.br +.Op Fl d | -double-quotes Ar string +.br +.Op Fl w | -wrap Ar string +.br +.Op Fl e | -escape-everything Ar string +.br +.Op Fl v | -version +.br +.Op Fl h | -help +.Sh DESCRIPTION +.Nm +escapes strings for use in CSS string literals or identifiers while generating the shortest possible valid ASCII-only output. +.Sh OPTIONS +.Bl -ohang -offset +.It Sy "-s, --single-quotes" +Escape any occurences of ' in the input string as \\', so that the output can be used in a CSS string literal wrapped in single quotes. +.It Sy "-d, --double-quotes" +Escape any occurences of " in the input string as \\", so that the output can be used in a CSS string literal wrapped in double quotes. +.It Sy "-w, --wrap" +Make sure the output is a valid CSS string literal wrapped in quotes. The type of quotes can be specified using the +.Ar -s | --single-quotes +or +.Ar -d | --double-quotes +settings. +.It Sy "-e, --escape-everything" +Escape all the symbols in the output, even printable ASCII symbols. +.It Sy "-v, --version" +Print cssesc's version. +.It Sy "-h, --help" +Show the help screen. +.El +.Sh EXIT STATUS +The +.Nm cssesc +utility exits with one of the following values: +.Pp +.Bl -tag -width flag -compact +.It Li 0 +.Nm +successfully escaped the given text and printed the result. +.It Li 1 +.Nm +wasn't instructed to escape anything (for example, the +.Ar --help +flag was set); or, an error occurred. +.El +.Sh EXAMPLES +.Bl -ohang -offset +.It Sy "cssesc 'foo bar baz'" +Print an escaped version of the given text. +.It Sy echo\ 'foo bar baz'\ |\ cssesc +Print an escaped version of the text that gets piped in. +.El +.Sh BUGS +cssesc's bug tracker is located at . +.Sh AUTHOR +Mathias Bynens +.Sh WWW + diff --git a/node_modules/cssesc/package.json b/node_modules/cssesc/package.json new file mode 100644 index 0000000000000..076c84dc83018 --- /dev/null +++ b/node_modules/cssesc/package.json @@ -0,0 +1,51 @@ +{ + "name": "cssesc", + "version": "3.0.0", + "description": "A JavaScript library for escaping CSS strings and identifiers while generating the shortest possible ASCII-only output.", + "homepage": "https://mths.be/cssesc", + "engines": { + "node": ">=4" + }, + "main": "cssesc.js", + "bin": "bin/cssesc", + "man": "man/cssesc.1", + "keywords": [ + "css", + "escape", + "identifier", + "string", + "tool" + ], + "license": "MIT", + "author": { + "name": "Mathias Bynens", + "url": "https://mathiasbynens.be/" + }, + "repository": { + "type": "git", + "url": "https://github.com/mathiasbynens/cssesc.git" + }, + "bugs": "https://github.com/mathiasbynens/cssesc/issues", + "files": [ + "LICENSE-MIT.txt", + "cssesc.js", + "bin/", + "man/" + ], + "scripts": { + "build": "grunt template && babel cssesc.js -o cssesc.js", + "test": "mocha tests", + "cover": "istanbul cover --report html node_modules/.bin/_mocha tests -- -u exports -R spec" + }, + "devDependencies": { + "babel-cli": "^6.26.0", + "babel-preset-env": "^1.6.1", + "codecov": "^1.0.1", + "grunt": "^1.0.1", + "grunt-template": "^1.0.0", + "istanbul": "^0.4.4", + "mocha": "^2.5.3", + "regenerate": "^1.2.1", + "requirejs": "^2.1.16" + } +} diff --git a/node_modules/postcss-selector-parser/API.md b/node_modules/postcss-selector-parser/API.md new file mode 100644 index 0000000000000..6aa1f1438f1be --- /dev/null +++ b/node_modules/postcss-selector-parser/API.md @@ -0,0 +1,873 @@ +# API Documentation + +*Please use only this documented API when working with the parser. Methods +not documented here are subject to change at any point.* + +## `parser` function + +This is the module's main entry point. + +```js +const parser = require('postcss-selector-parser'); +``` + +### `parser([transform], [options])` + +Creates a new `processor` instance + +```js +const processor = parser(); +``` + +Or, with optional transform function + +```js +const transform = selectors => { + selectors.walkUniversals(selector => { + selector.remove(); + }); +}; + +const processor = parser(transform) + +// Example +const result = processor.processSync('*.class'); +// => .class +``` + +[See processor documentation](#processor) + +Arguments: + +* `transform (function)`: Provide a function to work with the parsed AST. +* `options (object)`: Provide default options for all calls on the returned `Processor`. + +### `parser.attribute([props])` + +Creates a new attribute selector. + +```js +parser.attribute({attribute: 'href'}); +// => [href] +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.className([props])` + +Creates a new class selector. + +```js +parser.className({value: 'button'}); +// => .button +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.combinator([props])` + +Creates a new selector combinator. + +```js +parser.combinator({value: '+'}); +// => + +``` + +Arguments: + +* `props (object)`: The new node's properties. + +Notes: +* **Descendant Combinators** The value of descendant combinators created by the + parser always just a single space (`" "`). For descendant selectors with no + comments, additional space is now stored in `node.spaces.before`. Depending + on the location of comments, additional spaces may be stored in + `node.raws.spaces.before`, `node.raws.spaces.after`, or `node.raws.value`. +* **Named Combinators** Although, nonstandard and unlikely to ever become a standard, + named combinators like `/deep/` and `/for/` are parsed as combinators. The + `node.value` is name after being unescaped and normalized as lowercase. The + original value for the combinator name is stored in `node.raws.value`. + + +### `parser.comment([props])` + +Creates a new comment. + +```js +parser.comment({value: '/* Affirmative, Dave. I read you. */'}); +// => /* Affirmative, Dave. I read you. */ +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.id([props])` + +Creates a new id selector. + +```js +parser.id({value: 'search'}); +// => #search +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.nesting([props])` + +Creates a new nesting selector. + +```js +parser.nesting(); +// => & +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.pseudo([props])` + +Creates a new pseudo selector. + +```js +parser.pseudo({value: '::before'}); +// => ::before +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.root([props])` + +Creates a new root node. + +```js +parser.root(); +// => (empty) +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.selector([props])` + +Creates a new selector node. + +```js +parser.selector(); +// => (empty) +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.string([props])` + +Creates a new string node. + +```js +parser.string(); +// => (empty) +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.tag([props])` + +Creates a new tag selector. + +```js +parser.tag({value: 'button'}); +// => button +``` + +Arguments: + +* `props (object)`: The new node's properties. + +### `parser.universal([props])` + +Creates a new universal selector. + +```js +parser.universal(); +// => * +``` + +Arguments: + +* `props (object)`: The new node's properties. + +## Node types + +### `node.type` + +A string representation of the selector type. It can be one of the following; +`attribute`, `class`, `combinator`, `comment`, `id`, `nesting`, `pseudo`, +`root`, `selector`, `string`, `tag`, or `universal`. Note that for convenience, +these constants are exposed on the main `parser` as uppercased keys. So for +example you can get `id` by querying `parser.ID`. + +```js +parser.attribute({attribute: 'href'}).type; +// => 'attribute' +``` + +### `node.parent` + +Returns the parent node. + +```js +root.nodes[0].parent === root; +``` + +### `node.toString()`, `String(node)`, or `'' + node` + +Returns a string representation of the node. + +```js +const id = parser.id({value: 'search'}); +console.log(String(id)); +// => #search +``` + +### `node.next()` & `node.prev()` + +Returns the next/previous child of the parent node. + +```js +const next = id.next(); +if (next && next.type !== 'combinator') { + throw new Error('Qualified IDs are not allowed!'); +} +``` + +### `node.replaceWith(node)` + +Replace a node with another. + +```js +const attr = selectors.first.first; +const className = parser.className({value: 'test'}); +attr.replaceWith(className); +``` + +Arguments: + +* `node`: The node to substitute the original with. + +### `node.remove()` + +Removes the node from its parent node. + +```js +if (node.type === 'id') { + node.remove(); +} +``` + +### `node.clone()` + +Returns a copy of a node, detached from any parent containers that the +original might have had. + +```js +const cloned = parser.id({value: 'search'}); +String(cloned); + +// => #search +``` + +### `node.isAtPosition(line, column)` + +Return a `boolean` indicating whether this node includes the character at the +position of the given line and column. Returns `undefined` if the nodes lack +sufficient source metadata to determine the position. + +Arguments: + +* `line`: 1-index based line number relative to the start of the selector. +* `column`: 1-index based column number relative to the start of the selector. + +### `node.spaces` + +Extra whitespaces around the node will be moved into `node.spaces.before` and +`node.spaces.after`. So for example, these spaces will be moved as they have +no semantic meaning: + +```css + h1 , h2 {} +``` + +For descendent selectors, the value is always a single space. + +```css +h1 h2 {} +``` + +Additional whitespace is found in either the `node.spaces.before` and `node.spaces.after` depending on the presence of comments or other whitespace characters. If the actual whitespace does not start or end with a single space, the node's raw value is set to the actual space(s) found in the source. + +### `node.source` + +An object describing the node's start/end, line/column source position. + +Within the following CSS, the `.bar` class node ... + +```css +.foo, + .bar {} +``` + +... will contain the following `source` object. + +```js +source: { + start: { + line: 2, + column: 3 + }, + end: { + line: 2, + column: 6 + } +} +``` + +### `node.sourceIndex` + +The zero-based index of the node within the original source string. + +Within the following CSS, the `.baz` class node will have a `sourceIndex` of `12`. + +```css +.foo, .bar, .baz {} +``` + +## Container types + +The `root`, `selector`, and `pseudo` nodes have some helper methods for working +with their children. + +### `container.nodes` + +An array of the container's children. + +```js +// Input: h1 h2 +selectors.at(0).nodes.length // => 3 +selectors.at(0).nodes[0].value // => 'h1' +selectors.at(0).nodes[1].value // => ' ' +``` + +### `container.first` & `container.last` + +The first/last child of the container. + +```js +selector.first === selector.nodes[0]; +selector.last === selector.nodes[selector.nodes.length - 1]; +``` + +### `container.at(index)` + +Returns the node at position `index`. + +```js +selector.at(0) === selector.first; +selector.at(0) === selector.nodes[0]; +``` + +Arguments: + +* `index`: The index of the node to return. + +### `container.atPosition(line, column)` + +Returns the node at the source position `index`. + +```js +selector.at(0) === selector.first; +selector.at(0) === selector.nodes[0]; +``` + +Arguments: + +* `index`: The index of the node to return. + +### `container.index(node)` + +Return the index of the node within its container. + +```js +selector.index(selector.nodes[2]) // => 2 +``` + +Arguments: + +* `node`: A node within the current container. + +### `container.length` + +Proxy to the length of the container's nodes. + +```js +container.length === container.nodes.length +``` + +### `container` Array iterators + +The container class provides proxies to certain Array methods; these are: + +* `container.map === container.nodes.map` +* `container.reduce === container.nodes.reduce` +* `container.every === container.nodes.every` +* `container.some === container.nodes.some` +* `container.filter === container.nodes.filter` +* `container.sort === container.nodes.sort` + +Note that these methods only work on a container's immediate children; recursive +iteration is provided by `container.walk`. + +### `container.each(callback)` + +Iterate the container's immediate children, calling `callback` for each child. +You may return `false` within the callback to break the iteration. + +```js +let className; +selectors.each((selector, index) => { + if (selector.type === 'class') { + className = selector.value; + return false; + } +}); +``` + +Note that unlike `Array#forEach()`, this iterator is safe to use whilst adding +or removing nodes from the container. + +Arguments: + +* `callback (function)`: A function to call for each node, which receives `node` + and `index` arguments. + +### `container.walk(callback)` + +Like `container#each`, but will also iterate child nodes as long as they are +`container` types. + +```js +selectors.walk((selector, index) => { + // all nodes +}); +``` + +Arguments: + +* `callback (function)`: A function to call for each node, which receives `node` + and `index` arguments. + +This iterator is safe to use whilst mutating `container.nodes`, +like `container#each`. + +### `container.walk` proxies + +The container class provides proxy methods for iterating over types of nodes, +so that it is easier to write modules that target specific selectors. Those +methods are: + +* `container.walkAttributes` +* `container.walkClasses` +* `container.walkCombinators` +* `container.walkComments` +* `container.walkIds` +* `container.walkNesting` +* `container.walkPseudos` +* `container.walkTags` +* `container.walkUniversals` + +### `container.split(callback)` + +This method allows you to split a group of nodes by returning `true` from +a callback. It returns an array of arrays, where each inner array corresponds +to the groups that you created via the callback. + +```js +// (input) => h1 h2>>h3 +const list = selectors.first.split(selector => { + return selector.type === 'combinator'; +}); + +// (node values) => [['h1', ' '], ['h2', '>>'], ['h3']] +``` + +Arguments: + +* `callback (function)`: A function to call for each node, which receives `node` + as an argument. + +### `container.prepend(node)` & `container.append(node)` + +Add a node to the start/end of the container. Note that doing so will set +the parent property of the node to this container. + +```js +const id = parser.id({value: 'search'}); +selector.append(id); +``` + +Arguments: + +* `node`: The node to add. + +### `container.insertBefore(old, new)` & `container.insertAfter(old, new)` + +Add a node before or after an existing node in a container: + +```js +selectors.walk(selector => { + if (selector.type !== 'class') { + const className = parser.className({value: 'theme-name'}); + selector.parent.insertAfter(selector, className); + } +}); +``` + +Arguments: + +* `old`: The existing node in the container. +* `new`: The new node to add before/after the existing node. + +### `container.removeChild(node)` + +Remove the node from the container. Note that you can also use +`node.remove()` if you would like to remove just a single node. + +```js +selector.length // => 2 +selector.remove(id) +selector.length // => 1; +id.parent // undefined +``` + +Arguments: + +* `node`: The node to remove. + +### `container.removeAll()` or `container.empty()` + +Remove all children from the container. + +```js +selector.removeAll(); +selector.length // => 0 +``` + +## Root nodes + +A root node represents a comma separated list of selectors. Indeed, all +a root's `toString()` method does is join its selector children with a ','. +Other than this, it has no special functionality and acts like a container. + +### `root.trailingComma` + +This will be set to `true` if the input has a trailing comma, in order to +support parsing of legacy CSS hacks. + +## Selector nodes + +A selector node represents a single complex selector. For example, this +selector string `h1 h2 h3, [href] > p`, is represented as two selector nodes. +It has no special functionality of its own. + +## Pseudo nodes + +A pseudo selector extends a container node; if it has any parameters of its +own (such as `h1:not(h2, h3)`), they will be its children. Note that the pseudo +`value` will always contain the colons preceding the pseudo identifier. This +is so that both `:before` and `::before` are properly represented in the AST. + +## Attribute nodes + +### `attribute.quoted` + +Returns `true` if the attribute's value is wrapped in quotation marks, false if it is not. +Remains `undefined` if there is no attribute value. + +```css +[href=foo] /* false */ +[href='foo'] /* true */ +[href="foo"] /* true */ +[href] /* undefined */ +``` + +### `attribute.qualifiedAttribute` + +Returns the attribute name qualified with the namespace if one is given. + +### `attribute.offsetOf(part)` + + Returns the offset of the attribute part specified relative to the + start of the node of the output string. This is useful in raising + error messages about a specific part of the attribute, especially + in combination with `attribute.sourceIndex`. + + Returns `-1` if the name is invalid or the value doesn't exist in this + attribute. + + The legal values for `part` are: + + * `"ns"` - alias for "namespace" + * `"namespace"` - the namespace if it exists. + * `"attribute"` - the attribute name + * `"attributeNS"` - the start of the attribute or its namespace + * `"operator"` - the match operator of the attribute + * `"value"` - The value (string or identifier) + * `"insensitive"` - the case insensitivity flag + +### `attribute.raws.unquoted` + +Returns the unquoted content of the attribute's value. +Remains `undefined` if there is no attribute value. + +```css +[href=foo] /* foo */ +[href='foo'] /* foo */ +[href="foo"] /* foo */ +[href] /* undefined */ +``` + +### `attribute.spaces` + +Like `node.spaces` with the `before` and `after` values containing the spaces +around the element, the parts of the attribute can also have spaces before +and after them. The for each of `attribute`, `operator`, `value` and +`insensitive` there is corresponding property of the same nam in +`node.spaces` that has an optional `before` or `after` string containing only +whitespace. + +Note that corresponding values in `attributes.raws.spaces` contain values +including any comments. If set, these values will override the +`attribute.spaces` value. Take care to remove them if changing +`attribute.spaces`. + +### `attribute.raws` + +The raws object stores comments and other information necessary to re-render +the node exactly as it was in the source. + +If a comment is embedded within the identifiers for the `namespace`, `attribute` +or `value` then a property is placed in the raws for that value containing the full source of the propery including comments. + +If a comment is embedded within the space between parts of the attribute +then the raw for that space is set accordingly. + +Setting an attribute's property `raws` value to be deleted. + +For now, changing the spaces required also updating or removing any of the +raws values that override them. + +Example: `[ /*before*/ href /* after-attr */ = /* after-operator */ te/*inside-value*/st/* wow */ /*omg*/i/*bbq*/ /*whodoesthis*/]` would parse as: + +```js +{ + attribute: "href", + operator: "=", + value: "test", + spaces: { + before: '', + after: '', + attribute: { before: ' ', after: ' ' }, + operator: { after: ' ' }, + value: { after: ' ' }, + insensitive: { after: ' ' } + }, + raws: { + spaces: { + attribute: { before: ' /*before*/ ', after: ' /* after-attr */ ' }, + operator: { after: ' /* after-operator */ ' }, + value: { after: '/* wow */ /*omg*/' }, + insensitive: { after: '/*bbq*/ /*whodoesthis*/' } + }, + unquoted: 'test', + value: 'te/*inside-value*/st' + } +} +``` + +## `Processor` + +### `ProcessorOptions` + +* `lossless` - When `true`, whitespace is preserved. Defaults to `true`. +* `updateSelector` - When `true`, if any processor methods are passed a postcss + `Rule` node instead of a string, then that Rule's selector is updated + with the results of the processing. Defaults to `true`. + +### `process|processSync(selectors, [options])` + +Processes the `selectors`, returning a string from the result of processing. + +Note: when the `updateSelector` option is set, the rule's selector +will be updated with the resulting string. + +**Example:** + +```js +const parser = require("postcss-selector-parser"); +const processor = parser(); + +let result = processor.processSync(' .class'); +console.log(result); +// => .class + +// Asynchronous operation +let promise = processor.process(' .class').then(result => { + console.log(result) + // => .class +}); + +// To have the parser normalize whitespace values, utilize the options +result = processor.processSync(' .class ', {lossless: false}); +console.log(result); +// => .class + +// For better syntax errors, pass a PostCSS Rule node. +const postcss = require('postcss'); +rule = postcss.rule({selector: ' #foo > a, .class '}); +processor.process(rule, {lossless: false, updateSelector: true}).then(result => { + console.log(result); + // => #foo>a,.class + console.log("rule:", rule.selector); + // => rule: #foo>a,.class +}) +``` + +Arguments: + +* `selectors (string|postcss.Rule)`: Either a selector string or a PostCSS Rule + node. +* `[options] (object)`: Process options + + +### `ast|astSync(selectors, [options])` + +Like `process()` and `processSync()` but after +processing the `selectors` these methods return the `Root` node of the result +instead of a string. + +Note: when the `updateSelector` option is set, the rule's selector +will be updated with the resulting string. + +### `transform|transformSync(selectors, [options])` + +Like `process()` and `processSync()` but after +processing the `selectors` these methods return the value returned by the +processor callback. + +Note: when the `updateSelector` option is set, the rule's selector +will be updated with the resulting string. + +### Error Handling Within Selector Processors + +The root node passed to the selector processor callback +has a method `error(message, options)` that returns an +error object. This method should always be used to raise +errors relating to the syntax of selectors. The options +to this method are passed to postcss's error constructor +([documentation](http://api.postcss.org/Container.html#error)). + +#### Async Error Example + +```js +let processor = (root) => { + return new Promise((resolve, reject) => { + root.walkClasses((classNode) => { + if (/^(.*)[-_]/.test(classNode.value)) { + let msg = "classes may not have underscores or dashes in them"; + reject(root.error(msg, { + index: classNode.sourceIndex + RegExp.$1.length + 1, + word: classNode.value + })); + } + }); + resolve(); + }); +}; + +const postcss = require("postcss"); +const parser = require("postcss-selector-parser"); +const selectorProcessor = parser(processor); +const plugin = postcss.plugin('classValidator', (options) => { + return (root) => { + let promises = []; + root.walkRules(rule => { + promises.push(selectorProcessor.process(rule)); + }); + return Promise.all(promises); + }; +}); +postcss(plugin()).process(` +.foo-bar { + color: red; +} +`.trim(), {from: 'test.css'}).catch((e) => console.error(e.toString())); + +// CssSyntaxError: classValidator: ./test.css:1:5: classes may not have underscores or dashes in them +// +// > 1 | .foo-bar { +// | ^ +// 2 | color: red; +// 3 | } +``` + +#### Synchronous Error Example + +```js +let processor = (root) => { + root.walkClasses((classNode) => { + if (/.*[-_]/.test(classNode.value)) { + let msg = "classes may not have underscores or dashes in them"; + throw root.error(msg, { + index: classNode.sourceIndex, + word: classNode.value + }); + } + }); +}; + +const postcss = require("postcss"); +const parser = require("postcss-selector-parser"); +const selectorProcessor = parser(processor); +const plugin = postcss.plugin('classValidator', (options) => { + return (root) => { + root.walkRules(rule => { + selectorProcessor.processSync(rule); + }); + }; +}); +postcss(plugin()).process(` +.foo-bar { + color: red; +} +`.trim(), {from: 'test.css'}).catch((e) => console.error(e.toString())); + +// CssSyntaxError: classValidator: ./test.css:1:5: classes may not have underscores or dashes in them +// +// > 1 | .foo-bar { +// | ^ +// 2 | color: red; +// 3 | } +``` diff --git a/node_modules/postcss-selector-parser/LICENSE-MIT b/node_modules/postcss-selector-parser/LICENSE-MIT new file mode 100644 index 0000000000000..fd0e863a614fb --- /dev/null +++ b/node_modules/postcss-selector-parser/LICENSE-MIT @@ -0,0 +1,22 @@ +Copyright (c) Ben Briggs (http://beneb.info) + +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. diff --git a/node_modules/postcss-selector-parser/dist/index.js b/node_modules/postcss-selector-parser/dist/index.js new file mode 100644 index 0000000000000..6e76a32bdd442 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/index.js @@ -0,0 +1,24 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _processor = _interopRequireDefault(require("./processor")); + +var selectors = _interopRequireWildcard(require("./selectors")); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var parser = function parser(processor) { + return new _processor["default"](processor); +}; + +Object.assign(parser, selectors); +delete parser.__esModule; +var _default = parser; +exports["default"] = _default; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/parser.js b/node_modules/postcss-selector-parser/dist/parser.js new file mode 100644 index 0000000000000..e0451de00f43e --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/parser.js @@ -0,0 +1,1243 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _root = _interopRequireDefault(require("./selectors/root")); + +var _selector = _interopRequireDefault(require("./selectors/selector")); + +var _className = _interopRequireDefault(require("./selectors/className")); + +var _comment = _interopRequireDefault(require("./selectors/comment")); + +var _id = _interopRequireDefault(require("./selectors/id")); + +var _tag = _interopRequireDefault(require("./selectors/tag")); + +var _string = _interopRequireDefault(require("./selectors/string")); + +var _pseudo = _interopRequireDefault(require("./selectors/pseudo")); + +var _attribute = _interopRequireWildcard(require("./selectors/attribute")); + +var _universal = _interopRequireDefault(require("./selectors/universal")); + +var _combinator = _interopRequireDefault(require("./selectors/combinator")); + +var _nesting = _interopRequireDefault(require("./selectors/nesting")); + +var _sortAscending = _interopRequireDefault(require("./sortAscending")); + +var _tokenize = _interopRequireWildcard(require("./tokenize")); + +var tokens = _interopRequireWildcard(require("./tokenTypes")); + +var types = _interopRequireWildcard(require("./selectors/types")); + +var _util = require("./util"); + +var _WHITESPACE_TOKENS, _Object$assign; + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var WHITESPACE_TOKENS = (_WHITESPACE_TOKENS = {}, _WHITESPACE_TOKENS[tokens.space] = true, _WHITESPACE_TOKENS[tokens.cr] = true, _WHITESPACE_TOKENS[tokens.feed] = true, _WHITESPACE_TOKENS[tokens.newline] = true, _WHITESPACE_TOKENS[tokens.tab] = true, _WHITESPACE_TOKENS); +var WHITESPACE_EQUIV_TOKENS = Object.assign({}, WHITESPACE_TOKENS, (_Object$assign = {}, _Object$assign[tokens.comment] = true, _Object$assign)); + +function tokenStart(token) { + return { + line: token[_tokenize.FIELDS.START_LINE], + column: token[_tokenize.FIELDS.START_COL] + }; +} + +function tokenEnd(token) { + return { + line: token[_tokenize.FIELDS.END_LINE], + column: token[_tokenize.FIELDS.END_COL] + }; +} + +function getSource(startLine, startColumn, endLine, endColumn) { + return { + start: { + line: startLine, + column: startColumn + }, + end: { + line: endLine, + column: endColumn + } + }; +} + +function getTokenSource(token) { + return getSource(token[_tokenize.FIELDS.START_LINE], token[_tokenize.FIELDS.START_COL], token[_tokenize.FIELDS.END_LINE], token[_tokenize.FIELDS.END_COL]); +} + +function getTokenSourceSpan(startToken, endToken) { + if (!startToken) { + return undefined; + } + + return getSource(startToken[_tokenize.FIELDS.START_LINE], startToken[_tokenize.FIELDS.START_COL], endToken[_tokenize.FIELDS.END_LINE], endToken[_tokenize.FIELDS.END_COL]); +} + +function unescapeProp(node, prop) { + var value = node[prop]; + + if (typeof value !== "string") { + return; + } + + if (value.indexOf("\\") !== -1) { + (0, _util.ensureObject)(node, 'raws'); + node[prop] = (0, _util.unesc)(value); + + if (node.raws[prop] === undefined) { + node.raws[prop] = value; + } + } + + return node; +} + +function indexesOf(array, item) { + var i = -1; + var indexes = []; + + while ((i = array.indexOf(item, i + 1)) !== -1) { + indexes.push(i); + } + + return indexes; +} + +function uniqs() { + var list = Array.prototype.concat.apply([], arguments); + return list.filter(function (item, i) { + return i === list.indexOf(item); + }); +} + +var Parser = /*#__PURE__*/function () { + function Parser(rule, options) { + if (options === void 0) { + options = {}; + } + + this.rule = rule; + this.options = Object.assign({ + lossy: false, + safe: false + }, options); + this.position = 0; + this.css = typeof this.rule === 'string' ? this.rule : this.rule.selector; + this.tokens = (0, _tokenize["default"])({ + css: this.css, + error: this._errorGenerator(), + safe: this.options.safe + }); + var rootSource = getTokenSourceSpan(this.tokens[0], this.tokens[this.tokens.length - 1]); + this.root = new _root["default"]({ + source: rootSource + }); + this.root.errorGenerator = this._errorGenerator(); + var selector = new _selector["default"]({ + source: { + start: { + line: 1, + column: 1 + } + } + }); + this.root.append(selector); + this.current = selector; + this.loop(); + } + + var _proto = Parser.prototype; + + _proto._errorGenerator = function _errorGenerator() { + var _this = this; + + return function (message, errorOptions) { + if (typeof _this.rule === 'string') { + return new Error(message); + } + + return _this.rule.error(message, errorOptions); + }; + }; + + _proto.attribute = function attribute() { + var attr = []; + var startingToken = this.currToken; + this.position++; + + while (this.position < this.tokens.length && this.currToken[_tokenize.FIELDS.TYPE] !== tokens.closeSquare) { + attr.push(this.currToken); + this.position++; + } + + if (this.currToken[_tokenize.FIELDS.TYPE] !== tokens.closeSquare) { + return this.expected('closing square bracket', this.currToken[_tokenize.FIELDS.START_POS]); + } + + var len = attr.length; + var node = { + source: getSource(startingToken[1], startingToken[2], this.currToken[3], this.currToken[4]), + sourceIndex: startingToken[_tokenize.FIELDS.START_POS] + }; + + if (len === 1 && !~[tokens.word].indexOf(attr[0][_tokenize.FIELDS.TYPE])) { + return this.expected('attribute', attr[0][_tokenize.FIELDS.START_POS]); + } + + var pos = 0; + var spaceBefore = ''; + var commentBefore = ''; + var lastAdded = null; + var spaceAfterMeaningfulToken = false; + + while (pos < len) { + var token = attr[pos]; + var content = this.content(token); + var next = attr[pos + 1]; + + switch (token[_tokenize.FIELDS.TYPE]) { + case tokens.space: + // if ( + // len === 1 || + // pos === 0 && this.content(next) === '|' + // ) { + // return this.expected('attribute', token[TOKEN.START_POS], content); + // } + spaceAfterMeaningfulToken = true; + + if (this.options.lossy) { + break; + } + + if (lastAdded) { + (0, _util.ensureObject)(node, 'spaces', lastAdded); + var prevContent = node.spaces[lastAdded].after || ''; + node.spaces[lastAdded].after = prevContent + content; + var existingComment = (0, _util.getProp)(node, 'raws', 'spaces', lastAdded, 'after') || null; + + if (existingComment) { + node.raws.spaces[lastAdded].after = existingComment + content; + } + } else { + spaceBefore = spaceBefore + content; + commentBefore = commentBefore + content; + } + + break; + + case tokens.asterisk: + if (next[_tokenize.FIELDS.TYPE] === tokens.equals) { + node.operator = content; + lastAdded = 'operator'; + } else if ((!node.namespace || lastAdded === "namespace" && !spaceAfterMeaningfulToken) && next) { + if (spaceBefore) { + (0, _util.ensureObject)(node, 'spaces', 'attribute'); + node.spaces.attribute.before = spaceBefore; + spaceBefore = ''; + } + + if (commentBefore) { + (0, _util.ensureObject)(node, 'raws', 'spaces', 'attribute'); + node.raws.spaces.attribute.before = spaceBefore; + commentBefore = ''; + } + + node.namespace = (node.namespace || "") + content; + var rawValue = (0, _util.getProp)(node, 'raws', 'namespace') || null; + + if (rawValue) { + node.raws.namespace += content; + } + + lastAdded = 'namespace'; + } + + spaceAfterMeaningfulToken = false; + break; + + case tokens.dollar: + if (lastAdded === "value") { + var oldRawValue = (0, _util.getProp)(node, 'raws', 'value'); + node.value += "$"; + + if (oldRawValue) { + node.raws.value = oldRawValue + "$"; + } + + break; + } + + // Falls through + + case tokens.caret: + if (next[_tokenize.FIELDS.TYPE] === tokens.equals) { + node.operator = content; + lastAdded = 'operator'; + } + + spaceAfterMeaningfulToken = false; + break; + + case tokens.combinator: + if (content === '~' && next[_tokenize.FIELDS.TYPE] === tokens.equals) { + node.operator = content; + lastAdded = 'operator'; + } + + if (content !== '|') { + spaceAfterMeaningfulToken = false; + break; + } + + if (next[_tokenize.FIELDS.TYPE] === tokens.equals) { + node.operator = content; + lastAdded = 'operator'; + } else if (!node.namespace && !node.attribute) { + node.namespace = true; + } + + spaceAfterMeaningfulToken = false; + break; + + case tokens.word: + if (next && this.content(next) === '|' && attr[pos + 2] && attr[pos + 2][_tokenize.FIELDS.TYPE] !== tokens.equals && // this look-ahead probably fails with comment nodes involved. + !node.operator && !node.namespace) { + node.namespace = content; + lastAdded = 'namespace'; + } else if (!node.attribute || lastAdded === "attribute" && !spaceAfterMeaningfulToken) { + if (spaceBefore) { + (0, _util.ensureObject)(node, 'spaces', 'attribute'); + node.spaces.attribute.before = spaceBefore; + spaceBefore = ''; + } + + if (commentBefore) { + (0, _util.ensureObject)(node, 'raws', 'spaces', 'attribute'); + node.raws.spaces.attribute.before = commentBefore; + commentBefore = ''; + } + + node.attribute = (node.attribute || "") + content; + + var _rawValue = (0, _util.getProp)(node, 'raws', 'attribute') || null; + + if (_rawValue) { + node.raws.attribute += content; + } + + lastAdded = 'attribute'; + } else if (!node.value && node.value !== "" || lastAdded === "value" && !spaceAfterMeaningfulToken) { + var _unescaped = (0, _util.unesc)(content); + + var _oldRawValue = (0, _util.getProp)(node, 'raws', 'value') || ''; + + var oldValue = node.value || ''; + node.value = oldValue + _unescaped; + node.quoteMark = null; + + if (_unescaped !== content || _oldRawValue) { + (0, _util.ensureObject)(node, 'raws'); + node.raws.value = (_oldRawValue || oldValue) + content; + } + + lastAdded = 'value'; + } else { + var insensitive = content === 'i' || content === "I"; + + if ((node.value || node.value === '') && (node.quoteMark || spaceAfterMeaningfulToken)) { + node.insensitive = insensitive; + + if (!insensitive || content === "I") { + (0, _util.ensureObject)(node, 'raws'); + node.raws.insensitiveFlag = content; + } + + lastAdded = 'insensitive'; + + if (spaceBefore) { + (0, _util.ensureObject)(node, 'spaces', 'insensitive'); + node.spaces.insensitive.before = spaceBefore; + spaceBefore = ''; + } + + if (commentBefore) { + (0, _util.ensureObject)(node, 'raws', 'spaces', 'insensitive'); + node.raws.spaces.insensitive.before = commentBefore; + commentBefore = ''; + } + } else if (node.value || node.value === '') { + lastAdded = 'value'; + node.value += content; + + if (node.raws.value) { + node.raws.value += content; + } + } + } + + spaceAfterMeaningfulToken = false; + break; + + case tokens.str: + if (!node.attribute || !node.operator) { + return this.error("Expected an attribute followed by an operator preceding the string.", { + index: token[_tokenize.FIELDS.START_POS] + }); + } + + var _unescapeValue = (0, _attribute.unescapeValue)(content), + unescaped = _unescapeValue.unescaped, + quoteMark = _unescapeValue.quoteMark; + + node.value = unescaped; + node.quoteMark = quoteMark; + lastAdded = 'value'; + (0, _util.ensureObject)(node, 'raws'); + node.raws.value = content; + spaceAfterMeaningfulToken = false; + break; + + case tokens.equals: + if (!node.attribute) { + return this.expected('attribute', token[_tokenize.FIELDS.START_POS], content); + } + + if (node.value) { + return this.error('Unexpected "=" found; an operator was already defined.', { + index: token[_tokenize.FIELDS.START_POS] + }); + } + + node.operator = node.operator ? node.operator + content : content; + lastAdded = 'operator'; + spaceAfterMeaningfulToken = false; + break; + + case tokens.comment: + if (lastAdded) { + if (spaceAfterMeaningfulToken || next && next[_tokenize.FIELDS.TYPE] === tokens.space || lastAdded === 'insensitive') { + var lastComment = (0, _util.getProp)(node, 'spaces', lastAdded, 'after') || ''; + var rawLastComment = (0, _util.getProp)(node, 'raws', 'spaces', lastAdded, 'after') || lastComment; + (0, _util.ensureObject)(node, 'raws', 'spaces', lastAdded); + node.raws.spaces[lastAdded].after = rawLastComment + content; + } else { + var lastValue = node[lastAdded] || ''; + var rawLastValue = (0, _util.getProp)(node, 'raws', lastAdded) || lastValue; + (0, _util.ensureObject)(node, 'raws'); + node.raws[lastAdded] = rawLastValue + content; + } + } else { + commentBefore = commentBefore + content; + } + + break; + + default: + return this.error("Unexpected \"" + content + "\" found.", { + index: token[_tokenize.FIELDS.START_POS] + }); + } + + pos++; + } + + unescapeProp(node, "attribute"); + unescapeProp(node, "namespace"); + this.newNode(new _attribute["default"](node)); + this.position++; + } + /** + * return a node containing meaningless garbage up to (but not including) the specified token position. + * if the token position is negative, all remaining tokens are consumed. + * + * This returns an array containing a single string node if all whitespace, + * otherwise an array of comment nodes with space before and after. + * + * These tokens are not added to the current selector, the caller can add them or use them to amend + * a previous node's space metadata. + * + * In lossy mode, this returns only comments. + */ + ; + + _proto.parseWhitespaceEquivalentTokens = function parseWhitespaceEquivalentTokens(stopPosition) { + if (stopPosition < 0) { + stopPosition = this.tokens.length; + } + + var startPosition = this.position; + var nodes = []; + var space = ""; + var lastComment = undefined; + + do { + if (WHITESPACE_TOKENS[this.currToken[_tokenize.FIELDS.TYPE]]) { + if (!this.options.lossy) { + space += this.content(); + } + } else if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.comment) { + var spaces = {}; + + if (space) { + spaces.before = space; + space = ""; + } + + lastComment = new _comment["default"]({ + value: this.content(), + source: getTokenSource(this.currToken), + sourceIndex: this.currToken[_tokenize.FIELDS.START_POS], + spaces: spaces + }); + nodes.push(lastComment); + } + } while (++this.position < stopPosition); + + if (space) { + if (lastComment) { + lastComment.spaces.after = space; + } else if (!this.options.lossy) { + var firstToken = this.tokens[startPosition]; + var lastToken = this.tokens[this.position - 1]; + nodes.push(new _string["default"]({ + value: '', + source: getSource(firstToken[_tokenize.FIELDS.START_LINE], firstToken[_tokenize.FIELDS.START_COL], lastToken[_tokenize.FIELDS.END_LINE], lastToken[_tokenize.FIELDS.END_COL]), + sourceIndex: firstToken[_tokenize.FIELDS.START_POS], + spaces: { + before: space, + after: '' + } + })); + } + } + + return nodes; + } + /** + * + * @param {*} nodes + */ + ; + + _proto.convertWhitespaceNodesToSpace = function convertWhitespaceNodesToSpace(nodes, requiredSpace) { + var _this2 = this; + + if (requiredSpace === void 0) { + requiredSpace = false; + } + + var space = ""; + var rawSpace = ""; + nodes.forEach(function (n) { + var spaceBefore = _this2.lossySpace(n.spaces.before, requiredSpace); + + var rawSpaceBefore = _this2.lossySpace(n.rawSpaceBefore, requiredSpace); + + space += spaceBefore + _this2.lossySpace(n.spaces.after, requiredSpace && spaceBefore.length === 0); + rawSpace += spaceBefore + n.value + _this2.lossySpace(n.rawSpaceAfter, requiredSpace && rawSpaceBefore.length === 0); + }); + + if (rawSpace === space) { + rawSpace = undefined; + } + + var result = { + space: space, + rawSpace: rawSpace + }; + return result; + }; + + _proto.isNamedCombinator = function isNamedCombinator(position) { + if (position === void 0) { + position = this.position; + } + + return this.tokens[position + 0] && this.tokens[position + 0][_tokenize.FIELDS.TYPE] === tokens.slash && this.tokens[position + 1] && this.tokens[position + 1][_tokenize.FIELDS.TYPE] === tokens.word && this.tokens[position + 2] && this.tokens[position + 2][_tokenize.FIELDS.TYPE] === tokens.slash; + }; + + _proto.namedCombinator = function namedCombinator() { + if (this.isNamedCombinator()) { + var nameRaw = this.content(this.tokens[this.position + 1]); + var name = (0, _util.unesc)(nameRaw).toLowerCase(); + var raws = {}; + + if (name !== nameRaw) { + raws.value = "/" + nameRaw + "/"; + } + + var node = new _combinator["default"]({ + value: "/" + name + "/", + source: getSource(this.currToken[_tokenize.FIELDS.START_LINE], this.currToken[_tokenize.FIELDS.START_COL], this.tokens[this.position + 2][_tokenize.FIELDS.END_LINE], this.tokens[this.position + 2][_tokenize.FIELDS.END_COL]), + sourceIndex: this.currToken[_tokenize.FIELDS.START_POS], + raws: raws + }); + this.position = this.position + 3; + return node; + } else { + this.unexpected(); + } + }; + + _proto.combinator = function combinator() { + var _this3 = this; + + if (this.content() === '|') { + return this.namespace(); + } // We need to decide between a space that's a descendant combinator and meaningless whitespace at the end of a selector. + + + var nextSigTokenPos = this.locateNextMeaningfulToken(this.position); + + if (nextSigTokenPos < 0 || this.tokens[nextSigTokenPos][_tokenize.FIELDS.TYPE] === tokens.comma) { + var nodes = this.parseWhitespaceEquivalentTokens(nextSigTokenPos); + + if (nodes.length > 0) { + var last = this.current.last; + + if (last) { + var _this$convertWhitespa = this.convertWhitespaceNodesToSpace(nodes), + space = _this$convertWhitespa.space, + rawSpace = _this$convertWhitespa.rawSpace; + + if (rawSpace !== undefined) { + last.rawSpaceAfter += rawSpace; + } + + last.spaces.after += space; + } else { + nodes.forEach(function (n) { + return _this3.newNode(n); + }); + } + } + + return; + } + + var firstToken = this.currToken; + var spaceOrDescendantSelectorNodes = undefined; + + if (nextSigTokenPos > this.position) { + spaceOrDescendantSelectorNodes = this.parseWhitespaceEquivalentTokens(nextSigTokenPos); + } + + var node; + + if (this.isNamedCombinator()) { + node = this.namedCombinator(); + } else if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.combinator) { + node = new _combinator["default"]({ + value: this.content(), + source: getTokenSource(this.currToken), + sourceIndex: this.currToken[_tokenize.FIELDS.START_POS] + }); + this.position++; + } else if (WHITESPACE_TOKENS[this.currToken[_tokenize.FIELDS.TYPE]]) {// pass + } else if (!spaceOrDescendantSelectorNodes) { + this.unexpected(); + } + + if (node) { + if (spaceOrDescendantSelectorNodes) { + var _this$convertWhitespa2 = this.convertWhitespaceNodesToSpace(spaceOrDescendantSelectorNodes), + _space = _this$convertWhitespa2.space, + _rawSpace = _this$convertWhitespa2.rawSpace; + + node.spaces.before = _space; + node.rawSpaceBefore = _rawSpace; + } + } else { + // descendant combinator + var _this$convertWhitespa3 = this.convertWhitespaceNodesToSpace(spaceOrDescendantSelectorNodes, true), + _space2 = _this$convertWhitespa3.space, + _rawSpace2 = _this$convertWhitespa3.rawSpace; + + if (!_rawSpace2) { + _rawSpace2 = _space2; + } + + var spaces = {}; + var raws = { + spaces: {} + }; + + if (_space2.endsWith(' ') && _rawSpace2.endsWith(' ')) { + spaces.before = _space2.slice(0, _space2.length - 1); + raws.spaces.before = _rawSpace2.slice(0, _rawSpace2.length - 1); + } else if (_space2.startsWith(' ') && _rawSpace2.startsWith(' ')) { + spaces.after = _space2.slice(1); + raws.spaces.after = _rawSpace2.slice(1); + } else { + raws.value = _rawSpace2; + } + + node = new _combinator["default"]({ + value: ' ', + source: getTokenSourceSpan(firstToken, this.tokens[this.position - 1]), + sourceIndex: firstToken[_tokenize.FIELDS.START_POS], + spaces: spaces, + raws: raws + }); + } + + if (this.currToken && this.currToken[_tokenize.FIELDS.TYPE] === tokens.space) { + node.spaces.after = this.optionalSpace(this.content()); + this.position++; + } + + return this.newNode(node); + }; + + _proto.comma = function comma() { + if (this.position === this.tokens.length - 1) { + this.root.trailingComma = true; + this.position++; + return; + } + + this.current._inferEndPosition(); + + var selector = new _selector["default"]({ + source: { + start: tokenStart(this.tokens[this.position + 1]) + } + }); + this.current.parent.append(selector); + this.current = selector; + this.position++; + }; + + _proto.comment = function comment() { + var current = this.currToken; + this.newNode(new _comment["default"]({ + value: this.content(), + source: getTokenSource(current), + sourceIndex: current[_tokenize.FIELDS.START_POS] + })); + this.position++; + }; + + _proto.error = function error(message, opts) { + throw this.root.error(message, opts); + }; + + _proto.missingBackslash = function missingBackslash() { + return this.error('Expected a backslash preceding the semicolon.', { + index: this.currToken[_tokenize.FIELDS.START_POS] + }); + }; + + _proto.missingParenthesis = function missingParenthesis() { + return this.expected('opening parenthesis', this.currToken[_tokenize.FIELDS.START_POS]); + }; + + _proto.missingSquareBracket = function missingSquareBracket() { + return this.expected('opening square bracket', this.currToken[_tokenize.FIELDS.START_POS]); + }; + + _proto.unexpected = function unexpected() { + return this.error("Unexpected '" + this.content() + "'. Escaping special characters with \\ may help.", this.currToken[_tokenize.FIELDS.START_POS]); + }; + + _proto.namespace = function namespace() { + var before = this.prevToken && this.content(this.prevToken) || true; + + if (this.nextToken[_tokenize.FIELDS.TYPE] === tokens.word) { + this.position++; + return this.word(before); + } else if (this.nextToken[_tokenize.FIELDS.TYPE] === tokens.asterisk) { + this.position++; + return this.universal(before); + } + }; + + _proto.nesting = function nesting() { + if (this.nextToken) { + var nextContent = this.content(this.nextToken); + + if (nextContent === "|") { + this.position++; + return; + } + } + + var current = this.currToken; + this.newNode(new _nesting["default"]({ + value: this.content(), + source: getTokenSource(current), + sourceIndex: current[_tokenize.FIELDS.START_POS] + })); + this.position++; + }; + + _proto.parentheses = function parentheses() { + var last = this.current.last; + var unbalanced = 1; + this.position++; + + if (last && last.type === types.PSEUDO) { + var selector = new _selector["default"]({ + source: { + start: tokenStart(this.tokens[this.position - 1]) + } + }); + var cache = this.current; + last.append(selector); + this.current = selector; + + while (this.position < this.tokens.length && unbalanced) { + if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) { + unbalanced++; + } + + if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.closeParenthesis) { + unbalanced--; + } + + if (unbalanced) { + this.parse(); + } else { + this.current.source.end = tokenEnd(this.currToken); + this.current.parent.source.end = tokenEnd(this.currToken); + this.position++; + } + } + + this.current = cache; + } else { + // I think this case should be an error. It's used to implement a basic parse of media queries + // but I don't think it's a good idea. + var parenStart = this.currToken; + var parenValue = "("; + var parenEnd; + + while (this.position < this.tokens.length && unbalanced) { + if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) { + unbalanced++; + } + + if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.closeParenthesis) { + unbalanced--; + } + + parenEnd = this.currToken; + parenValue += this.parseParenthesisToken(this.currToken); + this.position++; + } + + if (last) { + last.appendToPropertyAndEscape("value", parenValue, parenValue); + } else { + this.newNode(new _string["default"]({ + value: parenValue, + source: getSource(parenStart[_tokenize.FIELDS.START_LINE], parenStart[_tokenize.FIELDS.START_COL], parenEnd[_tokenize.FIELDS.END_LINE], parenEnd[_tokenize.FIELDS.END_COL]), + sourceIndex: parenStart[_tokenize.FIELDS.START_POS] + })); + } + } + + if (unbalanced) { + return this.expected('closing parenthesis', this.currToken[_tokenize.FIELDS.START_POS]); + } + }; + + _proto.pseudo = function pseudo() { + var _this4 = this; + + var pseudoStr = ''; + var startingToken = this.currToken; + + while (this.currToken && this.currToken[_tokenize.FIELDS.TYPE] === tokens.colon) { + pseudoStr += this.content(); + this.position++; + } + + if (!this.currToken) { + return this.expected(['pseudo-class', 'pseudo-element'], this.position - 1); + } + + if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.word) { + this.splitWord(false, function (first, length) { + pseudoStr += first; + + _this4.newNode(new _pseudo["default"]({ + value: pseudoStr, + source: getTokenSourceSpan(startingToken, _this4.currToken), + sourceIndex: startingToken[_tokenize.FIELDS.START_POS] + })); + + if (length > 1 && _this4.nextToken && _this4.nextToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) { + _this4.error('Misplaced parenthesis.', { + index: _this4.nextToken[_tokenize.FIELDS.START_POS] + }); + } + }); + } else { + return this.expected(['pseudo-class', 'pseudo-element'], this.currToken[_tokenize.FIELDS.START_POS]); + } + }; + + _proto.space = function space() { + var content = this.content(); // Handle space before and after the selector + + if (this.position === 0 || this.prevToken[_tokenize.FIELDS.TYPE] === tokens.comma || this.prevToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis || this.current.nodes.every(function (node) { + return node.type === 'comment'; + })) { + this.spaces = this.optionalSpace(content); + this.position++; + } else if (this.position === this.tokens.length - 1 || this.nextToken[_tokenize.FIELDS.TYPE] === tokens.comma || this.nextToken[_tokenize.FIELDS.TYPE] === tokens.closeParenthesis) { + this.current.last.spaces.after = this.optionalSpace(content); + this.position++; + } else { + this.combinator(); + } + }; + + _proto.string = function string() { + var current = this.currToken; + this.newNode(new _string["default"]({ + value: this.content(), + source: getTokenSource(current), + sourceIndex: current[_tokenize.FIELDS.START_POS] + })); + this.position++; + }; + + _proto.universal = function universal(namespace) { + var nextToken = this.nextToken; + + if (nextToken && this.content(nextToken) === '|') { + this.position++; + return this.namespace(); + } + + var current = this.currToken; + this.newNode(new _universal["default"]({ + value: this.content(), + source: getTokenSource(current), + sourceIndex: current[_tokenize.FIELDS.START_POS] + }), namespace); + this.position++; + }; + + _proto.splitWord = function splitWord(namespace, firstCallback) { + var _this5 = this; + + var nextToken = this.nextToken; + var word = this.content(); + + while (nextToken && ~[tokens.dollar, tokens.caret, tokens.equals, tokens.word].indexOf(nextToken[_tokenize.FIELDS.TYPE])) { + this.position++; + var current = this.content(); + word += current; + + if (current.lastIndexOf('\\') === current.length - 1) { + var next = this.nextToken; + + if (next && next[_tokenize.FIELDS.TYPE] === tokens.space) { + word += this.requiredSpace(this.content(next)); + this.position++; + } + } + + nextToken = this.nextToken; + } + + var hasClass = indexesOf(word, '.').filter(function (i) { + // Allow escaped dot within class name + var escapedDot = word[i - 1] === '\\'; // Allow decimal numbers percent in @keyframes + + var isKeyframesPercent = /^\d+\.\d+%$/.test(word); + return !escapedDot && !isKeyframesPercent; + }); + var hasId = indexesOf(word, '#').filter(function (i) { + return word[i - 1] !== '\\'; + }); // Eliminate Sass interpolations from the list of id indexes + + var interpolations = indexesOf(word, '#{'); + + if (interpolations.length) { + hasId = hasId.filter(function (hashIndex) { + return !~interpolations.indexOf(hashIndex); + }); + } + + var indices = (0, _sortAscending["default"])(uniqs([0].concat(hasClass, hasId))); + indices.forEach(function (ind, i) { + var index = indices[i + 1] || word.length; + var value = word.slice(ind, index); + + if (i === 0 && firstCallback) { + return firstCallback.call(_this5, value, indices.length); + } + + var node; + var current = _this5.currToken; + var sourceIndex = current[_tokenize.FIELDS.START_POS] + indices[i]; + var source = getSource(current[1], current[2] + ind, current[3], current[2] + (index - 1)); + + if (~hasClass.indexOf(ind)) { + var classNameOpts = { + value: value.slice(1), + source: source, + sourceIndex: sourceIndex + }; + node = new _className["default"](unescapeProp(classNameOpts, "value")); + } else if (~hasId.indexOf(ind)) { + var idOpts = { + value: value.slice(1), + source: source, + sourceIndex: sourceIndex + }; + node = new _id["default"](unescapeProp(idOpts, "value")); + } else { + var tagOpts = { + value: value, + source: source, + sourceIndex: sourceIndex + }; + unescapeProp(tagOpts, "value"); + node = new _tag["default"](tagOpts); + } + + _this5.newNode(node, namespace); // Ensure that the namespace is used only once + + + namespace = null; + }); + this.position++; + }; + + _proto.word = function word(namespace) { + var nextToken = this.nextToken; + + if (nextToken && this.content(nextToken) === '|') { + this.position++; + return this.namespace(); + } + + return this.splitWord(namespace); + }; + + _proto.loop = function loop() { + while (this.position < this.tokens.length) { + this.parse(true); + } + + this.current._inferEndPosition(); + + return this.root; + }; + + _proto.parse = function parse(throwOnParenthesis) { + switch (this.currToken[_tokenize.FIELDS.TYPE]) { + case tokens.space: + this.space(); + break; + + case tokens.comment: + this.comment(); + break; + + case tokens.openParenthesis: + this.parentheses(); + break; + + case tokens.closeParenthesis: + if (throwOnParenthesis) { + this.missingParenthesis(); + } + + break; + + case tokens.openSquare: + this.attribute(); + break; + + case tokens.dollar: + case tokens.caret: + case tokens.equals: + case tokens.word: + this.word(); + break; + + case tokens.colon: + this.pseudo(); + break; + + case tokens.comma: + this.comma(); + break; + + case tokens.asterisk: + this.universal(); + break; + + case tokens.ampersand: + this.nesting(); + break; + + case tokens.slash: + case tokens.combinator: + this.combinator(); + break; + + case tokens.str: + this.string(); + break; + // These cases throw; no break needed. + + case tokens.closeSquare: + this.missingSquareBracket(); + + case tokens.semicolon: + this.missingBackslash(); + + default: + this.unexpected(); + } + } + /** + * Helpers + */ + ; + + _proto.expected = function expected(description, index, found) { + if (Array.isArray(description)) { + var last = description.pop(); + description = description.join(', ') + " or " + last; + } + + var an = /^[aeiou]/.test(description[0]) ? 'an' : 'a'; + + if (!found) { + return this.error("Expected " + an + " " + description + ".", { + index: index + }); + } + + return this.error("Expected " + an + " " + description + ", found \"" + found + "\" instead.", { + index: index + }); + }; + + _proto.requiredSpace = function requiredSpace(space) { + return this.options.lossy ? ' ' : space; + }; + + _proto.optionalSpace = function optionalSpace(space) { + return this.options.lossy ? '' : space; + }; + + _proto.lossySpace = function lossySpace(space, required) { + if (this.options.lossy) { + return required ? ' ' : ''; + } else { + return space; + } + }; + + _proto.parseParenthesisToken = function parseParenthesisToken(token) { + var content = this.content(token); + + if (token[_tokenize.FIELDS.TYPE] === tokens.space) { + return this.requiredSpace(content); + } else { + return content; + } + }; + + _proto.newNode = function newNode(node, namespace) { + if (namespace) { + if (/^ +$/.test(namespace)) { + if (!this.options.lossy) { + this.spaces = (this.spaces || '') + namespace; + } + + namespace = true; + } + + node.namespace = namespace; + unescapeProp(node, "namespace"); + } + + if (this.spaces) { + node.spaces.before = this.spaces; + this.spaces = ''; + } + + return this.current.append(node); + }; + + _proto.content = function content(token) { + if (token === void 0) { + token = this.currToken; + } + + return this.css.slice(token[_tokenize.FIELDS.START_POS], token[_tokenize.FIELDS.END_POS]); + }; + + /** + * returns the index of the next non-whitespace, non-comment token. + * returns -1 if no meaningful token is found. + */ + _proto.locateNextMeaningfulToken = function locateNextMeaningfulToken(startPosition) { + if (startPosition === void 0) { + startPosition = this.position + 1; + } + + var searchPosition = startPosition; + + while (searchPosition < this.tokens.length) { + if (WHITESPACE_EQUIV_TOKENS[this.tokens[searchPosition][_tokenize.FIELDS.TYPE]]) { + searchPosition++; + continue; + } else { + return searchPosition; + } + } + + return -1; + }; + + _createClass(Parser, [{ + key: "currToken", + get: function get() { + return this.tokens[this.position]; + } + }, { + key: "nextToken", + get: function get() { + return this.tokens[this.position + 1]; + } + }, { + key: "prevToken", + get: function get() { + return this.tokens[this.position - 1]; + } + }]); + + return Parser; +}(); + +exports["default"] = Parser; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/processor.js b/node_modules/postcss-selector-parser/dist/processor.js new file mode 100644 index 0000000000000..a00170c281f96 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/processor.js @@ -0,0 +1,206 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _parser = _interopRequireDefault(require("./parser")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var Processor = /*#__PURE__*/function () { + function Processor(func, options) { + this.func = func || function noop() {}; + + this.funcRes = null; + this.options = options; + } + + var _proto = Processor.prototype; + + _proto._shouldUpdateSelector = function _shouldUpdateSelector(rule, options) { + if (options === void 0) { + options = {}; + } + + var merged = Object.assign({}, this.options, options); + + if (merged.updateSelector === false) { + return false; + } else { + return typeof rule !== "string"; + } + }; + + _proto._isLossy = function _isLossy(options) { + if (options === void 0) { + options = {}; + } + + var merged = Object.assign({}, this.options, options); + + if (merged.lossless === false) { + return true; + } else { + return false; + } + }; + + _proto._root = function _root(rule, options) { + if (options === void 0) { + options = {}; + } + + var parser = new _parser["default"](rule, this._parseOptions(options)); + return parser.root; + }; + + _proto._parseOptions = function _parseOptions(options) { + return { + lossy: this._isLossy(options) + }; + }; + + _proto._run = function _run(rule, options) { + var _this = this; + + if (options === void 0) { + options = {}; + } + + return new Promise(function (resolve, reject) { + try { + var root = _this._root(rule, options); + + Promise.resolve(_this.func(root)).then(function (transform) { + var string = undefined; + + if (_this._shouldUpdateSelector(rule, options)) { + string = root.toString(); + rule.selector = string; + } + + return { + transform: transform, + root: root, + string: string + }; + }).then(resolve, reject); + } catch (e) { + reject(e); + return; + } + }); + }; + + _proto._runSync = function _runSync(rule, options) { + if (options === void 0) { + options = {}; + } + + var root = this._root(rule, options); + + var transform = this.func(root); + + if (transform && typeof transform.then === "function") { + throw new Error("Selector processor returned a promise to a synchronous call."); + } + + var string = undefined; + + if (options.updateSelector && typeof rule !== "string") { + string = root.toString(); + rule.selector = string; + } + + return { + transform: transform, + root: root, + string: string + }; + } + /** + * Process rule into a selector AST. + * + * @param rule {postcss.Rule | string} The css selector to be processed + * @param options The options for processing + * @returns {Promise} The AST of the selector after processing it. + */ + ; + + _proto.ast = function ast(rule, options) { + return this._run(rule, options).then(function (result) { + return result.root; + }); + } + /** + * Process rule into a selector AST synchronously. + * + * @param rule {postcss.Rule | string} The css selector to be processed + * @param options The options for processing + * @returns {parser.Root} The AST of the selector after processing it. + */ + ; + + _proto.astSync = function astSync(rule, options) { + return this._runSync(rule, options).root; + } + /** + * Process a selector into a transformed value asynchronously + * + * @param rule {postcss.Rule | string} The css selector to be processed + * @param options The options for processing + * @returns {Promise} The value returned by the processor. + */ + ; + + _proto.transform = function transform(rule, options) { + return this._run(rule, options).then(function (result) { + return result.transform; + }); + } + /** + * Process a selector into a transformed value synchronously. + * + * @param rule {postcss.Rule | string} The css selector to be processed + * @param options The options for processing + * @returns {any} The value returned by the processor. + */ + ; + + _proto.transformSync = function transformSync(rule, options) { + return this._runSync(rule, options).transform; + } + /** + * Process a selector into a new selector string asynchronously. + * + * @param rule {postcss.Rule | string} The css selector to be processed + * @param options The options for processing + * @returns {string} the selector after processing. + */ + ; + + _proto.process = function process(rule, options) { + return this._run(rule, options).then(function (result) { + return result.string || result.root.toString(); + }); + } + /** + * Process a selector into a new selector string synchronously. + * + * @param rule {postcss.Rule | string} The css selector to be processed + * @param options The options for processing + * @returns {string} the selector after processing. + */ + ; + + _proto.processSync = function processSync(rule, options) { + var result = this._runSync(rule, options); + + return result.string || result.root.toString(); + }; + + return Processor; +}(); + +exports["default"] = Processor; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/attribute.js b/node_modules/postcss-selector-parser/dist/selectors/attribute.js new file mode 100644 index 0000000000000..8f535e5d73129 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/attribute.js @@ -0,0 +1,515 @@ +"use strict"; + +exports.__esModule = true; +exports.unescapeValue = unescapeValue; +exports["default"] = void 0; + +var _cssesc = _interopRequireDefault(require("cssesc")); + +var _unesc = _interopRequireDefault(require("../util/unesc")); + +var _namespace = _interopRequireDefault(require("./namespace")); + +var _types = require("./types"); + +var _CSSESC_QUOTE_OPTIONS; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var deprecate = require("util-deprecate"); + +var WRAPPED_IN_QUOTES = /^('|")([^]*)\1$/; +var warnOfDeprecatedValueAssignment = deprecate(function () {}, "Assigning an attribute a value containing characters that might need to be escaped is deprecated. " + "Call attribute.setValue() instead."); +var warnOfDeprecatedQuotedAssignment = deprecate(function () {}, "Assigning attr.quoted is deprecated and has no effect. Assign to attr.quoteMark instead."); +var warnOfDeprecatedConstructor = deprecate(function () {}, "Constructing an Attribute selector with a value without specifying quoteMark is deprecated. Note: The value should be unescaped now."); + +function unescapeValue(value) { + var deprecatedUsage = false; + var quoteMark = null; + var unescaped = value; + var m = unescaped.match(WRAPPED_IN_QUOTES); + + if (m) { + quoteMark = m[1]; + unescaped = m[2]; + } + + unescaped = (0, _unesc["default"])(unescaped); + + if (unescaped !== value) { + deprecatedUsage = true; + } + + return { + deprecatedUsage: deprecatedUsage, + unescaped: unescaped, + quoteMark: quoteMark + }; +} + +function handleDeprecatedContructorOpts(opts) { + if (opts.quoteMark !== undefined) { + return opts; + } + + if (opts.value === undefined) { + return opts; + } + + warnOfDeprecatedConstructor(); + + var _unescapeValue = unescapeValue(opts.value), + quoteMark = _unescapeValue.quoteMark, + unescaped = _unescapeValue.unescaped; + + if (!opts.raws) { + opts.raws = {}; + } + + if (opts.raws.value === undefined) { + opts.raws.value = opts.value; + } + + opts.value = unescaped; + opts.quoteMark = quoteMark; + return opts; +} + +var Attribute = /*#__PURE__*/function (_Namespace) { + _inheritsLoose(Attribute, _Namespace); + + function Attribute(opts) { + var _this; + + if (opts === void 0) { + opts = {}; + } + + _this = _Namespace.call(this, handleDeprecatedContructorOpts(opts)) || this; + _this.type = _types.ATTRIBUTE; + _this.raws = _this.raws || {}; + Object.defineProperty(_this.raws, 'unquoted', { + get: deprecate(function () { + return _this.value; + }, "attr.raws.unquoted is deprecated. Call attr.value instead."), + set: deprecate(function () { + return _this.value; + }, "Setting attr.raws.unquoted is deprecated and has no effect. attr.value is unescaped by default now.") + }); + _this._constructed = true; + return _this; + } + /** + * Returns the Attribute's value quoted such that it would be legal to use + * in the value of a css file. The original value's quotation setting + * used for stringification is left unchanged. See `setValue(value, options)` + * if you want to control the quote settings of a new value for the attribute. + * + * You can also change the quotation used for the current value by setting quoteMark. + * + * Options: + * * quoteMark {'"' | "'" | null} - Use this value to quote the value. If this + * option is not set, the original value for quoteMark will be used. If + * indeterminate, a double quote is used. The legal values are: + * * `null` - the value will be unquoted and characters will be escaped as necessary. + * * `'` - the value will be quoted with a single quote and single quotes are escaped. + * * `"` - the value will be quoted with a double quote and double quotes are escaped. + * * preferCurrentQuoteMark {boolean} - if true, prefer the source quote mark + * over the quoteMark option value. + * * smart {boolean} - if true, will select a quote mark based on the value + * and the other options specified here. See the `smartQuoteMark()` + * method. + **/ + + + var _proto = Attribute.prototype; + + _proto.getQuotedValue = function getQuotedValue(options) { + if (options === void 0) { + options = {}; + } + + var quoteMark = this._determineQuoteMark(options); + + var cssescopts = CSSESC_QUOTE_OPTIONS[quoteMark]; + var escaped = (0, _cssesc["default"])(this._value, cssescopts); + return escaped; + }; + + _proto._determineQuoteMark = function _determineQuoteMark(options) { + return options.smart ? this.smartQuoteMark(options) : this.preferredQuoteMark(options); + } + /** + * Set the unescaped value with the specified quotation options. The value + * provided must not include any wrapping quote marks -- those quotes will + * be interpreted as part of the value and escaped accordingly. + */ + ; + + _proto.setValue = function setValue(value, options) { + if (options === void 0) { + options = {}; + } + + this._value = value; + this._quoteMark = this._determineQuoteMark(options); + + this._syncRawValue(); + } + /** + * Intelligently select a quoteMark value based on the value's contents. If + * the value is a legal CSS ident, it will not be quoted. Otherwise a quote + * mark will be picked that minimizes the number of escapes. + * + * If there's no clear winner, the quote mark from these options is used, + * then the source quote mark (this is inverted if `preferCurrentQuoteMark` is + * true). If the quoteMark is unspecified, a double quote is used. + * + * @param options This takes the quoteMark and preferCurrentQuoteMark options + * from the quoteValue method. + */ + ; + + _proto.smartQuoteMark = function smartQuoteMark(options) { + var v = this.value; + var numSingleQuotes = v.replace(/[^']/g, '').length; + var numDoubleQuotes = v.replace(/[^"]/g, '').length; + + if (numSingleQuotes + numDoubleQuotes === 0) { + var escaped = (0, _cssesc["default"])(v, { + isIdentifier: true + }); + + if (escaped === v) { + return Attribute.NO_QUOTE; + } else { + var pref = this.preferredQuoteMark(options); + + if (pref === Attribute.NO_QUOTE) { + // pick a quote mark that isn't none and see if it's smaller + var quote = this.quoteMark || options.quoteMark || Attribute.DOUBLE_QUOTE; + var opts = CSSESC_QUOTE_OPTIONS[quote]; + var quoteValue = (0, _cssesc["default"])(v, opts); + + if (quoteValue.length < escaped.length) { + return quote; + } + } + + return pref; + } + } else if (numDoubleQuotes === numSingleQuotes) { + return this.preferredQuoteMark(options); + } else if (numDoubleQuotes < numSingleQuotes) { + return Attribute.DOUBLE_QUOTE; + } else { + return Attribute.SINGLE_QUOTE; + } + } + /** + * Selects the preferred quote mark based on the options and the current quote mark value. + * If you want the quote mark to depend on the attribute value, call `smartQuoteMark(opts)` + * instead. + */ + ; + + _proto.preferredQuoteMark = function preferredQuoteMark(options) { + var quoteMark = options.preferCurrentQuoteMark ? this.quoteMark : options.quoteMark; + + if (quoteMark === undefined) { + quoteMark = options.preferCurrentQuoteMark ? options.quoteMark : this.quoteMark; + } + + if (quoteMark === undefined) { + quoteMark = Attribute.DOUBLE_QUOTE; + } + + return quoteMark; + }; + + _proto._syncRawValue = function _syncRawValue() { + var rawValue = (0, _cssesc["default"])(this._value, CSSESC_QUOTE_OPTIONS[this.quoteMark]); + + if (rawValue === this._value) { + if (this.raws) { + delete this.raws.value; + } + } else { + this.raws.value = rawValue; + } + }; + + _proto._handleEscapes = function _handleEscapes(prop, value) { + if (this._constructed) { + var escaped = (0, _cssesc["default"])(value, { + isIdentifier: true + }); + + if (escaped !== value) { + this.raws[prop] = escaped; + } else { + delete this.raws[prop]; + } + } + }; + + _proto._spacesFor = function _spacesFor(name) { + var attrSpaces = { + before: '', + after: '' + }; + var spaces = this.spaces[name] || {}; + var rawSpaces = this.raws.spaces && this.raws.spaces[name] || {}; + return Object.assign(attrSpaces, spaces, rawSpaces); + }; + + _proto._stringFor = function _stringFor(name, spaceName, concat) { + if (spaceName === void 0) { + spaceName = name; + } + + if (concat === void 0) { + concat = defaultAttrConcat; + } + + var attrSpaces = this._spacesFor(spaceName); + + return concat(this.stringifyProperty(name), attrSpaces); + } + /** + * returns the offset of the attribute part specified relative to the + * start of the node of the output string. + * + * * "ns" - alias for "namespace" + * * "namespace" - the namespace if it exists. + * * "attribute" - the attribute name + * * "attributeNS" - the start of the attribute or its namespace + * * "operator" - the match operator of the attribute + * * "value" - The value (string or identifier) + * * "insensitive" - the case insensitivity flag; + * @param part One of the possible values inside an attribute. + * @returns -1 if the name is invalid or the value doesn't exist in this attribute. + */ + ; + + _proto.offsetOf = function offsetOf(name) { + var count = 1; + + var attributeSpaces = this._spacesFor("attribute"); + + count += attributeSpaces.before.length; + + if (name === "namespace" || name === "ns") { + return this.namespace ? count : -1; + } + + if (name === "attributeNS") { + return count; + } + + count += this.namespaceString.length; + + if (this.namespace) { + count += 1; + } + + if (name === "attribute") { + return count; + } + + count += this.stringifyProperty("attribute").length; + count += attributeSpaces.after.length; + + var operatorSpaces = this._spacesFor("operator"); + + count += operatorSpaces.before.length; + var operator = this.stringifyProperty("operator"); + + if (name === "operator") { + return operator ? count : -1; + } + + count += operator.length; + count += operatorSpaces.after.length; + + var valueSpaces = this._spacesFor("value"); + + count += valueSpaces.before.length; + var value = this.stringifyProperty("value"); + + if (name === "value") { + return value ? count : -1; + } + + count += value.length; + count += valueSpaces.after.length; + + var insensitiveSpaces = this._spacesFor("insensitive"); + + count += insensitiveSpaces.before.length; + + if (name === "insensitive") { + return this.insensitive ? count : -1; + } + + return -1; + }; + + _proto.toString = function toString() { + var _this2 = this; + + var selector = [this.rawSpaceBefore, '[']; + selector.push(this._stringFor('qualifiedAttribute', 'attribute')); + + if (this.operator && (this.value || this.value === '')) { + selector.push(this._stringFor('operator')); + selector.push(this._stringFor('value')); + selector.push(this._stringFor('insensitiveFlag', 'insensitive', function (attrValue, attrSpaces) { + if (attrValue.length > 0 && !_this2.quoted && attrSpaces.before.length === 0 && !(_this2.spaces.value && _this2.spaces.value.after)) { + attrSpaces.before = " "; + } + + return defaultAttrConcat(attrValue, attrSpaces); + })); + } + + selector.push(']'); + selector.push(this.rawSpaceAfter); + return selector.join(''); + }; + + _createClass(Attribute, [{ + key: "quoted", + get: function get() { + var qm = this.quoteMark; + return qm === "'" || qm === '"'; + }, + set: function set(value) { + warnOfDeprecatedQuotedAssignment(); + } + /** + * returns a single (`'`) or double (`"`) quote character if the value is quoted. + * returns `null` if the value is not quoted. + * returns `undefined` if the quotation state is unknown (this can happen when + * the attribute is constructed without specifying a quote mark.) + */ + + }, { + key: "quoteMark", + get: function get() { + return this._quoteMark; + } + /** + * Set the quote mark to be used by this attribute's value. + * If the quote mark changes, the raw (escaped) value at `attr.raws.value` of the attribute + * value is updated accordingly. + * + * @param {"'" | '"' | null} quoteMark The quote mark or `null` if the value should be unquoted. + */ + , + set: function set(quoteMark) { + if (!this._constructed) { + this._quoteMark = quoteMark; + return; + } + + if (this._quoteMark !== quoteMark) { + this._quoteMark = quoteMark; + + this._syncRawValue(); + } + } + }, { + key: "qualifiedAttribute", + get: function get() { + return this.qualifiedName(this.raws.attribute || this.attribute); + } + }, { + key: "insensitiveFlag", + get: function get() { + return this.insensitive ? 'i' : ''; + } + }, { + key: "value", + get: function get() { + return this._value; + } + /** + * Before 3.0, the value had to be set to an escaped value including any wrapped + * quote marks. In 3.0, the semantics of `Attribute.value` changed so that the value + * is unescaped during parsing and any quote marks are removed. + * + * Because the ambiguity of this semantic change, if you set `attr.value = newValue`, + * a deprecation warning is raised when the new value contains any characters that would + * require escaping (including if it contains wrapped quotes). + * + * Instead, you should call `attr.setValue(newValue, opts)` and pass options that describe + * how the new value is quoted. + */ + , + set: function set(v) { + if (this._constructed) { + var _unescapeValue2 = unescapeValue(v), + deprecatedUsage = _unescapeValue2.deprecatedUsage, + unescaped = _unescapeValue2.unescaped, + quoteMark = _unescapeValue2.quoteMark; + + if (deprecatedUsage) { + warnOfDeprecatedValueAssignment(); + } + + if (unescaped === this._value && quoteMark === this._quoteMark) { + return; + } + + this._value = unescaped; + this._quoteMark = quoteMark; + + this._syncRawValue(); + } else { + this._value = v; + } + } + }, { + key: "attribute", + get: function get() { + return this._attribute; + }, + set: function set(name) { + this._handleEscapes("attribute", name); + + this._attribute = name; + } + }]); + + return Attribute; +}(_namespace["default"]); + +exports["default"] = Attribute; +Attribute.NO_QUOTE = null; +Attribute.SINGLE_QUOTE = "'"; +Attribute.DOUBLE_QUOTE = '"'; +var CSSESC_QUOTE_OPTIONS = (_CSSESC_QUOTE_OPTIONS = { + "'": { + quotes: 'single', + wrap: true + }, + '"': { + quotes: 'double', + wrap: true + } +}, _CSSESC_QUOTE_OPTIONS[null] = { + isIdentifier: true +}, _CSSESC_QUOTE_OPTIONS); + +function defaultAttrConcat(attrValue, attrSpaces) { + return "" + attrSpaces.before + attrValue + attrSpaces.after; +} \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/className.js b/node_modules/postcss-selector-parser/dist/selectors/className.js new file mode 100644 index 0000000000000..22409914cf728 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/className.js @@ -0,0 +1,69 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _cssesc = _interopRequireDefault(require("cssesc")); + +var _util = require("../util"); + +var _node = _interopRequireDefault(require("./node")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var ClassName = /*#__PURE__*/function (_Node) { + _inheritsLoose(ClassName, _Node); + + function ClassName(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + _this.type = _types.CLASS; + _this._constructed = true; + return _this; + } + + var _proto = ClassName.prototype; + + _proto.valueToString = function valueToString() { + return '.' + _Node.prototype.valueToString.call(this); + }; + + _createClass(ClassName, [{ + key: "value", + get: function get() { + return this._value; + }, + set: function set(v) { + if (this._constructed) { + var escaped = (0, _cssesc["default"])(v, { + isIdentifier: true + }); + + if (escaped !== v) { + (0, _util.ensureObject)(this, "raws"); + this.raws.value = escaped; + } else if (this.raws) { + delete this.raws.value; + } + } + + this._value = v; + } + }]); + + return ClassName; +}(_node["default"]); + +exports["default"] = ClassName; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/combinator.js b/node_modules/postcss-selector-parser/dist/selectors/combinator.js new file mode 100644 index 0000000000000..271ab4d3b1f44 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/combinator.js @@ -0,0 +1,31 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _node = _interopRequireDefault(require("./node")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Combinator = /*#__PURE__*/function (_Node) { + _inheritsLoose(Combinator, _Node); + + function Combinator(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + _this.type = _types.COMBINATOR; + return _this; + } + + return Combinator; +}(_node["default"]); + +exports["default"] = Combinator; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/comment.js b/node_modules/postcss-selector-parser/dist/selectors/comment.js new file mode 100644 index 0000000000000..e778094e110c2 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/comment.js @@ -0,0 +1,31 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _node = _interopRequireDefault(require("./node")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Comment = /*#__PURE__*/function (_Node) { + _inheritsLoose(Comment, _Node); + + function Comment(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + _this.type = _types.COMMENT; + return _this; + } + + return Comment; +}(_node["default"]); + +exports["default"] = Comment; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/constructors.js b/node_modules/postcss-selector-parser/dist/selectors/constructors.js new file mode 100644 index 0000000000000..078023eb28f2d --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/constructors.js @@ -0,0 +1,102 @@ +"use strict"; + +exports.__esModule = true; +exports.universal = exports.tag = exports.string = exports.selector = exports.root = exports.pseudo = exports.nesting = exports.id = exports.comment = exports.combinator = exports.className = exports.attribute = void 0; + +var _attribute = _interopRequireDefault(require("./attribute")); + +var _className = _interopRequireDefault(require("./className")); + +var _combinator = _interopRequireDefault(require("./combinator")); + +var _comment = _interopRequireDefault(require("./comment")); + +var _id = _interopRequireDefault(require("./id")); + +var _nesting = _interopRequireDefault(require("./nesting")); + +var _pseudo = _interopRequireDefault(require("./pseudo")); + +var _root = _interopRequireDefault(require("./root")); + +var _selector = _interopRequireDefault(require("./selector")); + +var _string = _interopRequireDefault(require("./string")); + +var _tag = _interopRequireDefault(require("./tag")); + +var _universal = _interopRequireDefault(require("./universal")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +var attribute = function attribute(opts) { + return new _attribute["default"](opts); +}; + +exports.attribute = attribute; + +var className = function className(opts) { + return new _className["default"](opts); +}; + +exports.className = className; + +var combinator = function combinator(opts) { + return new _combinator["default"](opts); +}; + +exports.combinator = combinator; + +var comment = function comment(opts) { + return new _comment["default"](opts); +}; + +exports.comment = comment; + +var id = function id(opts) { + return new _id["default"](opts); +}; + +exports.id = id; + +var nesting = function nesting(opts) { + return new _nesting["default"](opts); +}; + +exports.nesting = nesting; + +var pseudo = function pseudo(opts) { + return new _pseudo["default"](opts); +}; + +exports.pseudo = pseudo; + +var root = function root(opts) { + return new _root["default"](opts); +}; + +exports.root = root; + +var selector = function selector(opts) { + return new _selector["default"](opts); +}; + +exports.selector = selector; + +var string = function string(opts) { + return new _string["default"](opts); +}; + +exports.string = string; + +var tag = function tag(opts) { + return new _tag["default"](opts); +}; + +exports.tag = tag; + +var universal = function universal(opts) { + return new _universal["default"](opts); +}; + +exports.universal = universal; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/container.js b/node_modules/postcss-selector-parser/dist/selectors/container.js new file mode 100644 index 0000000000000..2626fb85bba85 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/container.js @@ -0,0 +1,395 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _node = _interopRequireDefault(require("./node")); + +var types = _interopRequireWildcard(require("./types")); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } it = o[Symbol.iterator](); return it.next.bind(it); } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Container = /*#__PURE__*/function (_Node) { + _inheritsLoose(Container, _Node); + + function Container(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + + if (!_this.nodes) { + _this.nodes = []; + } + + return _this; + } + + var _proto = Container.prototype; + + _proto.append = function append(selector) { + selector.parent = this; + this.nodes.push(selector); + return this; + }; + + _proto.prepend = function prepend(selector) { + selector.parent = this; + this.nodes.unshift(selector); + return this; + }; + + _proto.at = function at(index) { + return this.nodes[index]; + }; + + _proto.index = function index(child) { + if (typeof child === 'number') { + return child; + } + + return this.nodes.indexOf(child); + }; + + _proto.removeChild = function removeChild(child) { + child = this.index(child); + this.at(child).parent = undefined; + this.nodes.splice(child, 1); + var index; + + for (var id in this.indexes) { + index = this.indexes[id]; + + if (index >= child) { + this.indexes[id] = index - 1; + } + } + + return this; + }; + + _proto.removeAll = function removeAll() { + for (var _iterator = _createForOfIteratorHelperLoose(this.nodes), _step; !(_step = _iterator()).done;) { + var node = _step.value; + node.parent = undefined; + } + + this.nodes = []; + return this; + }; + + _proto.empty = function empty() { + return this.removeAll(); + }; + + _proto.insertAfter = function insertAfter(oldNode, newNode) { + newNode.parent = this; + var oldIndex = this.index(oldNode); + this.nodes.splice(oldIndex + 1, 0, newNode); + newNode.parent = this; + var index; + + for (var id in this.indexes) { + index = this.indexes[id]; + + if (oldIndex <= index) { + this.indexes[id] = index + 1; + } + } + + return this; + }; + + _proto.insertBefore = function insertBefore(oldNode, newNode) { + newNode.parent = this; + var oldIndex = this.index(oldNode); + this.nodes.splice(oldIndex, 0, newNode); + newNode.parent = this; + var index; + + for (var id in this.indexes) { + index = this.indexes[id]; + + if (index <= oldIndex) { + this.indexes[id] = index + 1; + } + } + + return this; + }; + + _proto._findChildAtPosition = function _findChildAtPosition(line, col) { + var found = undefined; + this.each(function (node) { + if (node.atPosition) { + var foundChild = node.atPosition(line, col); + + if (foundChild) { + found = foundChild; + return false; + } + } else if (node.isAtPosition(line, col)) { + found = node; + return false; + } + }); + return found; + } + /** + * Return the most specific node at the line and column number given. + * The source location is based on the original parsed location, locations aren't + * updated as selector nodes are mutated. + * + * Note that this location is relative to the location of the first character + * of the selector, and not the location of the selector in the overall document + * when used in conjunction with postcss. + * + * If not found, returns undefined. + * @param {number} line The line number of the node to find. (1-based index) + * @param {number} col The column number of the node to find. (1-based index) + */ + ; + + _proto.atPosition = function atPosition(line, col) { + if (this.isAtPosition(line, col)) { + return this._findChildAtPosition(line, col) || this; + } else { + return undefined; + } + }; + + _proto._inferEndPosition = function _inferEndPosition() { + if (this.last && this.last.source && this.last.source.end) { + this.source = this.source || {}; + this.source.end = this.source.end || {}; + Object.assign(this.source.end, this.last.source.end); + } + }; + + _proto.each = function each(callback) { + if (!this.lastEach) { + this.lastEach = 0; + } + + if (!this.indexes) { + this.indexes = {}; + } + + this.lastEach++; + var id = this.lastEach; + this.indexes[id] = 0; + + if (!this.length) { + return undefined; + } + + var index, result; + + while (this.indexes[id] < this.length) { + index = this.indexes[id]; + result = callback(this.at(index), index); + + if (result === false) { + break; + } + + this.indexes[id] += 1; + } + + delete this.indexes[id]; + + if (result === false) { + return false; + } + }; + + _proto.walk = function walk(callback) { + return this.each(function (node, i) { + var result = callback(node, i); + + if (result !== false && node.length) { + result = node.walk(callback); + } + + if (result === false) { + return false; + } + }); + }; + + _proto.walkAttributes = function walkAttributes(callback) { + var _this2 = this; + + return this.walk(function (selector) { + if (selector.type === types.ATTRIBUTE) { + return callback.call(_this2, selector); + } + }); + }; + + _proto.walkClasses = function walkClasses(callback) { + var _this3 = this; + + return this.walk(function (selector) { + if (selector.type === types.CLASS) { + return callback.call(_this3, selector); + } + }); + }; + + _proto.walkCombinators = function walkCombinators(callback) { + var _this4 = this; + + return this.walk(function (selector) { + if (selector.type === types.COMBINATOR) { + return callback.call(_this4, selector); + } + }); + }; + + _proto.walkComments = function walkComments(callback) { + var _this5 = this; + + return this.walk(function (selector) { + if (selector.type === types.COMMENT) { + return callback.call(_this5, selector); + } + }); + }; + + _proto.walkIds = function walkIds(callback) { + var _this6 = this; + + return this.walk(function (selector) { + if (selector.type === types.ID) { + return callback.call(_this6, selector); + } + }); + }; + + _proto.walkNesting = function walkNesting(callback) { + var _this7 = this; + + return this.walk(function (selector) { + if (selector.type === types.NESTING) { + return callback.call(_this7, selector); + } + }); + }; + + _proto.walkPseudos = function walkPseudos(callback) { + var _this8 = this; + + return this.walk(function (selector) { + if (selector.type === types.PSEUDO) { + return callback.call(_this8, selector); + } + }); + }; + + _proto.walkTags = function walkTags(callback) { + var _this9 = this; + + return this.walk(function (selector) { + if (selector.type === types.TAG) { + return callback.call(_this9, selector); + } + }); + }; + + _proto.walkUniversals = function walkUniversals(callback) { + var _this10 = this; + + return this.walk(function (selector) { + if (selector.type === types.UNIVERSAL) { + return callback.call(_this10, selector); + } + }); + }; + + _proto.split = function split(callback) { + var _this11 = this; + + var current = []; + return this.reduce(function (memo, node, index) { + var split = callback.call(_this11, node); + current.push(node); + + if (split) { + memo.push(current); + current = []; + } else if (index === _this11.length - 1) { + memo.push(current); + } + + return memo; + }, []); + }; + + _proto.map = function map(callback) { + return this.nodes.map(callback); + }; + + _proto.reduce = function reduce(callback, memo) { + return this.nodes.reduce(callback, memo); + }; + + _proto.every = function every(callback) { + return this.nodes.every(callback); + }; + + _proto.some = function some(callback) { + return this.nodes.some(callback); + }; + + _proto.filter = function filter(callback) { + return this.nodes.filter(callback); + }; + + _proto.sort = function sort(callback) { + return this.nodes.sort(callback); + }; + + _proto.toString = function toString() { + return this.map(String).join(''); + }; + + _createClass(Container, [{ + key: "first", + get: function get() { + return this.at(0); + } + }, { + key: "last", + get: function get() { + return this.at(this.length - 1); + } + }, { + key: "length", + get: function get() { + return this.nodes.length; + } + }]); + + return Container; +}(_node["default"]); + +exports["default"] = Container; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/guards.js b/node_modules/postcss-selector-parser/dist/selectors/guards.js new file mode 100644 index 0000000000000..c949af57eb1fd --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/guards.js @@ -0,0 +1,64 @@ +"use strict"; + +exports.__esModule = true; +exports.isNode = isNode; +exports.isPseudoElement = isPseudoElement; +exports.isPseudoClass = isPseudoClass; +exports.isContainer = isContainer; +exports.isNamespace = isNamespace; +exports.isUniversal = exports.isTag = exports.isString = exports.isSelector = exports.isRoot = exports.isPseudo = exports.isNesting = exports.isIdentifier = exports.isComment = exports.isCombinator = exports.isClassName = exports.isAttribute = void 0; + +var _types = require("./types"); + +var _IS_TYPE; + +var IS_TYPE = (_IS_TYPE = {}, _IS_TYPE[_types.ATTRIBUTE] = true, _IS_TYPE[_types.CLASS] = true, _IS_TYPE[_types.COMBINATOR] = true, _IS_TYPE[_types.COMMENT] = true, _IS_TYPE[_types.ID] = true, _IS_TYPE[_types.NESTING] = true, _IS_TYPE[_types.PSEUDO] = true, _IS_TYPE[_types.ROOT] = true, _IS_TYPE[_types.SELECTOR] = true, _IS_TYPE[_types.STRING] = true, _IS_TYPE[_types.TAG] = true, _IS_TYPE[_types.UNIVERSAL] = true, _IS_TYPE); + +function isNode(node) { + return typeof node === "object" && IS_TYPE[node.type]; +} + +function isNodeType(type, node) { + return isNode(node) && node.type === type; +} + +var isAttribute = isNodeType.bind(null, _types.ATTRIBUTE); +exports.isAttribute = isAttribute; +var isClassName = isNodeType.bind(null, _types.CLASS); +exports.isClassName = isClassName; +var isCombinator = isNodeType.bind(null, _types.COMBINATOR); +exports.isCombinator = isCombinator; +var isComment = isNodeType.bind(null, _types.COMMENT); +exports.isComment = isComment; +var isIdentifier = isNodeType.bind(null, _types.ID); +exports.isIdentifier = isIdentifier; +var isNesting = isNodeType.bind(null, _types.NESTING); +exports.isNesting = isNesting; +var isPseudo = isNodeType.bind(null, _types.PSEUDO); +exports.isPseudo = isPseudo; +var isRoot = isNodeType.bind(null, _types.ROOT); +exports.isRoot = isRoot; +var isSelector = isNodeType.bind(null, _types.SELECTOR); +exports.isSelector = isSelector; +var isString = isNodeType.bind(null, _types.STRING); +exports.isString = isString; +var isTag = isNodeType.bind(null, _types.TAG); +exports.isTag = isTag; +var isUniversal = isNodeType.bind(null, _types.UNIVERSAL); +exports.isUniversal = isUniversal; + +function isPseudoElement(node) { + return isPseudo(node) && node.value && (node.value.startsWith("::") || node.value.toLowerCase() === ":before" || node.value.toLowerCase() === ":after" || node.value.toLowerCase() === ":first-letter" || node.value.toLowerCase() === ":first-line"); +} + +function isPseudoClass(node) { + return isPseudo(node) && !isPseudoElement(node); +} + +function isContainer(node) { + return !!(isNode(node) && node.walk); +} + +function isNamespace(node) { + return isAttribute(node) || isTag(node); +} \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/id.js b/node_modules/postcss-selector-parser/dist/selectors/id.js new file mode 100644 index 0000000000000..4e83147e3c4ef --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/id.js @@ -0,0 +1,37 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _node = _interopRequireDefault(require("./node")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var ID = /*#__PURE__*/function (_Node) { + _inheritsLoose(ID, _Node); + + function ID(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + _this.type = _types.ID; + return _this; + } + + var _proto = ID.prototype; + + _proto.valueToString = function valueToString() { + return '#' + _Node.prototype.valueToString.call(this); + }; + + return ID; +}(_node["default"]); + +exports["default"] = ID; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/index.js b/node_modules/postcss-selector-parser/dist/selectors/index.js new file mode 100644 index 0000000000000..1fe9b138a5a26 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/index.js @@ -0,0 +1,27 @@ +"use strict"; + +exports.__esModule = true; + +var _types = require("./types"); + +Object.keys(_types).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _types[key]) return; + exports[key] = _types[key]; +}); + +var _constructors = require("./constructors"); + +Object.keys(_constructors).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _constructors[key]) return; + exports[key] = _constructors[key]; +}); + +var _guards = require("./guards"); + +Object.keys(_guards).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _guards[key]) return; + exports[key] = _guards[key]; +}); \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/namespace.js b/node_modules/postcss-selector-parser/dist/selectors/namespace.js new file mode 100644 index 0000000000000..fd6c729e16661 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/namespace.js @@ -0,0 +1,101 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _cssesc = _interopRequireDefault(require("cssesc")); + +var _util = require("../util"); + +var _node = _interopRequireDefault(require("./node")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Namespace = /*#__PURE__*/function (_Node) { + _inheritsLoose(Namespace, _Node); + + function Namespace() { + return _Node.apply(this, arguments) || this; + } + + var _proto = Namespace.prototype; + + _proto.qualifiedName = function qualifiedName(value) { + if (this.namespace) { + return this.namespaceString + "|" + value; + } else { + return value; + } + }; + + _proto.valueToString = function valueToString() { + return this.qualifiedName(_Node.prototype.valueToString.call(this)); + }; + + _createClass(Namespace, [{ + key: "namespace", + get: function get() { + return this._namespace; + }, + set: function set(namespace) { + if (namespace === true || namespace === "*" || namespace === "&") { + this._namespace = namespace; + + if (this.raws) { + delete this.raws.namespace; + } + + return; + } + + var escaped = (0, _cssesc["default"])(namespace, { + isIdentifier: true + }); + this._namespace = namespace; + + if (escaped !== namespace) { + (0, _util.ensureObject)(this, "raws"); + this.raws.namespace = escaped; + } else if (this.raws) { + delete this.raws.namespace; + } + } + }, { + key: "ns", + get: function get() { + return this._namespace; + }, + set: function set(namespace) { + this.namespace = namespace; + } + }, { + key: "namespaceString", + get: function get() { + if (this.namespace) { + var ns = this.stringifyProperty("namespace"); + + if (ns === true) { + return ''; + } else { + return ns; + } + } else { + return ''; + } + } + }]); + + return Namespace; +}(_node["default"]); + +exports["default"] = Namespace; +; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/nesting.js b/node_modules/postcss-selector-parser/dist/selectors/nesting.js new file mode 100644 index 0000000000000..3288c78f2dddb --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/nesting.js @@ -0,0 +1,32 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _node = _interopRequireDefault(require("./node")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Nesting = /*#__PURE__*/function (_Node) { + _inheritsLoose(Nesting, _Node); + + function Nesting(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + _this.type = _types.NESTING; + _this.value = '&'; + return _this; + } + + return Nesting; +}(_node["default"]); + +exports["default"] = Nesting; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/node.js b/node_modules/postcss-selector-parser/dist/selectors/node.js new file mode 100644 index 0000000000000..e8eca11c70ecf --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/node.js @@ -0,0 +1,239 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _util = require("../util"); + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +var cloneNode = function cloneNode(obj, parent) { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + var cloned = new obj.constructor(); + + for (var i in obj) { + if (!obj.hasOwnProperty(i)) { + continue; + } + + var value = obj[i]; + var type = typeof value; + + if (i === 'parent' && type === 'object') { + if (parent) { + cloned[i] = parent; + } + } else if (value instanceof Array) { + cloned[i] = value.map(function (j) { + return cloneNode(j, cloned); + }); + } else { + cloned[i] = cloneNode(value, cloned); + } + } + + return cloned; +}; + +var Node = /*#__PURE__*/function () { + function Node(opts) { + if (opts === void 0) { + opts = {}; + } + + Object.assign(this, opts); + this.spaces = this.spaces || {}; + this.spaces.before = this.spaces.before || ''; + this.spaces.after = this.spaces.after || ''; + } + + var _proto = Node.prototype; + + _proto.remove = function remove() { + if (this.parent) { + this.parent.removeChild(this); + } + + this.parent = undefined; + return this; + }; + + _proto.replaceWith = function replaceWith() { + if (this.parent) { + for (var index in arguments) { + this.parent.insertBefore(this, arguments[index]); + } + + this.remove(); + } + + return this; + }; + + _proto.next = function next() { + return this.parent.at(this.parent.index(this) + 1); + }; + + _proto.prev = function prev() { + return this.parent.at(this.parent.index(this) - 1); + }; + + _proto.clone = function clone(overrides) { + if (overrides === void 0) { + overrides = {}; + } + + var cloned = cloneNode(this); + + for (var name in overrides) { + cloned[name] = overrides[name]; + } + + return cloned; + } + /** + * Some non-standard syntax doesn't follow normal escaping rules for css. + * This allows non standard syntax to be appended to an existing property + * by specifying the escaped value. By specifying the escaped value, + * illegal characters are allowed to be directly inserted into css output. + * @param {string} name the property to set + * @param {any} value the unescaped value of the property + * @param {string} valueEscaped optional. the escaped value of the property. + */ + ; + + _proto.appendToPropertyAndEscape = function appendToPropertyAndEscape(name, value, valueEscaped) { + if (!this.raws) { + this.raws = {}; + } + + var originalValue = this[name]; + var originalEscaped = this.raws[name]; + this[name] = originalValue + value; // this may trigger a setter that updates raws, so it has to be set first. + + if (originalEscaped || valueEscaped !== value) { + this.raws[name] = (originalEscaped || originalValue) + valueEscaped; + } else { + delete this.raws[name]; // delete any escaped value that was created by the setter. + } + } + /** + * Some non-standard syntax doesn't follow normal escaping rules for css. + * This allows the escaped value to be specified directly, allowing illegal + * characters to be directly inserted into css output. + * @param {string} name the property to set + * @param {any} value the unescaped value of the property + * @param {string} valueEscaped the escaped value of the property. + */ + ; + + _proto.setPropertyAndEscape = function setPropertyAndEscape(name, value, valueEscaped) { + if (!this.raws) { + this.raws = {}; + } + + this[name] = value; // this may trigger a setter that updates raws, so it has to be set first. + + this.raws[name] = valueEscaped; + } + /** + * When you want a value to passed through to CSS directly. This method + * deletes the corresponding raw value causing the stringifier to fallback + * to the unescaped value. + * @param {string} name the property to set. + * @param {any} value The value that is both escaped and unescaped. + */ + ; + + _proto.setPropertyWithoutEscape = function setPropertyWithoutEscape(name, value) { + this[name] = value; // this may trigger a setter that updates raws, so it has to be set first. + + if (this.raws) { + delete this.raws[name]; + } + } + /** + * + * @param {number} line The number (starting with 1) + * @param {number} column The column number (starting with 1) + */ + ; + + _proto.isAtPosition = function isAtPosition(line, column) { + if (this.source && this.source.start && this.source.end) { + if (this.source.start.line > line) { + return false; + } + + if (this.source.end.line < line) { + return false; + } + + if (this.source.start.line === line && this.source.start.column > column) { + return false; + } + + if (this.source.end.line === line && this.source.end.column < column) { + return false; + } + + return true; + } + + return undefined; + }; + + _proto.stringifyProperty = function stringifyProperty(name) { + return this.raws && this.raws[name] || this[name]; + }; + + _proto.valueToString = function valueToString() { + return String(this.stringifyProperty("value")); + }; + + _proto.toString = function toString() { + return [this.rawSpaceBefore, this.valueToString(), this.rawSpaceAfter].join(''); + }; + + _createClass(Node, [{ + key: "rawSpaceBefore", + get: function get() { + var rawSpace = this.raws && this.raws.spaces && this.raws.spaces.before; + + if (rawSpace === undefined) { + rawSpace = this.spaces && this.spaces.before; + } + + return rawSpace || ""; + }, + set: function set(raw) { + (0, _util.ensureObject)(this, "raws", "spaces"); + this.raws.spaces.before = raw; + } + }, { + key: "rawSpaceAfter", + get: function get() { + var rawSpace = this.raws && this.raws.spaces && this.raws.spaces.after; + + if (rawSpace === undefined) { + rawSpace = this.spaces.after; + } + + return rawSpace || ""; + }, + set: function set(raw) { + (0, _util.ensureObject)(this, "raws", "spaces"); + this.raws.spaces.after = raw; + } + }]); + + return Node; +}(); + +exports["default"] = Node; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/pseudo.js b/node_modules/postcss-selector-parser/dist/selectors/pseudo.js new file mode 100644 index 0000000000000..a0e7bca170a76 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/pseudo.js @@ -0,0 +1,38 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _container = _interopRequireDefault(require("./container")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Pseudo = /*#__PURE__*/function (_Container) { + _inheritsLoose(Pseudo, _Container); + + function Pseudo(opts) { + var _this; + + _this = _Container.call(this, opts) || this; + _this.type = _types.PSEUDO; + return _this; + } + + var _proto = Pseudo.prototype; + + _proto.toString = function toString() { + var params = this.length ? '(' + this.map(String).join(',') + ')' : ''; + return [this.rawSpaceBefore, this.stringifyProperty("value"), params, this.rawSpaceAfter].join(''); + }; + + return Pseudo; +}(_container["default"]); + +exports["default"] = Pseudo; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/root.js b/node_modules/postcss-selector-parser/dist/selectors/root.js new file mode 100644 index 0000000000000..be5c2ccb2dac8 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/root.js @@ -0,0 +1,60 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _container = _interopRequireDefault(require("./container")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Root = /*#__PURE__*/function (_Container) { + _inheritsLoose(Root, _Container); + + function Root(opts) { + var _this; + + _this = _Container.call(this, opts) || this; + _this.type = _types.ROOT; + return _this; + } + + var _proto = Root.prototype; + + _proto.toString = function toString() { + var str = this.reduce(function (memo, selector) { + memo.push(String(selector)); + return memo; + }, []).join(','); + return this.trailingComma ? str + ',' : str; + }; + + _proto.error = function error(message, options) { + if (this._error) { + return this._error(message, options); + } else { + return new Error(message); + } + }; + + _createClass(Root, [{ + key: "errorGenerator", + set: function set(handler) { + this._error = handler; + } + }]); + + return Root; +}(_container["default"]); + +exports["default"] = Root; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/selector.js b/node_modules/postcss-selector-parser/dist/selectors/selector.js new file mode 100644 index 0000000000000..699eeb6e546f9 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/selector.js @@ -0,0 +1,31 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _container = _interopRequireDefault(require("./container")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Selector = /*#__PURE__*/function (_Container) { + _inheritsLoose(Selector, _Container); + + function Selector(opts) { + var _this; + + _this = _Container.call(this, opts) || this; + _this.type = _types.SELECTOR; + return _this; + } + + return Selector; +}(_container["default"]); + +exports["default"] = Selector; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/string.js b/node_modules/postcss-selector-parser/dist/selectors/string.js new file mode 100644 index 0000000000000..e61df30c74a0a --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/string.js @@ -0,0 +1,31 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _node = _interopRequireDefault(require("./node")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var String = /*#__PURE__*/function (_Node) { + _inheritsLoose(String, _Node); + + function String(opts) { + var _this; + + _this = _Node.call(this, opts) || this; + _this.type = _types.STRING; + return _this; + } + + return String; +}(_node["default"]); + +exports["default"] = String; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/tag.js b/node_modules/postcss-selector-parser/dist/selectors/tag.js new file mode 100644 index 0000000000000..e298db15fafd1 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/tag.js @@ -0,0 +1,31 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _namespace = _interopRequireDefault(require("./namespace")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Tag = /*#__PURE__*/function (_Namespace) { + _inheritsLoose(Tag, _Namespace); + + function Tag(opts) { + var _this; + + _this = _Namespace.call(this, opts) || this; + _this.type = _types.TAG; + return _this; + } + + return Tag; +}(_namespace["default"]); + +exports["default"] = Tag; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/types.js b/node_modules/postcss-selector-parser/dist/selectors/types.js new file mode 100644 index 0000000000000..ab897b8ce5c12 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/types.js @@ -0,0 +1,28 @@ +"use strict"; + +exports.__esModule = true; +exports.UNIVERSAL = exports.ATTRIBUTE = exports.CLASS = exports.COMBINATOR = exports.COMMENT = exports.ID = exports.NESTING = exports.PSEUDO = exports.ROOT = exports.SELECTOR = exports.STRING = exports.TAG = void 0; +var TAG = 'tag'; +exports.TAG = TAG; +var STRING = 'string'; +exports.STRING = STRING; +var SELECTOR = 'selector'; +exports.SELECTOR = SELECTOR; +var ROOT = 'root'; +exports.ROOT = ROOT; +var PSEUDO = 'pseudo'; +exports.PSEUDO = PSEUDO; +var NESTING = 'nesting'; +exports.NESTING = NESTING; +var ID = 'id'; +exports.ID = ID; +var COMMENT = 'comment'; +exports.COMMENT = COMMENT; +var COMBINATOR = 'combinator'; +exports.COMBINATOR = COMBINATOR; +var CLASS = 'class'; +exports.CLASS = CLASS; +var ATTRIBUTE = 'attribute'; +exports.ATTRIBUTE = ATTRIBUTE; +var UNIVERSAL = 'universal'; +exports.UNIVERSAL = UNIVERSAL; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/selectors/universal.js b/node_modules/postcss-selector-parser/dist/selectors/universal.js new file mode 100644 index 0000000000000..cf25473d1c3d4 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/selectors/universal.js @@ -0,0 +1,32 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = void 0; + +var _namespace = _interopRequireDefault(require("./namespace")); + +var _types = require("./types"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } + +function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +var Universal = /*#__PURE__*/function (_Namespace) { + _inheritsLoose(Universal, _Namespace); + + function Universal(opts) { + var _this; + + _this = _Namespace.call(this, opts) || this; + _this.type = _types.UNIVERSAL; + _this.value = '*'; + return _this; + } + + return Universal; +}(_namespace["default"]); + +exports["default"] = Universal; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/sortAscending.js b/node_modules/postcss-selector-parser/dist/sortAscending.js new file mode 100644 index 0000000000000..3ef56acc570c8 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/sortAscending.js @@ -0,0 +1,13 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = sortAscending; + +function sortAscending(list) { + return list.sort(function (a, b) { + return a - b; + }); +} + +; +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/tokenTypes.js b/node_modules/postcss-selector-parser/dist/tokenTypes.js new file mode 100644 index 0000000000000..48314b93e0058 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/tokenTypes.js @@ -0,0 +1,95 @@ +"use strict"; + +exports.__esModule = true; +exports.combinator = exports.word = exports.comment = exports.str = exports.tab = exports.newline = exports.feed = exports.cr = exports.backslash = exports.bang = exports.slash = exports.doubleQuote = exports.singleQuote = exports.space = exports.greaterThan = exports.pipe = exports.equals = exports.plus = exports.caret = exports.tilde = exports.dollar = exports.closeSquare = exports.openSquare = exports.closeParenthesis = exports.openParenthesis = exports.semicolon = exports.colon = exports.comma = exports.at = exports.asterisk = exports.ampersand = void 0; +var ampersand = 38; // `&`.charCodeAt(0); + +exports.ampersand = ampersand; +var asterisk = 42; // `*`.charCodeAt(0); + +exports.asterisk = asterisk; +var at = 64; // `@`.charCodeAt(0); + +exports.at = at; +var comma = 44; // `,`.charCodeAt(0); + +exports.comma = comma; +var colon = 58; // `:`.charCodeAt(0); + +exports.colon = colon; +var semicolon = 59; // `;`.charCodeAt(0); + +exports.semicolon = semicolon; +var openParenthesis = 40; // `(`.charCodeAt(0); + +exports.openParenthesis = openParenthesis; +var closeParenthesis = 41; // `)`.charCodeAt(0); + +exports.closeParenthesis = closeParenthesis; +var openSquare = 91; // `[`.charCodeAt(0); + +exports.openSquare = openSquare; +var closeSquare = 93; // `]`.charCodeAt(0); + +exports.closeSquare = closeSquare; +var dollar = 36; // `$`.charCodeAt(0); + +exports.dollar = dollar; +var tilde = 126; // `~`.charCodeAt(0); + +exports.tilde = tilde; +var caret = 94; // `^`.charCodeAt(0); + +exports.caret = caret; +var plus = 43; // `+`.charCodeAt(0); + +exports.plus = plus; +var equals = 61; // `=`.charCodeAt(0); + +exports.equals = equals; +var pipe = 124; // `|`.charCodeAt(0); + +exports.pipe = pipe; +var greaterThan = 62; // `>`.charCodeAt(0); + +exports.greaterThan = greaterThan; +var space = 32; // ` `.charCodeAt(0); + +exports.space = space; +var singleQuote = 39; // `'`.charCodeAt(0); + +exports.singleQuote = singleQuote; +var doubleQuote = 34; // `"`.charCodeAt(0); + +exports.doubleQuote = doubleQuote; +var slash = 47; // `/`.charCodeAt(0); + +exports.slash = slash; +var bang = 33; // `!`.charCodeAt(0); + +exports.bang = bang; +var backslash = 92; // '\\'.charCodeAt(0); + +exports.backslash = backslash; +var cr = 13; // '\r'.charCodeAt(0); + +exports.cr = cr; +var feed = 12; // '\f'.charCodeAt(0); + +exports.feed = feed; +var newline = 10; // '\n'.charCodeAt(0); + +exports.newline = newline; +var tab = 9; // '\t'.charCodeAt(0); +// Expose aliases primarily for readability. + +exports.tab = tab; +var str = singleQuote; // No good single character representation! + +exports.str = str; +var comment = -1; +exports.comment = comment; +var word = -2; +exports.word = word; +var combinator = -3; +exports.combinator = combinator; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/tokenize.js b/node_modules/postcss-selector-parser/dist/tokenize.js new file mode 100644 index 0000000000000..bee9fee632e84 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/tokenize.js @@ -0,0 +1,271 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = tokenize; +exports.FIELDS = void 0; + +var t = _interopRequireWildcard(require("./tokenTypes")); + +var _unescapable, _wordDelimiters; + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +var unescapable = (_unescapable = {}, _unescapable[t.tab] = true, _unescapable[t.newline] = true, _unescapable[t.cr] = true, _unescapable[t.feed] = true, _unescapable); +var wordDelimiters = (_wordDelimiters = {}, _wordDelimiters[t.space] = true, _wordDelimiters[t.tab] = true, _wordDelimiters[t.newline] = true, _wordDelimiters[t.cr] = true, _wordDelimiters[t.feed] = true, _wordDelimiters[t.ampersand] = true, _wordDelimiters[t.asterisk] = true, _wordDelimiters[t.bang] = true, _wordDelimiters[t.comma] = true, _wordDelimiters[t.colon] = true, _wordDelimiters[t.semicolon] = true, _wordDelimiters[t.openParenthesis] = true, _wordDelimiters[t.closeParenthesis] = true, _wordDelimiters[t.openSquare] = true, _wordDelimiters[t.closeSquare] = true, _wordDelimiters[t.singleQuote] = true, _wordDelimiters[t.doubleQuote] = true, _wordDelimiters[t.plus] = true, _wordDelimiters[t.pipe] = true, _wordDelimiters[t.tilde] = true, _wordDelimiters[t.greaterThan] = true, _wordDelimiters[t.equals] = true, _wordDelimiters[t.dollar] = true, _wordDelimiters[t.caret] = true, _wordDelimiters[t.slash] = true, _wordDelimiters); +var hex = {}; +var hexChars = "0123456789abcdefABCDEF"; + +for (var i = 0; i < hexChars.length; i++) { + hex[hexChars.charCodeAt(i)] = true; +} +/** + * Returns the last index of the bar css word + * @param {string} css The string in which the word begins + * @param {number} start The index into the string where word's first letter occurs + */ + + +function consumeWord(css, start) { + var next = start; + var code; + + do { + code = css.charCodeAt(next); + + if (wordDelimiters[code]) { + return next - 1; + } else if (code === t.backslash) { + next = consumeEscape(css, next) + 1; + } else { + // All other characters are part of the word + next++; + } + } while (next < css.length); + + return next - 1; +} +/** + * Returns the last index of the escape sequence + * @param {string} css The string in which the sequence begins + * @param {number} start The index into the string where escape character (`\`) occurs. + */ + + +function consumeEscape(css, start) { + var next = start; + var code = css.charCodeAt(next + 1); + + if (unescapable[code]) {// just consume the escape char + } else if (hex[code]) { + var hexDigits = 0; // consume up to 6 hex chars + + do { + next++; + hexDigits++; + code = css.charCodeAt(next + 1); + } while (hex[code] && hexDigits < 6); // if fewer than 6 hex chars, a trailing space ends the escape + + + if (hexDigits < 6 && code === t.space) { + next++; + } + } else { + // the next char is part of the current word + next++; + } + + return next; +} + +var FIELDS = { + TYPE: 0, + START_LINE: 1, + START_COL: 2, + END_LINE: 3, + END_COL: 4, + START_POS: 5, + END_POS: 6 +}; +exports.FIELDS = FIELDS; + +function tokenize(input) { + var tokens = []; + var css = input.css.valueOf(); + var _css = css, + length = _css.length; + var offset = -1; + var line = 1; + var start = 0; + var end = 0; + var code, content, endColumn, endLine, escaped, escapePos, last, lines, next, nextLine, nextOffset, quote, tokenType; + + function unclosed(what, fix) { + if (input.safe) { + // fyi: this is never set to true. + css += fix; + next = css.length - 1; + } else { + throw input.error('Unclosed ' + what, line, start - offset, start); + } + } + + while (start < length) { + code = css.charCodeAt(start); + + if (code === t.newline) { + offset = start; + line += 1; + } + + switch (code) { + case t.space: + case t.tab: + case t.newline: + case t.cr: + case t.feed: + next = start; + + do { + next += 1; + code = css.charCodeAt(next); + + if (code === t.newline) { + offset = next; + line += 1; + } + } while (code === t.space || code === t.newline || code === t.tab || code === t.cr || code === t.feed); + + tokenType = t.space; + endLine = line; + endColumn = next - offset - 1; + end = next; + break; + + case t.plus: + case t.greaterThan: + case t.tilde: + case t.pipe: + next = start; + + do { + next += 1; + code = css.charCodeAt(next); + } while (code === t.plus || code === t.greaterThan || code === t.tilde || code === t.pipe); + + tokenType = t.combinator; + endLine = line; + endColumn = start - offset; + end = next; + break; + // Consume these characters as single tokens. + + case t.asterisk: + case t.ampersand: + case t.bang: + case t.comma: + case t.equals: + case t.dollar: + case t.caret: + case t.openSquare: + case t.closeSquare: + case t.colon: + case t.semicolon: + case t.openParenthesis: + case t.closeParenthesis: + next = start; + tokenType = code; + endLine = line; + endColumn = start - offset; + end = next + 1; + break; + + case t.singleQuote: + case t.doubleQuote: + quote = code === t.singleQuote ? "'" : '"'; + next = start; + + do { + escaped = false; + next = css.indexOf(quote, next + 1); + + if (next === -1) { + unclosed('quote', quote); + } + + escapePos = next; + + while (css.charCodeAt(escapePos - 1) === t.backslash) { + escapePos -= 1; + escaped = !escaped; + } + } while (escaped); + + tokenType = t.str; + endLine = line; + endColumn = start - offset; + end = next + 1; + break; + + default: + if (code === t.slash && css.charCodeAt(start + 1) === t.asterisk) { + next = css.indexOf('*/', start + 2) + 1; + + if (next === 0) { + unclosed('comment', '*/'); + } + + content = css.slice(start, next + 1); + lines = content.split('\n'); + last = lines.length - 1; + + if (last > 0) { + nextLine = line + last; + nextOffset = next - lines[last].length; + } else { + nextLine = line; + nextOffset = offset; + } + + tokenType = t.comment; + line = nextLine; + endLine = nextLine; + endColumn = next - nextOffset; + } else if (code === t.slash) { + next = start; + tokenType = code; + endLine = line; + endColumn = start - offset; + end = next + 1; + } else { + next = consumeWord(css, start); + tokenType = t.word; + endLine = line; + endColumn = next - offset; + } + + end = next + 1; + break; + } // Ensure that the token structure remains consistent + + + tokens.push([tokenType, // [0] Token type + line, // [1] Starting line + start - offset, // [2] Starting column + endLine, // [3] Ending line + endColumn, // [4] Ending column + start, // [5] Start position / Source index + end // [6] End position + ]); // Reset offset for the next token + + if (nextOffset) { + offset = nextOffset; + nextOffset = null; + } + + start = end; + } + + return tokens; +} \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/util/ensureObject.js b/node_modules/postcss-selector-parser/dist/util/ensureObject.js new file mode 100644 index 0000000000000..3472e07522840 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/util/ensureObject.js @@ -0,0 +1,22 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = ensureObject; + +function ensureObject(obj) { + for (var _len = arguments.length, props = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + props[_key - 1] = arguments[_key]; + } + + while (props.length > 0) { + var prop = props.shift(); + + if (!obj[prop]) { + obj[prop] = {}; + } + + obj = obj[prop]; + } +} + +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/util/getProp.js b/node_modules/postcss-selector-parser/dist/util/getProp.js new file mode 100644 index 0000000000000..53e07c90253eb --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/util/getProp.js @@ -0,0 +1,24 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = getProp; + +function getProp(obj) { + for (var _len = arguments.length, props = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + props[_key - 1] = arguments[_key]; + } + + while (props.length > 0) { + var prop = props.shift(); + + if (!obj[prop]) { + return undefined; + } + + obj = obj[prop]; + } + + return obj; +} + +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/util/index.js b/node_modules/postcss-selector-parser/dist/util/index.js new file mode 100644 index 0000000000000..043fda8c64b9a --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/util/index.js @@ -0,0 +1,22 @@ +"use strict"; + +exports.__esModule = true; +exports.stripComments = exports.ensureObject = exports.getProp = exports.unesc = void 0; + +var _unesc = _interopRequireDefault(require("./unesc")); + +exports.unesc = _unesc["default"]; + +var _getProp = _interopRequireDefault(require("./getProp")); + +exports.getProp = _getProp["default"]; + +var _ensureObject = _interopRequireDefault(require("./ensureObject")); + +exports.ensureObject = _ensureObject["default"]; + +var _stripComments = _interopRequireDefault(require("./stripComments")); + +exports.stripComments = _stripComments["default"]; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/util/stripComments.js b/node_modules/postcss-selector-parser/dist/util/stripComments.js new file mode 100644 index 0000000000000..c74f1fecdcc64 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/util/stripComments.js @@ -0,0 +1,27 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = stripComments; + +function stripComments(str) { + var s = ""; + var commentStart = str.indexOf("/*"); + var lastEnd = 0; + + while (commentStart >= 0) { + s = s + str.slice(lastEnd, commentStart); + var commentEnd = str.indexOf("*/", commentStart + 2); + + if (commentEnd < 0) { + return s; + } + + lastEnd = commentEnd + 2; + commentStart = str.indexOf("/*", lastEnd); + } + + s = s + str.slice(lastEnd); + return s; +} + +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/dist/util/unesc.js b/node_modules/postcss-selector-parser/dist/util/unesc.js new file mode 100644 index 0000000000000..3136e7e008420 --- /dev/null +++ b/node_modules/postcss-selector-parser/dist/util/unesc.js @@ -0,0 +1,93 @@ +"use strict"; + +exports.__esModule = true; +exports["default"] = unesc; + +// Many thanks for this post which made this migration much easier. +// https://mathiasbynens.be/notes/css-escapes + +/** + * + * @param {string} str + * @returns {[string, number]|undefined} + */ +function gobbleHex(str) { + var lower = str.toLowerCase(); + var hex = ''; + var spaceTerminated = false; + + for (var i = 0; i < 6 && lower[i] !== undefined; i++) { + var code = lower.charCodeAt(i); // check to see if we are dealing with a valid hex char [a-f|0-9] + + var valid = code >= 97 && code <= 102 || code >= 48 && code <= 57; // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point + + spaceTerminated = code === 32; + + if (!valid) { + break; + } + + hex += lower[i]; + } + + if (hex.length === 0) { + return undefined; + } + + var codePoint = parseInt(hex, 16); + var isSurrogate = codePoint >= 0xD800 && codePoint <= 0xDFFF; // Add special case for + // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point" + // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point + + if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10FFFF) { + return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)]; + } + + return [String.fromCodePoint(codePoint), hex.length + (spaceTerminated ? 1 : 0)]; +} + +var CONTAINS_ESCAPE = /\\/; + +function unesc(str) { + var needToProcess = CONTAINS_ESCAPE.test(str); + + if (!needToProcess) { + return str; + } + + var ret = ""; + + for (var i = 0; i < str.length; i++) { + if (str[i] === "\\") { + var gobbled = gobbleHex(str.slice(i + 1, i + 7)); + + if (gobbled !== undefined) { + ret += gobbled[0]; + i += gobbled[1]; + continue; + } // Retain a pair of \\ if double escaped `\\\\` + // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e + + + if (str[i + 1] === "\\") { + ret += "\\"; + i++; + continue; + } // if \\ is at the end of the string retain it + // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb + + + if (str.length === i + 1) { + ret += str[i]; + } + + continue; + } + + ret += str[i]; + } + + return ret; +} + +module.exports = exports.default; \ No newline at end of file diff --git a/node_modules/postcss-selector-parser/package.json b/node_modules/postcss-selector-parser/package.json new file mode 100644 index 0000000000000..a6f33589ba051 --- /dev/null +++ b/node_modules/postcss-selector-parser/package.json @@ -0,0 +1,78 @@ +{ + "name": "postcss-selector-parser", + "version": "6.0.10", + "devDependencies": { + "@babel/cli": "^7.11.6", + "@babel/core": "^7.11.6", + "@babel/eslint-parser": "^7.11.5", + "@babel/eslint-plugin": "^7.11.5", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/preset-env": "^7.11.5", + "@babel/register": "^7.11.5", + "ava": "^3.12.1", + "babel-plugin-add-module-exports": "^1.0.4", + "coveralls": "^3.1.0", + "del-cli": "^3.0.1", + "eslint": "^7.9.0", + "eslint-plugin-import": "^2.22.0", + "glob": "^7.1.6", + "minimist": "^1.2.5", + "nyc": "^15.1.0", + "postcss": "^8.0.0", + "semver": "^7.3.2", + "typescript": "^4.0.3" + }, + "main": "dist/index.js", + "types": "postcss-selector-parser.d.ts", + "files": [ + "API.md", + "CHANGELOG.md", + "LICENSE-MIT", + "dist", + "postcss-selector-parser.d.ts", + "!**/__tests__" + ], + "scripts": { + "pretest": "eslint src && tsc --noEmit postcss-selector-parser.d.ts", + "prepare": "del-cli dist && BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/", + "lintfix": "eslint --fix src", + "report": "nyc report --reporter=html", + "test": "nyc ava src/__tests__/*.js ", + "testone": "ava" + }, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "license": "MIT", + "engines": { + "node": ">=4" + }, + "homepage": "https://github.com/postcss/postcss-selector-parser", + "contributors": [ + { + "name": "Ben Briggs", + "email": "beneb.info@gmail.com", + "url": "http://beneb.info" + }, + { + "name": "Chris Eppstein", + "email": "chris@eppsteins.net", + "url": "http://twitter.com/chriseppstein" + } + ], + "repository": "postcss/postcss-selector-parser", + "ava": { + "require": [ + "@babel/register" + ], + "concurrency": 5, + "timeout": "25s" + }, + "nyc": { + "exclude": [ + "node_modules", + "**/__tests__" + ] + } +} diff --git a/node_modules/postcss-selector-parser/postcss-selector-parser.d.ts b/node_modules/postcss-selector-parser/postcss-selector-parser.d.ts new file mode 100644 index 0000000000000..89a2c5239edb1 --- /dev/null +++ b/node_modules/postcss-selector-parser/postcss-selector-parser.d.ts @@ -0,0 +1,555 @@ +// Type definitions for postcss-selector-parser 2.2.3 +// Definitions by: Chris Eppstein + +/*~ Note that ES6 modules cannot directly export callable functions. + *~ This file should be imported using the CommonJS-style: + *~ import x = require('someLibrary'); + *~ + *~ Refer to the documentation to understand common + *~ workarounds for this limitation of ES6 modules. + */ + +/*~ This declaration specifies that the function + *~ is the exported object from the file + */ +export = parser; + +// A type that's T but not U. +type Diff = T extends U ? never : T; + +// TODO: Conditional types in TS 1.8 will really clean this up. +declare function parser(): parser.Processor; +declare function parser(processor: parser.AsyncProcessor): parser.Processor; +declare function parser(processor: parser.AsyncProcessor): parser.Processor; +declare function parser(processor: parser.SyncProcessor): parser.Processor; +declare function parser(processor: parser.SyncProcessor): parser.Processor; +declare function parser(processor?: parser.SyncProcessor | parser.AsyncProcessor): parser.Processor; + +/*~ If you want to expose types from your module as well, you can + *~ place them in this block. Often you will want to describe the + *~ shape of the return type of the function; that type should + *~ be declared in here, as this example shows. + */ +declare namespace parser { + /* copied from postcss -- so we don't need to add a dependency */ + type ErrorOptions = { + plugin?: string; + word?: string; + index?: number + }; + /* the bits we use of postcss.Rule, copied from postcss -- so we don't need to add a dependency */ + type PostCSSRuleNode = { + selector: string + /** + * @returns postcss.CssSyntaxError but it's a complex object, caller + * should cast to it if they have a dependency on postcss. + */ + error(message: string, options?: ErrorOptions): Error; + }; + /** Accepts a string */ + type Selectors = string | PostCSSRuleNode + type ProcessorFn = (root: parser.Root) => ReturnType; + type SyncProcessor = ProcessorFn; + type AsyncProcessor = ProcessorFn>; + + const TAG: "tag"; + const STRING: "string"; + const SELECTOR: "selector"; + const ROOT: "root"; + const PSEUDO: "pseudo"; + const NESTING: "nesting"; + const ID: "id"; + const COMMENT: "comment"; + const COMBINATOR: "combinator"; + const CLASS: "class"; + const ATTRIBUTE: "attribute"; + const UNIVERSAL: "universal"; + + interface NodeTypes { + tag: Tag, + string: String, + selector: Selector, + root: Root, + pseudo: Pseudo, + nesting: Nesting, + id: Identifier, + comment: Comment, + combinator: Combinator, + class: ClassName, + attribute: Attribute, + universal: Universal + } + + type Node = NodeTypes[keyof NodeTypes]; + + function isNode(node: any): node is Node; + + interface Options { + /** + * Preserve whitespace when true. Default: false; + */ + lossless: boolean; + /** + * When true and a postcss.Rule is passed, set the result of + * processing back onto the rule when done. Default: false. + */ + updateSelector: boolean; + } + class Processor< + TransformType = never, + SyncSelectorsType extends Selectors | never = Selectors + > { + res: Root; + readonly result: String; + ast(selectors: Selectors, options?: Partial): Promise; + astSync(selectors: SyncSelectorsType, options?: Partial): Root; + transform(selectors: Selectors, options?: Partial): Promise; + transformSync(selectors: SyncSelectorsType, options?: Partial): TransformType; + process(selectors: Selectors, options?: Partial): Promise; + processSync(selectors: SyncSelectorsType, options?: Partial): string; + } + interface ParserOptions { + css: string; + error: (message: string, options: ErrorOptions) => Error; + options: Options; + } + class Parser { + input: ParserOptions; + lossy: boolean; + position: number; + root: Root; + selectors: string; + current: Selector; + constructor(input: ParserOptions); + /** + * Raises an error, if the processor is invoked on + * a postcss Rule node, a better error message is raised. + */ + error(message: string, options?: ErrorOptions): void; + } + interface NodeSource { + start?: { + line: number, + column: number + }, + end?: { + line: number, + column: number + } + } + interface SpaceAround { + before: string; + after: string; + } + interface Spaces extends SpaceAround { + [spaceType: string]: string | Partial | undefined; + } + interface NodeOptions { + value: Value; + spaces?: Partial; + source?: NodeSource; + sourceIndex?: number; + } + interface Base< + Value extends string | undefined = string, + ParentType extends Container | undefined = Container | undefined + > { + type: keyof NodeTypes; + parent: ParentType; + value: Value; + spaces: Spaces; + source?: NodeSource; + sourceIndex: number; + rawSpaceBefore: string; + rawSpaceAfter: string; + remove(): Node; + replaceWith(...nodes: Node[]): Node; + next(): Node; + prev(): Node; + clone(opts: {[override: string]:any}): Node; + /** + * Return whether this node includes the character at the position of the given line and column. + * Returns undefined if the nodes lack sufficient source metadata to determine the position. + * @param line 1-index based line number relative to the start of the selector. + * @param column 1-index based column number relative to the start of the selector. + */ + isAtPosition(line: number, column: number): boolean | undefined; + /** + * Some non-standard syntax doesn't follow normal escaping rules for css, + * this allows the escaped value to be specified directly, allowing illegal characters to be + * directly inserted into css output. + * @param name the property to set + * @param value the unescaped value of the property + * @param valueEscaped optional. the escaped value of the property. + */ + setPropertyAndEscape(name: string, value: any, valueEscaped: string): void; + /** + * When you want a value to passed through to CSS directly. This method + * deletes the corresponding raw value causing the stringifier to fallback + * to the unescaped value. + * @param name the property to set. + * @param value The value that is both escaped and unescaped. + */ + setPropertyWithoutEscape(name: string, value: any): void; + /** + * Some non-standard syntax doesn't follow normal escaping rules for css. + * This allows non standard syntax to be appended to an existing property + * by specifying the escaped value. By specifying the escaped value, + * illegal characters are allowed to be directly inserted into css output. + * @param {string} name the property to set + * @param {any} value the unescaped value of the property + * @param {string} valueEscaped optional. the escaped value of the property. + */ + appendToPropertyAndEscape(name: string, value: any, valueEscaped: string): void; + toString(): string; + } + interface ContainerOptions extends NodeOptions { + nodes?: Array; + } + interface Container< + Value extends string | undefined = string, + Child extends Node = Node + > extends Base { + nodes: Array; + append(selector: Selector): this; + prepend(selector: Selector): this; + at(index: number): Child; + /** + * Return the most specific node at the line and column number given. + * The source location is based on the original parsed location, locations aren't + * updated as selector nodes are mutated. + * + * Note that this location is relative to the location of the first character + * of the selector, and not the location of the selector in the overall document + * when used in conjunction with postcss. + * + * If not found, returns undefined. + * @param line The line number of the node to find. (1-based index) + * @param col The column number of the node to find. (1-based index) + */ + atPosition(line: number, column: number): Child; + index(child: Child): number; + readonly first: Child; + readonly last: Child; + readonly length: number; + removeChild(child: Child): this; + removeAll(): Container; + empty(): Container; + insertAfter(oldNode: Child, newNode: Child): this; + insertBefore(oldNode: Child, newNode: Child): this; + each(callback: (node: Child) => boolean | void): boolean | undefined; + walk( + callback: (node: Node) => boolean | void + ): boolean | undefined; + walkAttributes( + callback: (node: Attribute) => boolean | void + ): boolean | undefined; + walkClasses( + callback: (node: ClassName) => boolean | void + ): boolean | undefined; + walkCombinators( + callback: (node: Combinator) => boolean | void + ): boolean | undefined; + walkComments( + callback: (node: Comment) => boolean | void + ): boolean | undefined; + walkIds( + callback: (node: Identifier) => boolean | void + ): boolean | undefined; + walkNesting( + callback: (node: Nesting) => boolean | void + ): boolean | undefined; + walkPseudos( + callback: (node: Pseudo) => boolean | void + ): boolean | undefined; + walkTags(callback: (node: Tag) => boolean | void): boolean | undefined; + split(callback: (node: Child) => boolean): [Child[], Child[]]; + map(callback: (node: Child) => T): T[]; + reduce( + callback: ( + previousValue: Child, + currentValue: Child, + currentIndex: number, + array: readonly Child[] + ) => Child + ): Child; + reduce( + callback: ( + previousValue: Child, + currentValue: Child, + currentIndex: number, + array: readonly Child[] + ) => Child, + initialValue: Child + ): Child; + reduce( + callback: ( + previousValue: T, + currentValue: Child, + currentIndex: number, + array: readonly Child[] + ) => T, + initialValue: T + ): T; + every(callback: (node: Child) => boolean): boolean; + some(callback: (node: Child) => boolean): boolean; + filter(callback: (node: Child) => boolean): Child[]; + sort(callback: (nodeA: Child, nodeB: Child) => number): Child[]; + toString(): string; + } + function isContainer(node: any): node is Root | Selector | Pseudo; + + interface NamespaceOptions extends NodeOptions { + namespace?: string | true; + } + interface Namespace extends Base { + /** alias for namespace */ + ns: string | true; + /** + * namespace prefix. + */ + namespace: string | true; + /** + * If a namespace exists, prefix the value provided with it, separated by |. + */ + qualifiedName(value: string): string; + /** + * A string representing the namespace suitable for output. + */ + readonly namespaceString: string; + } + function isNamespace(node: any): node is Attribute | Tag; + + interface Root extends Container { + type: "root"; + /** + * Raises an error, if the processor is invoked on + * a postcss Rule node, a better error message is raised. + */ + error(message: string, options?: ErrorOptions): Error; + nodeAt(line: number, column: number): Node + } + function root(opts: ContainerOptions): Root; + function isRoot(node: any): node is Root; + + interface _Selector extends Container> { + type: "selector"; + } + type Selector = _Selector; + function selector(opts: ContainerOptions): Selector; + function isSelector(node: any): node is Selector; + + interface CombinatorRaws { + value?: string; + spaces?: { + before?: string; + after?: string; + }; + } + interface Combinator extends Base { + type: "combinator"; + raws?: CombinatorRaws; + } + function combinator(opts: NodeOptions): Combinator; + function isCombinator(node: any): node is Combinator; + + interface ClassName extends Base { + type: "class"; + } + function className(opts: NamespaceOptions): ClassName; + function isClassName(node: any): node is ClassName; + + type AttributeOperator = "=" | "~=" | "|=" | "^=" | "$=" | "*="; + type QuoteMark = '"' | "'" | null; + interface PreferredQuoteMarkOptions { + quoteMark?: QuoteMark; + preferCurrentQuoteMark?: boolean; + } + interface SmartQuoteMarkOptions extends PreferredQuoteMarkOptions { + smart?: boolean; + } + interface AttributeOptions extends NamespaceOptions { + attribute: string; + operator?: AttributeOperator; + insensitive?: boolean; + quoteMark?: QuoteMark; + /** @deprecated Use quoteMark instead. */ + quoted?: boolean; + spaces?: { + before?: string; + after?: string; + attribute?: Partial; + operator?: Partial; + value?: Partial; + insensitive?: Partial; + } + raws: { + unquoted?: string; + attribute?: string; + operator?: string; + value?: string; + insensitive?: string; + spaces?: { + attribute?: Partial; + operator?: Partial; + value?: Partial; + insensitive?: Partial; + } + }; + } + interface Attribute extends Namespace { + type: "attribute"; + attribute: string; + operator?: AttributeOperator; + insensitive?: boolean; + quoteMark: QuoteMark; + quoted?: boolean; + spaces: { + before: string; + after: string; + attribute?: Partial; + operator?: Partial; + value?: Partial; + insensitive?: Partial; + } + raws: { + /** @deprecated The attribute value is unquoted, use that instead.. */ + unquoted?: string; + attribute?: string; + operator?: string; + /** The value of the attribute with quotes and escapes. */ + value?: string; + insensitive?: string; + spaces?: { + attribute?: Partial; + operator?: Partial; + value?: Partial; + insensitive?: Partial; + } + }; + /** + * The attribute name after having been qualified with a namespace. + */ + readonly qualifiedAttribute: string; + + /** + * The case insensitivity flag or an empty string depending on whether this + * attribute is case insensitive. + */ + readonly insensitiveFlag : 'i' | ''; + + /** + * Returns the attribute's value quoted such that it would be legal to use + * in the value of a css file. The original value's quotation setting + * used for stringification is left unchanged. See `setValue(value, options)` + * if you want to control the quote settings of a new value for the attribute or + * `set quoteMark(mark)` if you want to change the quote settings of the current + * value. + * + * You can also change the quotation used for the current value by setting quoteMark. + **/ + getQuotedValue(options?: SmartQuoteMarkOptions): string; + + /** + * Set the unescaped value with the specified quotation options. The value + * provided must not include any wrapping quote marks -- those quotes will + * be interpreted as part of the value and escaped accordingly. + * @param value + */ + setValue(value: string, options?: SmartQuoteMarkOptions): void; + + /** + * Intelligently select a quoteMark value based on the value's contents. If + * the value is a legal CSS ident, it will not be quoted. Otherwise a quote + * mark will be picked that minimizes the number of escapes. + * + * If there's no clear winner, the quote mark from these options is used, + * then the source quote mark (this is inverted if `preferCurrentQuoteMark` is + * true). If the quoteMark is unspecified, a double quote is used. + **/ + smartQuoteMark(options: PreferredQuoteMarkOptions): QuoteMark; + + /** + * Selects the preferred quote mark based on the options and the current quote mark value. + * If you want the quote mark to depend on the attribute value, call `smartQuoteMark(opts)` + * instead. + */ + preferredQuoteMark(options: PreferredQuoteMarkOptions): QuoteMark + + /** + * returns the offset of the attribute part specified relative to the + * start of the node of the output string. + * + * * "ns" - alias for "namespace" + * * "namespace" - the namespace if it exists. + * * "attribute" - the attribute name + * * "attributeNS" - the start of the attribute or its namespace + * * "operator" - the match operator of the attribute + * * "value" - The value (string or identifier) + * * "insensitive" - the case insensitivity flag; + * @param part One of the possible values inside an attribute. + * @returns -1 if the name is invalid or the value doesn't exist in this attribute. + */ + offsetOf(part: "ns" | "namespace" | "attribute" | "attributeNS" | "operator" | "value" | "insensitive"): number; + } + function attribute(opts: AttributeOptions): Attribute; + function isAttribute(node: any): node is Attribute; + + interface Pseudo extends Container { + type: "pseudo"; + } + function pseudo(opts: ContainerOptions): Pseudo; + /** + * Checks wether the node is the Psuedo subtype of node. + */ + function isPseudo(node: any): node is Pseudo; + + /** + * Checks wether the node is, specifically, a pseudo element instead of + * pseudo class. + */ + function isPseudoElement(node: any): node is Pseudo; + + /** + * Checks wether the node is, specifically, a pseudo class instead of + * pseudo element. + */ + function isPseudoClass(node: any): node is Pseudo; + + + interface Tag extends Namespace { + type: "tag"; + } + function tag(opts: NamespaceOptions): Tag; + function isTag(node: any): node is Tag; + + interface Comment extends Base { + type: "comment"; + } + function comment(opts: NodeOptions): Comment; + function isComment(node: any): node is Comment; + + interface Identifier extends Base { + type: "id"; + } + function id(opts: any): any; + function isIdentifier(node: any): node is Identifier; + + interface Nesting extends Base { + type: "nesting"; + } + function nesting(opts: any): any; + function isNesting(node: any): node is Nesting; + + interface String extends Base { + type: "string"; + } + function string(opts: NodeOptions): String; + function isString(node: any): node is String; + + interface Universal extends Base { + type: "universal"; + } + function universal(opts?: NamespaceOptions): any; + function isUniversal(node: any): node is Universal; +} diff --git a/package-lock.json b/package-lock.json index 3ef6ead05e562..27f49b841dc44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2007,6 +2007,17 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cssom": { "version": "0.5.0", "dev": true, @@ -5685,6 +5696,18 @@ "dev": true, "license": "MIT" }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/prebuild-install": { "version": "6.1.4", "dev": true, @@ -10009,6 +10032,7 @@ "npmlog": "^6.0.2", "pacote": "^13.0.5", "parse-conflict-json": "^2.0.1", + "postcss-selector-parser": "^6.0.10", "proc-log": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", @@ -10735,6 +10759,7 @@ "npmlog": "^6.0.2", "pacote": "^13.0.5", "parse-conflict-json": "^2.0.1", + "postcss-selector-parser": "*", "proc-log": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", @@ -11501,6 +11526,11 @@ "which": "^2.0.1" } }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, "cssom": { "version": "0.5.0", "dev": true @@ -13970,6 +14000,15 @@ "version": "1.3.6", "dev": true }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, "prebuild-install": { "version": "6.1.4", "dev": true, diff --git a/workspaces/arborist/lib/node.js b/workspaces/arborist/lib/node.js index d731e5f617908..b2c62f6a5eb7a 100644 --- a/workspaces/arborist/lib/node.js +++ b/workspaces/arborist/lib/node.js @@ -69,6 +69,8 @@ const consistentResolve = require('./consistent-resolve.js') const printableTree = require('./printable.js') const CaseInsensitiveMap = require('./case-insensitive-map.js') +const querySelectorAll = require('./query-selector-all.js') + class Node { constructor (options) { // NB: path can be null if it's a link target @@ -1446,6 +1448,12 @@ class Node { return base === name && basename(nm) === 'node_modules' ? dir : false } + // maybe accept both string value or array of strings + // seems to be what dom API does + querySelectorAll(query) { + return querySelectorAll(this, query) + } + toJSON () { return printableTree(this) } diff --git a/workspaces/arborist/lib/query-selector-all-response.js b/workspaces/arborist/lib/query-selector-all-response.js new file mode 100644 index 0000000000000..394c677200714 --- /dev/null +++ b/workspaces/arborist/lib/query-selector-all-response.js @@ -0,0 +1,29 @@ +'use strict' + +class QuerySelectorAllResponse { + #node = null + + constructor (node) { + const { + name, + version, + location, + path, + resolved, + isLink, + isWorkspace, + pkgid, + } = node.target + + this.name = name + this.version = version + this.pkgid = pkgid + this.location = location + this.path = path + this.resolved = resolved + this.isLink = isLink + this.isWorkspace = isWorkspace + } +} + +module.exports = QuerySelectorAllResponse diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js new file mode 100644 index 0000000000000..c77f21b43b270 --- /dev/null +++ b/workspaces/arborist/lib/query-selector-all.js @@ -0,0 +1,334 @@ +'use strict' + +const localeCompare = require('@isaacs/string-locale-compare')('en') +const parser = require('postcss-selector-parser') +const npa = require('npm-package-arg') +const semver = require('semver') +const QuerySelectorAllResponse = require('./query-selector-all-response.js') + +// retrieves a normalized inventory +const convertInventoryItemsToResponses = inventory => { + const responses = [] + const responsesSeen = new Set() + for (const node of inventory) { + if (!responsesSeen.has(node.target.path)) { + const item = new QuerySelectorAllResponse(node) + responses.push(item) + responsesSeen.add(item.path) + } + } + return responses +} + +const fixupIdSpecs = selector => { + let foundId = null + selector.walk((nextQueryNode) => { + const appendSelectorAsPartOfId = + foundId && + ( + ( + nextQueryNode.type === 'class' && + /[0-9]|x/.test(String(nextQueryNode)) + ) || ( + nextQueryNode.type === 'tag' && + /[0-9]|\-/.test(String(nextQueryNode)) + ) || + ( + ( + String(nextQueryNode) === ' ' || + String(nextQueryNode) === '||' || + String(nextQueryNode) === '~' || + String(nextQueryNode) === '>' || + String(nextQueryNode) === '*' + ) && + (!nextQueryNode.spaces.before && !nextQueryNode.spaces.after) + ) + ) + + if (appendSelectorAsPartOfId) { + foundId.value = `${foundId.value}${String(nextQueryNode)}` + nextQueryNode.remove() + } else { + foundId = null + } + + const idSpecWithVersion = + nextQueryNode.type === 'id' && + String(nextQueryNode).indexOf('@') > -1 + if (idSpecWithVersion) { + foundId = nextQueryNode + } + + }) +} + +const querySelectorAll = (rootNode, query) => { + // if query is an empty string or any falsy + // value, just returns an empty result + if (!query) { + return [] + } + + const ArboristNode = rootNode.constructor + const rootSelector = + parser(fixupIdSpecs).astSync(query, { lossless: false }) + let currentSelector = rootSelector + let prevSelector = null + let pendingCombinator = null + + const inventory = [...rootNode.inventory.values()] + + // maps containing the logic to parse each of the supported css selectors + const attributeOperatorsMap = new Map(Object.entries({ + '=' ({ attribute, value, pkg }) { return pkg[attribute] === value }, + '*=' ({ attribute, value, pkg }) { + return pkg[attribute].indexOf(value) > -1 + }, + '|=' ({ attribute, value, pkg }) { + return pkg[attribute].split('-')[0] === value + }, + '^=' ({ attribute, value, pkg }) { + return pkg[attribute].startsWith(value) + }, + '$=' ({ attribute, value, pkg }) { return pkg[attribute].endsWith(value) }, + })) + const classesMap = new Map(Object.entries({ + '.prod' (prevResults) { + return prevResults.filter(node => + [...node.edgesIn].some(edge => edge.prod)) + }, + '.dev' (prevResults) { + return prevResults.filter(node => + [...node.edgesIn].some(edge => edge.dev)) + }, + '.optional' (prevResults) { + return prevResults.filter(node => + [...node.edgesIn].some(edge => edge.optional)) + }, + '.peer' (prevResults) { + return prevResults.filter(node => + [...node.edgesIn].some(edge => edge.peer)) + }, + '.workspace' (prevResults) { + return prevResults.filter(node => node.isWorkspace) + }, + '.bundled' (prevResults) { + return prevResults.filter(node => node.inBundle) + }, + })) + + const hasParent = (node, compareNodes) => + compareNodes.some(compareNode => + // follows logical parent for link anscestors + (node.isTop && node.resolveParent) === compareNode || + // follows edges-in to check if they match a possible parent + [...node.edgesIn].some(edge => + edge && edge.from === compareNode)) + + // checks if a given node is a descendant of any + // of the nodes provided in the compare nodes array + const ancestorCache = new Map() + const hasAscendant = (node, compareNodes) => { + const key = [node.pkgid, ':', ...compareNodes.map(n => n.pkgid)].join(' ') + if (ancestorCache.has(key)) { + return ancestorCache.get(key) + } + + if (hasParent(node, compareNodes)) { + ancestorCache.set(key, true) + return true + } + + const ancestorFound = (node.isTop && node.resolveParent) + ? hasAscendant(node.resolveParent, compareNodes) + : [...node.edgesIn].some(edge => + edge && edge.from && hasAscendant(edge.from, compareNodes)) + + ancestorCache.set(key, ancestorFound) + return ancestorFound + } + + const combinatorsMap = new Map(Object.entries({ + '>' (prevResults, nextResults) { + return nextResults.filter(nextItem => + hasParent(nextItem, prevResults)) + }, + ' ' (prevResults, nextResults) { + return nextResults.filter(nextItem => + hasAscendant(nextItem, prevResults)) + }, + '~' () { return result }, + })) + const pseudoMap = new Map(Object.entries({ + ':empty' () { + return getInitialItems().filter(node => node.edgesOut.size === 0) + }, + ':extraneous' () { + return getInitialItems().filter(node => node.extraneous) + }, + ':invalid' () { + return getInitialItems().filter(node => + [...node.edgesIn].some(edge => edge.invalid)) + }, + ':link' () { + return getInitialItems().filter(node => node.isLink) + }, + ':missing' () { + const ress = inventory.reduce((res, node) => { + for (const edge of node.edgesOut.values()) { + if (edge.missing) { + const pkg = { name: edge.name, version: edge.spec } + res.push(new ArboristNode({ pkg })) + } + } + return res + }, []) + return ress + }, + ':private' () { + return getInitialItems().filter(node => node.package.private) + }, + ':root' () { + const [rootNode] = inventory + return [rootNode] + }, + })) + + // retrieves the initial items to which start the filtering / matching + // for most of the different types of css selectors, e.g: class, id, * + // in different contexts we need to start with a different list of items + // from the inventory, for example a query for `.workspace` actually means + // same as `*.workspace` so we want to start with the full inventory in + // that case + const getInitialItems = () => { + const attributeFilterQueryNodes = new Set([ + 'attribute', + 'class', + 'id', + 'pseudo', + 'universal', + ]) + const firstParsedSelector = + prevSelector.type === 'selector' && result.length === 0 + + // when parsing the first selector or previous query node parsed + // is a combinator we're going to need the entire inventory in + // order to match items using that operator + return firstParsedSelector || prevSelector.type === 'combinator' + ? inventory + : result + } + + // combinators need information about previously filtered items along + // with info of the items parsed / retrieved from the selector right + // past the combinator, for this reason combinators are stored and + // only ran as the last part of each selector logic + const processPendingCombinator = (prevResults, nextResults) => { + if (pendingCombinator) { + const res = pendingCombinator(prevResults, nextResults) + pendingCombinator = null + return res + } + return nextResults + } + + // below are the functions containing the logic to + // parse each of the recognized css selectors types + const attribute = () => { + const { + qualifiedAttribute: attribute, + operator, + value, + } = currentSelector + const prevResult = result + const nextResult = getInitialItems().filter(node => { + return attributeOperatorsMap.get(operator)({ + attribute, + value, + pkg: node.package, + })}) + result = processPendingCombinator(prevResult, nextResult) + } + const classType = () => { + const prevResult = result + const nextResult = + classesMap.get(String(currentSelector))(getInitialItems()) + result = processPendingCombinator(prevResult, nextResult) + } + const combinator = () => { + pendingCombinator = combinatorsMap.get(String(currentSelector)) + } + const id = () => { + const spec = npa(String(currentSelector).slice(1)) + const prevResult = result + const nextResult = getInitialItems().filter(node => + (node.name === spec.name || node.package.name === spec.name) && + (semver.satisfies(node.version, spec.fetchSpec) || !spec.rawSpec)) + result = processPendingCombinator(prevResult, nextResult) + } + const pseudo = () => { + const currentSelectorFn = pseudoMap.get(String(currentSelector)) + if (currentSelectorFn) { + const prevResult = result + const nextResult = currentSelectorFn() + result = processPendingCombinator(prevResult, nextResult) + } + } + const selector = () => { + // collect results for the current selector + results.push(result) + // starts a new result array for the next selector to use + result = [] + } + const universal = () => { + if (prevSelector.type === 'root' && !result.length) { + result = inventory + } else { + result = processPendingCombinator(result, getInitialItems()) + } + } + + // maps each of the recognized css selectors + // to a function that parses it + const retrieveByType = new Map(Object.entries({ + attribute, + 'class': classType, + combinator, + id, + pseudo, + selector, + universal, + })) + + // results is going to be a set of all nodes returned for each + // parsed container selector, the `result` variable tracks the + // current result for each of nested selectors within a query + const results = [] + let result = [] + + // walks through the parsed css query and update + // the result after parsing / executing each selector + // console.error(require('util').inspect(rootSelector, { depth: 10 })) + rootSelector.walk((nextQueryNode) => { + prevSelector = currentSelector + currentSelector = nextQueryNode + // console.error('prevSelector', prevSelector.type, String(prevSelector)) + //console.error('currentSelector', currentSelector.type, String(currentSelector)) + + const updateResult = + retrieveByType.get(currentSelector.type) + if (updateResult) { + updateResult() + } + }) + + // append any remaining selector result + results.push(result) + + // returns normalized json output + return JSON.parse(JSON.stringify( + convertInventoryItemsToResponses(results.flatMap(i => i)) + .sort((a, b) => localeCompare(a.path, b.path)))) +} + +module.exports = querySelectorAll diff --git a/workspaces/arborist/package.json b/workspaces/arborist/package.json index e6d2d9613c206..e5e25143675b1 100644 --- a/workspaces/arborist/package.json +++ b/workspaces/arborist/package.json @@ -27,6 +27,7 @@ "npmlog": "^6.0.2", "pacote": "^13.0.5", "parse-conflict-json": "^2.0.1", + "postcss-selector-parser": "^6.0.10", "proc-log": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js new file mode 100644 index 0000000000000..ce450659d289d --- /dev/null +++ b/workspaces/arborist/test/query-selector-all.js @@ -0,0 +1,352 @@ +'use strict' + +const util = require('util') +const t = require('tap') +const Arborist = require('..') +const QuerySelectorAllResponse = require('../lib/query-selector-all-response.js') + +const querySelectorAll = t.mock('../lib/query-selector-all.js', { + '../lib/query-selector-all-response.js': class extends QuerySelectorAllResponse { + // tweaking the output to be used in assertions for + // readability's sake, please keep in mind that the + // querySelectorAll normally returns a json object instead + toJSON () { + return this.pkgid + } + } +}) + +t.test('query-selector-all', async t => { + /* + fixture: + + query-selector-all-tests@1.0.0 + ├── @npmcli/abbrev@2.0.0 extraneous + ├─┬ a@1.0.0 -> ./a + │ └─┬ baz@1.0.0 + │ └── lorem@1.0.0 + ├── abbrev@1.1.1 + ├─┬ b@1.0.0 -> ./b + │ └── bar@2.0.0 deduped + ├── bar@2.0.0 + └─┬ foo@2.2.2 + ├─┬ bar@1.4.0 + │ └── dasherino-sep-pkg@2.0.0 + └── dash-separated-pkg@1.0.0 + */ + const path = t.testdir({ + 'node_modules': { + '@npmcli': { + abbrev: { + 'package.json': JSON.stringify({ + name: '@npmcli/abbrev', + version: '2.0.0' + }), + }, + }, + a: t.fixture('symlink', '../a'), + abbrev: { + 'package.json': JSON.stringify({ name: 'abbrev', version: '1.1.1' }), + }, + b: t.fixture('symlink', '../b'), + bar: { + 'package.json': JSON.stringify({ + name: 'bar', + version: '2.0.0', + }), + }, + baz: { + 'package.json': JSON.stringify({ + name: 'baz', + version: '1.0.0', + dependencies: { + lorem: '^1.0.0', + }, + }), + }, + foo: { + 'node_modules': { + bar: { + 'package.json': JSON.stringify({ + name: 'bar', + version: '1.4.0', + dependencies: { + 'dasherino-sep-pkg': '2.0.0', + }, + }), + }, + }, + 'package.json': JSON.stringify({ + name: 'foo', + version: '2.2.2', + dependencies: { + bar: '^1.0.0', + 'dash-separated-pkg': '^1.0.0', + }, + }), + }, + 'dash-separated-pkg': { + 'package.json': JSON.stringify({ + name: 'dash-separated-pkg', + version: '1.0.0', + }), + }, + 'dasherino-sep-pkg': { + 'package.json': JSON.stringify({ + name: 'dasherino-sep-pkg', + version: '2.0.0', + }), + }, + ipsum: { + 'package.json': JSON.stringify({ + name: 'sit', + version: '1.0.0', + }), + }, + lorem: { + 'package.json': JSON.stringify({ + name: 'lorem', + version: '1.0.0', + }), + }, + }, + a: { 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + dependencies: { + baz: '^1.0.0', + }, + }), }, + b: { 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + dependencies: { + bar: '^2.0.0', + }, + }), }, + 'package.json': JSON.stringify({ + name: 'query-selector-all-tests', + version: '1.0.0', + workspaces: ['a', 'b'], + dependencies: { + a: '^1.0.0', + abbrev: '^1.1.1', + bar: '^2.0.0', + ipsum: 'npm:sit@1.0.0', + }, + devDependencies: { + foo: '^2.0.0', + }, + }), + }) + + const opts = { + path, + } + const arb = new Arborist(opts) + const tree = await arb.loadActual(opts) + console.error(require('child_process').execSync('tree', { cwd: path }).toString()) + console.error(require('child_process').execSync('npm ls --all', { cwd: path }).toString()) + + t.same( + querySelectorAll(tree, '*'), + [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasherino-sep-pkg@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ], + '*' + ) + + t.match( + querySelectorAll(tree, ':root'), + [ + 'query-selector-all-tests@1.0.0' + ], + ':root' + ) + + t.same( + querySelectorAll(tree, ':root > *'), + [ + 'a@1.0.0', + 'b@1.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'foo@2.2.2', + 'ipsum@npm:sit@1.0.0', + ], + ':root > *' + ) + + t.same( + querySelectorAll(tree, ':root > .workspace'), + [ + 'a@1.0.0', + 'b@1.0.0', + ], + ':root > .workspace' + ) + + t.same( + querySelectorAll(tree, ':root > *.workspace'), + [ + 'a@1.0.0', + 'b@1.0.0', + ], + ':root > *.workspace' + ) + + t.same( + querySelectorAll(tree, ':root > .workspace[name=a]'), + [ + 'a@1.0.0', + ], + ':root > .workspace[name=a]' + ) + + t.same( + querySelectorAll(tree, ':root > [name=bar]'), + [ + 'bar@2.0.0', + ], + ':root > [name=bar]' + ) + + t.same( + querySelectorAll(tree, ':root > .workspace[version=1.0.0]'), + [ + 'a@1.0.0', + 'b@1.0.0', + ], + ':root > .workspace[version=1.0.0]' + ) + + t.same( + querySelectorAll(tree, ':root > .workspace[name=a][version=1.0.0]'), + [ + 'a@1.0.0', + ], + ':root > .workspace[name=a][version=1.0.0]' + ) + + t.same( + querySelectorAll(tree, ':root > [name=a]'), + [ + 'a@1.0.0', + ], + ':root > [name=a]' + ) + + t.same( + querySelectorAll(tree, '[name=a]'), + [ + 'a@1.0.0', + ], + '[name=a]' + ) + + t.same( + querySelectorAll(tree, '[name^=a]'), + [ + 'a@1.0.0', + 'abbrev@1.1.1', + ], + '[name^=a]' + ) + + t.same( + querySelectorAll(tree, '[name|=dash]'), + [ + 'dash-separated-pkg@1.0.0', + ], + '[name|=dash]' + ) + + t.same( + querySelectorAll(tree, '[name$=oo]'), + [ + 'foo@2.2.2', + ], + '[name$=oo]' + ) + + t.same( + querySelectorAll(tree, '[name*=a]'), + [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasherino-sep-pkg@2.0.0', + 'bar@1.4.0', + ], + '[name*=a]' + ) + + t.same( + querySelectorAll(tree, '.workspace'), + [ + 'a@1.0.0', + 'b@1.0.0', + ], + '.workspace' + ) + + t.same( + querySelectorAll(tree, '.workspace > *'), + [ + 'bar@2.0.0', + 'baz@1.0.0', + ], + '.workspace > *' + ) + + const idSpecParsing = [ + ['#bar', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@2.0.0', [ 'bar@2.0.0' ]], + ['#bar@^2.0.0', [ 'bar@2.0.0' ]], + ['#bar@~2.0.0', [ 'bar@2.0.0' ]], + ['#bar@=2.0.0', [ 'bar@2.0.0' ]], + ['#bar@>=2.0.0', [ 'bar@2.0.0' ]], + ['#bar@<3.0.0', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@2 - 3', [ 'bar@2.0.0' ]], + ['#bar@2.0.0 - 3.0.0', [ 'bar@2.0.0' ]], + ['#bar@*', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@^2.0.0-beta.0', [ 'bar@2.0.0' ]], + ['#bar@>1.5.0 <3.0.0', [ 'bar@2.0.0' ]], + ['#bar@2.x', [ 'bar@2.0.0' ]], + ['#bar@2.x.x', [ 'bar@2.0.0' ]], + ['#bar@1||2', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@1 || 2', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@1 || 2.0.0', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@1.4.0 || 2', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#ipsum', [ 'ipsum@npm:sit@1.0.0' ]], + ['#bar > *', [ 'dasherino-sep-pkg@2.0.0' ]], + [':root > #bar', [ 'bar@2.0.0' ]], + [':root > #bar > *', []], + [':root #bar > *', [ 'dasherino-sep-pkg@2.0.0' ]], + ['#bar@2, #foo', [ 'bar@2.0.0', 'foo@2.2.2' ]], + ['#a, #bar@2, #foo', [ 'a@1.0.0', 'bar@2.0.0', 'foo@2.2.2' ]], + ] + idSpecParsing.forEach(([selector, expected]) => { + t.same( + querySelectorAll(tree, selector), + expected, + selector + ) + }) +}) From b6a1668dc79c48c7c5f653f19d0dea13efed9836 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Fri, 27 May 2022 15:38:31 -0400 Subject: [PATCH 02/17] async api --- workspaces/arborist/lib/query-selector-all.js | 2 +- .../arborist/test/query-selector-all.js | 189 ++++-------------- 2 files changed, 45 insertions(+), 146 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index c77f21b43b270..a3395f4a4cd9f 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -62,7 +62,7 @@ const fixupIdSpecs = selector => { }) } -const querySelectorAll = (rootNode, query) => { +const querySelectorAll = async (rootNode, query) => { // if query is an empty string or any falsy // value, just returns an empty result if (!query) { diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index ce450659d289d..d1ab44828c256 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -148,9 +148,20 @@ t.test('query-selector-all', async t => { console.error(require('child_process').execSync('tree', { cwd: path }).toString()) console.error(require('child_process').execSync('npm ls --all', { cwd: path }).toString()) - t.same( - querySelectorAll(tree, '*'), - [ + const runSpecParsing = async testCase => { + for (const [selector, expected] of testCase) { + const res = await querySelectorAll(tree, selector) + t.same( + res, + expected, + selector + ) + } + } + + // universal selector + await runSpecParsing([ + ['*', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', @@ -164,126 +175,31 @@ t.test('query-selector-all', async t => { 'bar@1.4.0', 'ipsum@npm:sit@1.0.0', 'lorem@1.0.0', - ], - '*' - ) + ]], + ]) - t.match( - querySelectorAll(tree, ':root'), - [ - 'query-selector-all-tests@1.0.0' - ], - ':root' - ) - - t.same( - querySelectorAll(tree, ':root > *'), - [ + // pseudo :root + await runSpecParsing([ + [':root', ['query-selector-all-tests@1.0.0']], + [':root > *', [ 'a@1.0.0', 'b@1.0.0', 'abbrev@1.1.1', 'bar@2.0.0', 'foo@2.2.2', 'ipsum@npm:sit@1.0.0', - ], - ':root > *' - ) - - t.same( - querySelectorAll(tree, ':root > .workspace'), - [ - 'a@1.0.0', - 'b@1.0.0', - ], - ':root > .workspace' - ) - - t.same( - querySelectorAll(tree, ':root > *.workspace'), - [ - 'a@1.0.0', - 'b@1.0.0', - ], - ':root > *.workspace' - ) - - t.same( - querySelectorAll(tree, ':root > .workspace[name=a]'), - [ - 'a@1.0.0', - ], - ':root > .workspace[name=a]' - ) - - t.same( - querySelectorAll(tree, ':root > [name=bar]'), - [ - 'bar@2.0.0', - ], - ':root > [name=bar]' - ) - - t.same( - querySelectorAll(tree, ':root > .workspace[version=1.0.0]'), - [ - 'a@1.0.0', - 'b@1.0.0', - ], - ':root > .workspace[version=1.0.0]' - ) - - t.same( - querySelectorAll(tree, ':root > .workspace[name=a][version=1.0.0]'), - [ - 'a@1.0.0', - ], - ':root > .workspace[name=a][version=1.0.0]' - ) - - t.same( - querySelectorAll(tree, ':root > [name=a]'), - [ - 'a@1.0.0', - ], - ':root > [name=a]' - ) - - t.same( - querySelectorAll(tree, '[name=a]'), - [ - 'a@1.0.0', - ], - '[name=a]' - ) - - t.same( - querySelectorAll(tree, '[name^=a]'), - [ - 'a@1.0.0', - 'abbrev@1.1.1', - ], - '[name^=a]' - ) - - t.same( - querySelectorAll(tree, '[name|=dash]'), - [ - 'dash-separated-pkg@1.0.0', - ], - '[name|=dash]' - ) - - t.same( - querySelectorAll(tree, '[name$=oo]'), - [ - 'foo@2.2.2', - ], - '[name$=oo]' - ) - - t.same( - querySelectorAll(tree, '[name*=a]'), - [ + ]], + [':root > .workspace', [ 'a@1.0.0', 'b@1.0.0' ]], + [':root > *.workspace', [ 'a@1.0.0', 'b@1.0.0' ]], + [':root > .workspace[name=a]', [ 'a@1.0.0' ]], + [':root > [name=bar]', [ 'bar@2.0.0' ]], + [':root > .workspace[version=1.0.0]', [ 'a@1.0.0', 'b@1.0.0' ]], + [':root > .workspace[name=a][version=1.0.0]', [ 'a@1.0.0' ]], + [':root > [name=a]', [ 'a@1.0.0' ]], + [':root > [name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], + ['[name|=dash]', [ 'dash-separated-pkg@1.0.0' ]], + ['[name$=oo]', [ 'foo@2.2.2' ]], + ['[name*=a]', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', '@npmcli/abbrev@2.0.0', @@ -293,30 +209,20 @@ t.test('query-selector-all', async t => { 'dash-separated-pkg@1.0.0', 'dasherino-sep-pkg@2.0.0', 'bar@1.4.0', - ], - '[name*=a]' - ) + ]], + ]) - t.same( - querySelectorAll(tree, '.workspace'), - [ - 'a@1.0.0', - 'b@1.0.0', - ], - '.workspace' - ) + // classes + await runSpecParsing([ + ['.workspace', [ 'a@1.0.0', 'b@1.0.0' ]], + ['.workspace > *', [ 'bar@2.0.0', 'baz@1.0.0' ]], + ]) - t.same( - querySelectorAll(tree, '.workspace > *'), - [ - 'bar@2.0.0', - 'baz@1.0.0', - ], - '.workspace > *' - ) - - const idSpecParsing = [ + // id selector + await runSpecParsing([ ['#bar', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar@2', [ 'bar@2.0.0' ]], + ['#bar@2.0', [ 'bar@2.0.0' ]], ['#bar@2.0.0', [ 'bar@2.0.0' ]], ['#bar@^2.0.0', [ 'bar@2.0.0' ]], ['#bar@~2.0.0', [ 'bar@2.0.0' ]], @@ -341,12 +247,5 @@ t.test('query-selector-all', async t => { [':root #bar > *', [ 'dasherino-sep-pkg@2.0.0' ]], ['#bar@2, #foo', [ 'bar@2.0.0', 'foo@2.2.2' ]], ['#a, #bar@2, #foo', [ 'a@1.0.0', 'bar@2.0.0', 'foo@2.2.2' ]], - ] - idSpecParsing.forEach(([selector, expected]) => { - t.same( - querySelectorAll(tree, selector), - expected, - selector - ) - }) + ]) }) From 00058b58bf51176cc5dcb4fb9e040d62d2686a24 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Fri, 27 May 2022 18:40:08 -0400 Subject: [PATCH 03/17] added sibling combinator also added more tests --- workspaces/arborist/lib/query-selector-all.js | 30 +++++- .../arborist/test/query-selector-all.js | 94 ++++++++++++++++--- 2 files changed, 105 insertions(+), 19 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index a3395f4a4cd9f..a51b0597e0f06 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -35,11 +35,15 @@ const fixupIdSpecs = selector => { ) || ( ( - String(nextQueryNode) === ' ' || String(nextQueryNode) === '||' || - String(nextQueryNode) === '~' || - String(nextQueryNode) === '>' || - String(nextQueryNode) === '*' + String(nextQueryNode) === ' ' || + ( + ( + String(nextQueryNode) === '~' || + String(nextQueryNode) === '>' || + String(nextQueryNode) === '*' + ) && String(foundId).endsWith('@') + ) ) && (!nextQueryNode.spaces.before && !nextQueryNode.spaces.after) ) @@ -157,7 +161,23 @@ const querySelectorAll = async (rootNode, query) => { return nextResults.filter(nextItem => hasAscendant(nextItem, prevResults)) }, - '~' () { return result }, + '~' (prevResults, nextResults) { + return nextResults.filter(nextItem => { + const seenNodes = new Set() + const possibleParentNodes = + (prevResults.isTop && prevResults.resolveParent) || + prevResults + .flatMap(node => { + seenNodes.add(node) + return [...node.edgesIn] + }) + .map(edge => edge ? edge.from : null) + .filter(Boolean) + + return !seenNodes.has(nextItem) && + hasParent(nextItem, [...possibleParentNodes]) + }) + }, })) const pseudoMap = new Map(Object.entries({ ':empty' () { diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index d1ab44828c256..3a4aba54076cc 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -31,7 +31,7 @@ t.test('query-selector-all', async t => { ├── bar@2.0.0 └─┬ foo@2.2.2 ├─┬ bar@1.4.0 - │ └── dasherino-sep-pkg@2.0.0 + │ └── dasher@2.0.0 └── dash-separated-pkg@1.0.0 */ const path = t.testdir({ @@ -60,7 +60,7 @@ t.test('query-selector-all', async t => { name: 'baz', version: '1.0.0', dependencies: { - lorem: '^1.0.0', + lorem: '^2.0.0', }, }), }, @@ -70,8 +70,8 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'bar', version: '1.4.0', - dependencies: { - 'dasherino-sep-pkg': '2.0.0', + peerDependencies: { + 'dasher': '2.0.0', }, }), }, @@ -91,9 +91,9 @@ t.test('query-selector-all', async t => { version: '1.0.0', }), }, - 'dasherino-sep-pkg': { + 'dasher': { 'package.json': JSON.stringify({ - name: 'dasherino-sep-pkg', + name: 'dasher', version: '2.0.0', }), }, @@ -101,6 +101,9 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'sit', version: '1.0.0', + dependencies: { + 'missing-dep': '^1.0.0', + }, }), }, lorem: { @@ -113,13 +116,14 @@ t.test('query-selector-all', async t => { a: { 'package.json': JSON.stringify({ name: 'a', version: '1.0.0', - dependencies: { + optionalDependencies: { baz: '^1.0.0', }, }), }, b: { 'package.json': JSON.stringify({ name: 'b', version: '1.0.0', + private: true, dependencies: { bar: '^2.0.0', }, @@ -137,6 +141,7 @@ t.test('query-selector-all', async t => { devDependencies: { foo: '^2.0.0', }, + bundleDependencies: [ 'abbrev' ], }), }) @@ -146,7 +151,7 @@ t.test('query-selector-all', async t => { const arb = new Arborist(opts) const tree = await arb.loadActual(opts) console.error(require('child_process').execSync('tree', { cwd: path }).toString()) - console.error(require('child_process').execSync('npm ls --all', { cwd: path }).toString()) + //console.error(require('child_process').execSync('npm ls --all', { cwd: path }).toString()) const runSpecParsing = async testCase => { for (const [selector, expected] of testCase) { @@ -170,7 +175,20 @@ t.test('query-selector-all', async t => { 'bar@2.0.0', 'baz@1.0.0', 'dash-separated-pkg@1.0.0', - 'dasherino-sep-pkg@2.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], + ['* > *', [ + 'a@1.0.0', + 'b@1.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', 'foo@2.2.2', 'bar@1.4.0', 'ipsum@npm:sit@1.0.0', @@ -195,8 +213,43 @@ t.test('query-selector-all', async t => { [':root > [name=bar]', [ 'bar@2.0.0' ]], [':root > .workspace[version=1.0.0]', [ 'a@1.0.0', 'b@1.0.0' ]], [':root > .workspace[name=a][version=1.0.0]', [ 'a@1.0.0' ]], - [':root > [name=a]', [ 'a@1.0.0' ]], - [':root > [name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], + [':root > :root', []], + ['* > :root', []], + ['* :root', []], + [':root, :root', ['query-selector-all-tests@1.0.0']], + ['#a :root', []], + ]) + + // pseudo miscelaneous + await runSpecParsing([ + [':empty', [ + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'lorem@1.0.0', + ]], + [':root > :empty', [ + 'a@1.0.0', + 'b@1.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + ]], + [':extraneous', [ '@npmcli/abbrev@2.0.0' ]], + [':invalid', [ 'lorem@1.0.0' ]], + [':link', [ 'a@1.0.0', 'b@1.0.0' ]], + [':link', [ 'a@1.0.0', 'b@1.0.0' ]], + [':missing', [ 'missing-dep@^1.0.0' ]], + [':private', [ 'b@1.0.0' ]], + ]) + + // attribute matchers + await runSpecParsing([ + ['[name=a]', [ 'a@1.0.0' ]], + ['[name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], ['[name|=dash]', [ 'dash-separated-pkg@1.0.0' ]], ['[name$=oo]', [ 'foo@2.2.2' ]], ['[name*=a]', [ @@ -207,7 +260,7 @@ t.test('query-selector-all', async t => { 'bar@2.0.0', 'baz@1.0.0', 'dash-separated-pkg@1.0.0', - 'dasherino-sep-pkg@2.0.0', + 'dasher@2.0.0', 'bar@1.4.0', ]], ]) @@ -216,6 +269,18 @@ t.test('query-selector-all', async t => { await runSpecParsing([ ['.workspace', [ 'a@1.0.0', 'b@1.0.0' ]], ['.workspace > *', [ 'bar@2.0.0', 'baz@1.0.0' ]], + ['.dev', [ 'foo@2.2.2' ]], + ['.dev *', [ 'dash-separated-pkg@1.0.0', 'dasher@2.0.0', 'bar@1.4.0' ]], + ['.peer', [ 'dasher@2.0.0' ]], + ['.optional', [ 'baz@1.0.0' ]], + ['.bundled', [ 'abbrev@1.1.1' ]], + ['.bundled ~ *', [ + 'a@1.0.0', + 'b@1.0.0', + 'bar@2.0.0', + 'foo@2.2.2', + 'ipsum@npm:sit@1.0.0', + ]], ]) // id selector @@ -241,10 +306,11 @@ t.test('query-selector-all', async t => { ['#bar@1 || 2.0.0', [ 'bar@2.0.0', 'bar@1.4.0' ]], ['#bar@1.4.0 || 2', [ 'bar@2.0.0', 'bar@1.4.0' ]], ['#ipsum', [ 'ipsum@npm:sit@1.0.0' ]], - ['#bar > *', [ 'dasherino-sep-pkg@2.0.0' ]], + ['#bar > *', [ 'dasher@2.0.0' ]], [':root > #bar', [ 'bar@2.0.0' ]], [':root > #bar > *', []], - [':root #bar > *', [ 'dasherino-sep-pkg@2.0.0' ]], + [':root #bar > *', [ 'dasher@2.0.0' ]], + [':root #bar@1 ~ *', [ 'dash-separated-pkg@1.0.0' ]], ['#bar@2, #foo', [ 'bar@2.0.0', 'foo@2.2.2' ]], ['#a, #bar@2, #foo', [ 'a@1.0.0', 'bar@2.0.0', 'foo@2.2.2' ]], ]) From 6305cd89c898ae6d82090242939b0a2592d999d8 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Mon, 30 May 2022 09:52:25 -0400 Subject: [PATCH 04/17] wip :not --- lib/commands/query.js | 3 +- workspaces/arborist/lib/query-selector-all.js | 245 +++++++++++------- .../arborist/test/query-selector-all.js | 36 +++ 3 files changed, 195 insertions(+), 89 deletions(-) diff --git a/lib/commands/query.js b/lib/commands/query.js index 42cd26cc7afaa..55cf3b6ff031c 100644 --- a/lib/commands/query.js +++ b/lib/commands/query.js @@ -22,7 +22,8 @@ class Query extends BaseCommand { } const arb = new Arborist(opts) const tree = await arb.loadActual(opts) - const res = JSON.stringify(tree.querySelectorAll(args[0]), null, 2) + const items = await tree.querySelectorAll(args[0]) + const res = JSON.stringify(items, null, 2) return this.npm.output(res) } diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index a51b0597e0f06..033645cb54373 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -22,45 +22,45 @@ const convertInventoryItemsToResponses = inventory => { const fixupIdSpecs = selector => { let foundId = null - selector.walk((nextQueryNode) => { - const appendSelectorAsPartOfId = + selector.walk((nextAstNode) => { + const appendAstNodeAsPartOfId = foundId && ( ( - nextQueryNode.type === 'class' && - /[0-9]|x/.test(String(nextQueryNode)) + nextAstNode.type === 'class' && + /[0-9]|x/.test(String(nextAstNode)) ) || ( - nextQueryNode.type === 'tag' && - /[0-9]|\-/.test(String(nextQueryNode)) + nextAstNode.type === 'tag' && + /[0-9]|\-/.test(String(nextAstNode)) ) || ( ( - String(nextQueryNode) === '||' || - String(nextQueryNode) === ' ' || + String(nextAstNode) === '||' || + String(nextAstNode) === ' ' || ( ( - String(nextQueryNode) === '~' || - String(nextQueryNode) === '>' || - String(nextQueryNode) === '*' + String(nextAstNode) === '~' || + String(nextAstNode) === '>' || + String(nextAstNode) === '*' ) && String(foundId).endsWith('@') ) ) && - (!nextQueryNode.spaces.before && !nextQueryNode.spaces.after) + (!nextAstNode.spaces.before && !nextAstNode.spaces.after) ) ) - if (appendSelectorAsPartOfId) { - foundId.value = `${foundId.value}${String(nextQueryNode)}` - nextQueryNode.remove() + if (appendAstNodeAsPartOfId) { + foundId.value = `${foundId.value}${String(nextAstNode)}` + nextAstNode.remove() } else { foundId = null } const idSpecWithVersion = - nextQueryNode.type === 'id' && - String(nextQueryNode).indexOf('@') > -1 + nextAstNode.type === 'id' && + String(nextAstNode).indexOf('@') > -1 if (idSpecWithVersion) { - foundId = nextQueryNode + foundId = nextAstNode } }) @@ -74,13 +74,37 @@ const querySelectorAll = async (rootNode, query) => { } const ArboristNode = rootNode.constructor - const rootSelector = + const rootAstNode = parser(fixupIdSpecs).astSync(query, { lossless: false }) - let currentSelector = rootSelector - let prevSelector = null + let currentAstNode = rootAstNode + let currentAstSelector = null + let prevAstNode = null let pendingCombinator = null - + let pendingPseudo = new Map() const inventory = [...rootNode.inventory.values()] + // results is going to be a Map in which its values are the + // resulting items returned for each parsed css ast selector + const results = new Map([[rootAstNode, inventory]]) + + const getCurrentResult = () => + results.get(currentAstSelector) + + const updateCurrentResult = (value) => { + console.error('update results', value) + results.set(currentAstSelector, value) + } + + const getSelectorsResult = astNode => { + const acc = new Set() + for (const n of astNode.nodes) { + if (results.has(n)) { + for (const node of results.get(n)) { + acc.add(node) + } + } + } + return acc + } // maps containing the logic to parse each of the supported css selectors const attributeOperatorsMap = new Map(Object.entries({ @@ -191,10 +215,10 @@ const querySelectorAll = async (rootNode, query) => { [...node.edgesIn].some(edge => edge.invalid)) }, ':link' () { - return getInitialItems().filter(node => node.isLink) + return getInitialItems().filter(node => node.isLink || (node.isTop && !node.isRoot)) }, ':missing' () { - const ress = inventory.reduce((res, node) => { + return inventory.reduce((res, node) => { for (const edge of node.edgesOut.values()) { if (edge.missing) { const pkg = { name: edge.name, version: edge.spec } @@ -203,40 +227,62 @@ const querySelectorAll = async (rootNode, query) => { } return res }, []) - return ress + }, + ':not' () { + console.error(':not PSEUDO SELECTOR') + const astNode = currentAstNode + pendingPseudo.set( + astNode, + () => { + console.error('--- process pending pseudo') + const childResult = getSelectorsResult(astNode) + console.error('current results', results.get(astNode.parent)) + console.error('childResult', childResult) + const res = results.get(astNode.parent).filter(node => + !childResult.has(node)) + console.error('filtered res', res) + results.set(astNode.parent, res) + } + ) + return getInitialItems() }, ':private' () { return getInitialItems().filter(node => node.package.private) }, ':root' () { + return [rootNode.root] + }, + ':scope' () { const [rootNode] = inventory return [rootNode] }, })) // retrieves the initial items to which start the filtering / matching - // for most of the different types of css selectors, e.g: class, id, * - // in different contexts we need to start with a different list of items - // from the inventory, for example a query for `.workspace` actually means - // same as `*.workspace` so we want to start with the full inventory in - // that case + // for most of the different types of recognized ast nodes, e.g: class, + // id, *, etc in different contexts we need to start with the current list + // of filtered results, for example a query for `.workspace` actually + // means the same as `*.workspace` so we want to start with the full + // inventory if that's the first ast node we're reading but if it appears + // in the middle of a query it should respect the previous filtered + // results, combinators are a special case in which we always want to + // have the complete inventory list in order to use the left-hand side + // ast node as a filter combined with the element on its right-hand side const getInitialItems = () => { - const attributeFilterQueryNodes = new Set([ - 'attribute', - 'class', - 'id', - 'pseudo', - 'universal', - ]) - const firstParsedSelector = - prevSelector.type === 'selector' && result.length === 0 - - // when parsing the first selector or previous query node parsed - // is a combinator we're going to need the entire inventory in - // order to match items using that operator - return firstParsedSelector || prevSelector.type === 'combinator' + const firstAstNode = + currentAstNode.parent.nodes[0] === currentAstNode + + const firstParsed = firstAstNode && + currentAstNode.parent.parent.type === 'root' + + const firstSelector = firstAstNode && + currentAstNode.parent.type === 'selector' + + return firstParsed || prevAstNode.type === 'combinator' ? inventory - : result + : firstSelector + ? results.get(currentAstNode.parent.parent.parent) + : getCurrentResult() } // combinators need information about previously filtered items along @@ -252,6 +298,17 @@ const querySelectorAll = async (rootNode, query) => { return nextResults } + const processPendingPseudo = (astNode) => { + console.error('processPendingPseudo', pendingPseudo) + if (pendingPseudo.has(astNode)) { + console.error('processPendingPseudo', String(astNode.value)) + const fn = pendingPseudo.get(astNode) + console.error('pending fn', fn.toString()) + results.set(astNode, fn()) + pendingPseudo.delete(astNode) + } + } + // below are the functions containing the logic to // parse each of the recognized css selectors types const attribute = () => { @@ -259,53 +316,53 @@ const querySelectorAll = async (rootNode, query) => { qualifiedAttribute: attribute, operator, value, - } = currentSelector - const prevResult = result + } = currentAstNode + const prevResult = getCurrentResult() + console.error('initial items', getInitialItems()) const nextResult = getInitialItems().filter(node => { return attributeOperatorsMap.get(operator)({ attribute, value, pkg: node.package, })}) - result = processPendingCombinator(prevResult, nextResult) + console.error('attribute next result', nextResult) + updateCurrentResult(processPendingCombinator(prevResult, nextResult)) } const classType = () => { - const prevResult = result + const prevResult = getCurrentResult() const nextResult = - classesMap.get(String(currentSelector))(getInitialItems()) - result = processPendingCombinator(prevResult, nextResult) + classesMap.get(String(currentAstNode))(getInitialItems()) + updateCurrentResult(processPendingCombinator(prevResult, nextResult)) } const combinator = () => { - pendingCombinator = combinatorsMap.get(String(currentSelector)) + pendingCombinator = combinatorsMap.get(String(currentAstNode)) } const id = () => { - const spec = npa(String(currentSelector).slice(1)) - const prevResult = result + const spec = npa(String(currentAstNode).slice(1)) + const prevResult = getCurrentResult() const nextResult = getInitialItems().filter(node => (node.name === spec.name || node.package.name === spec.name) && (semver.satisfies(node.version, spec.fetchSpec) || !spec.rawSpec)) - result = processPendingCombinator(prevResult, nextResult) + updateCurrentResult(processPendingCombinator(prevResult, nextResult)) } const pseudo = () => { - const currentSelectorFn = pseudoMap.get(String(currentSelector)) - if (currentSelectorFn) { - const prevResult = result - const nextResult = currentSelectorFn() - result = processPendingCombinator(prevResult, nextResult) + const currentAstNodeFn = pseudoMap.get(currentAstNode.value) + if (currentAstNodeFn) { + const prevResult = getCurrentResult() + const nextResult = currentAstNodeFn() + updateCurrentResult(processPendingCombinator(prevResult, nextResult)) } } const selector = () => { - // collect results for the current selector - results.push(result) - // starts a new result array for the next selector to use - result = [] + currentAstSelector = currentAstNode + // starts a new array in which resulting items + // can be stored for each given ast selector + updateCurrentResult([]) } const universal = () => { - if (prevSelector.type === 'root' && !result.length) { - result = inventory - } else { - result = processPendingCombinator(result, getInitialItems()) - } + const prevResult = getCurrentResult() + const nextResult = getInitialItems() + updateCurrentResult(processPendingCombinator(prevResult, nextResult)) } // maps each of the recognized css selectors @@ -320,34 +377,46 @@ const querySelectorAll = async (rootNode, query) => { universal, })) - // results is going to be a set of all nodes returned for each - // parsed container selector, the `result` variable tracks the - // current result for each of nested selectors within a query - const results = [] - let result = [] - - // walks through the parsed css query and update - // the result after parsing / executing each selector - // console.error(require('util').inspect(rootSelector, { depth: 10 })) - rootSelector.walk((nextQueryNode) => { - prevSelector = currentSelector - currentSelector = nextQueryNode - // console.error('prevSelector', prevSelector.type, String(prevSelector)) - //console.error('currentSelector', currentSelector.type, String(currentSelector)) + const maybeProcessPendingPseudo = (nextAstNode) => { + const doneParsingAst = !nextAstNode + const doneParsingSelector = () => + nextAstNode.parent !== prevAstNode.parent && + nextAstNode.parent !== prevAstNode + const followingAstNode = () => + pendingPseudo.has(prevAstNode.parent) + const shouldProcessPendingPseudo = + prevAstNode && + (doneParsingAst || doneParsingSelector()) && + followingAstNode() + if (shouldProcessPendingPseudo) { + processPendingPseudo(prevAstNode.parent) + } + } + + // walks through the parsed css query and update the + // current result after parsing / executing each ast node + console.error(require('util').inspect(rootAstNode, { depth: 10 })) + rootAstNode.walk((nextAstNode) => { + // when walking away from a nested selector, make sure to + // execute any pending pseudo selectors associated to it + maybeProcessPendingPseudo(nextAstNode) + + prevAstNode = currentAstNode + currentAstNode = nextAstNode + // console.error('prevAstNode', prevAstNode.type, String(prevAstNode)) + console.error('currentAstNode', currentAstNode.type, String(currentAstNode)) const updateResult = - retrieveByType.get(currentSelector.type) + retrieveByType.get(currentAstNode.type) if (updateResult) { updateResult() } }) - - // append any remaining selector result - results.push(result) + maybeProcessPendingPseudo() // returns normalized json output return JSON.parse(JSON.stringify( - convertInventoryItemsToResponses(results.flatMap(i => i)) + convertInventoryItemsToResponses(getSelectorsResult(rootAstNode)) .sort((a, b) => localeCompare(a.path, b.path)))) } diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 3a4aba54076cc..7b5773af7bd83 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -220,6 +220,40 @@ t.test('query-selector-all', async t => { ['#a :root', []], ]) + // pseudo :not() + await runSpecParsing([ + [':not(#foo)', [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], + [':root > .workspace:not(#b)', [ 'a@1.0.0' ]], + [':root > .workspace > *:not(#bar)', [ 'baz@1.0.0' ]], + ['.bundled ~ :not(.workspace)', [ + 'bar@2.0.0', + 'foo@2.2.2', + 'ipsum@npm:sit@1.0.0', + ]], + ['*:root > *:empty:not(*[name^=a], #b)', [ 'bar@2.0.0', ]], + /* TODO: should this be supported? + * apparently only "simple selectors" are supported in css negation + * ref: https://drafts.csswg.org/selectors/#negation + [':not(:not(:link))', [ + 'a@1.0.0', + 'b@1.0.0', + ]], + */ + ]) + // pseudo miscelaneous await runSpecParsing([ [':empty', [ @@ -249,6 +283,8 @@ t.test('query-selector-all', async t => { // attribute matchers await runSpecParsing([ ['[name=a]', [ 'a@1.0.0' ]], + ['[name=a], [name=b]', [ 'a@1.0.0', 'b@1.0.0' ]], + ['[name=a], *[name=b]', [ 'a@1.0.0', 'b@1.0.0' ]], ['[name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], ['[name|=dash]', [ 'dash-separated-pkg@1.0.0' ]], ['[name$=oo]', [ 'foo@2.2.2' ]], From 2f4a890d9b0b911fbc37b5f746c22de760cd64bc Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Mon, 30 May 2022 17:56:13 -0400 Subject: [PATCH 05/17] added :scope and ~= attr matcher --- workspaces/arborist/lib/query-selector-all.js | 14 +++++++------ .../arborist/test/query-selector-all.js | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 033645cb54373..595141abe1463 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -66,14 +66,14 @@ const fixupIdSpecs = selector => { }) } -const querySelectorAll = async (rootNode, query) => { +const querySelectorAll = async (targetNode, query) => { // if query is an empty string or any falsy // value, just returns an empty result if (!query) { return [] } - const ArboristNode = rootNode.constructor + const ArboristNode = targetNode.constructor const rootAstNode = parser(fixupIdSpecs).astSync(query, { lossless: false }) let currentAstNode = rootAstNode @@ -81,7 +81,7 @@ const querySelectorAll = async (rootNode, query) => { let prevAstNode = null let pendingCombinator = null let pendingPseudo = new Map() - const inventory = [...rootNode.inventory.values()] + const inventory = [...targetNode.root.inventory.values()] // results is going to be a Map in which its values are the // resulting items returned for each parsed css ast selector const results = new Map([[rootAstNode, inventory]]) @@ -109,6 +109,9 @@ const querySelectorAll = async (rootNode, query) => { // maps containing the logic to parse each of the supported css selectors const attributeOperatorsMap = new Map(Object.entries({ '=' ({ attribute, value, pkg }) { return pkg[attribute] === value }, + '~=' ({ attribute, value, pkg }) { + return String(pkg[attribute]).match(/\w+/g).includes(value) + }, '*=' ({ attribute, value, pkg }) { return pkg[attribute].indexOf(value) > -1 }, @@ -250,11 +253,10 @@ const querySelectorAll = async (rootNode, query) => { return getInitialItems().filter(node => node.package.private) }, ':root' () { - return [rootNode.root] + return [targetNode.root] }, ':scope' () { - const [rootNode] = inventory - return [rootNode] + return [targetNode] }, })) diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 7b5773af7bd83..252b1d14836ca 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -89,12 +89,14 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'dash-separated-pkg', version: '1.0.0', + description: 'One of the best libraries every dev should know about.' }), }, 'dasher': { 'package.json': JSON.stringify({ name: 'dasher', version: '2.0.0', + description: 'The best library ever.', }), }, ipsum: { @@ -153,6 +155,18 @@ t.test('query-selector-all', async t => { console.error(require('child_process').execSync('tree', { cwd: path }).toString()) //console.error(require('child_process').execSync('npm ls --all', { cwd: path }).toString()) + // :scope pseudo-class + const nodeFoo = [...tree.edgesOut.values()] + .find(node => node.name === 'foo').to + const scopeRes = await querySelectorAll(nodeFoo, ':scope') + t.same(scopeRes, [ 'foo@2.2.2' ], ':scope') + + const scopeChildren = await querySelectorAll(nodeFoo, ':scope > *') + t.same(scopeChildren, [ + 'dash-separated-pkg@1.0.0', + 'bar@1.4.0', + ], ':scope > *') + const runSpecParsing = async testCase => { for (const [selector, expected] of testCase) { const res = await querySelectorAll(tree, selector) @@ -199,6 +213,7 @@ t.test('query-selector-all', async t => { // pseudo :root await runSpecParsing([ [':root', ['query-selector-all-tests@1.0.0']], + [':scope', ['query-selector-all-tests@1.0.0']], // same as root in this context [':root > *', [ 'a@1.0.0', 'b@1.0.0', @@ -288,6 +303,11 @@ t.test('query-selector-all', async t => { ['[name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], ['[name|=dash]', [ 'dash-separated-pkg@1.0.0' ]], ['[name$=oo]', [ 'foo@2.2.2' ]], + ['[description~=ever]', [ 'dasher@2.0.0' ]], + ['[description~=best]', [ + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + ]], ['[name*=a]', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', From b9e30d2afc27a108f6eaecf3e4f78340c5dc44b0 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Mon, 30 May 2022 18:19:40 -0400 Subject: [PATCH 06/17] added default attribute matcher --- workspaces/arborist/lib/query-selector-all.js | 3 +- .../arborist/test/query-selector-all.js | 45 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 595141abe1463..4e4495655970b 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -108,6 +108,7 @@ const querySelectorAll = async (targetNode, query) => { // maps containing the logic to parse each of the supported css selectors const attributeOperatorsMap = new Map(Object.entries({ + '' ({ attribute, value, pkg }) { return Boolean(pkg[attribute]) }, '=' ({ attribute, value, pkg }) { return pkg[attribute] === value }, '~=' ({ attribute, value, pkg }) { return String(pkg[attribute]).match(/\w+/g).includes(value) @@ -316,7 +317,7 @@ const querySelectorAll = async (targetNode, query) => { const attribute = () => { const { qualifiedAttribute: attribute, - operator, + operator = '', value, } = currentAstNode const prevResult = getCurrentResult() diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 252b1d14836ca..a6a6c52777408 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -178,8 +178,8 @@ t.test('query-selector-all', async t => { } } - // universal selector await runSpecParsing([ + // universal selector ['*', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', @@ -208,10 +208,8 @@ t.test('query-selector-all', async t => { 'ipsum@npm:sit@1.0.0', 'lorem@1.0.0', ]], - ]) - // pseudo :root - await runSpecParsing([ + // pseudo :root [':root', ['query-selector-all-tests@1.0.0']], [':scope', ['query-selector-all-tests@1.0.0']], // same as root in this context [':root > *', [ @@ -233,10 +231,8 @@ t.test('query-selector-all', async t => { ['* :root', []], [':root, :root', ['query-selector-all-tests@1.0.0']], ['#a :root', []], - ]) - // pseudo :not() - await runSpecParsing([ + // pseudo :not() [':not(#foo)', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', @@ -267,10 +263,8 @@ t.test('query-selector-all', async t => { 'b@1.0.0', ]], */ - ]) - // pseudo miscelaneous - await runSpecParsing([ + // pseudo miscelaneous [':empty', [ 'a@1.0.0', 'b@1.0.0', @@ -293,16 +287,33 @@ t.test('query-selector-all', async t => { [':link', [ 'a@1.0.0', 'b@1.0.0' ]], [':missing', [ 'missing-dep@^1.0.0' ]], [':private', [ 'b@1.0.0' ]], - ]) - // attribute matchers - await runSpecParsing([ + // attribute matchers + ['[name]', [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], ['[name=a]', [ 'a@1.0.0' ]], ['[name=a], [name=b]', [ 'a@1.0.0', 'b@1.0.0' ]], ['[name=a], *[name=b]', [ 'a@1.0.0', 'b@1.0.0' ]], ['[name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], ['[name|=dash]', [ 'dash-separated-pkg@1.0.0' ]], ['[name$=oo]', [ 'foo@2.2.2' ]], + ['[description]', [ + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + ]], ['[description~=ever]', [ 'dasher@2.0.0' ]], ['[description~=best]', [ 'dash-separated-pkg@1.0.0', @@ -319,10 +330,8 @@ t.test('query-selector-all', async t => { 'dasher@2.0.0', 'bar@1.4.0', ]], - ]) - // classes - await runSpecParsing([ + // classes ['.workspace', [ 'a@1.0.0', 'b@1.0.0' ]], ['.workspace > *', [ 'bar@2.0.0', 'baz@1.0.0' ]], ['.dev', [ 'foo@2.2.2' ]], @@ -337,10 +346,8 @@ t.test('query-selector-all', async t => { 'foo@2.2.2', 'ipsum@npm:sit@1.0.0', ]], - ]) - // id selector - await runSpecParsing([ + // id selector ['#bar', [ 'bar@2.0.0', 'bar@1.4.0' ]], ['#bar@2', [ 'bar@2.0.0' ]], ['#bar@2.0', [ 'bar@2.0.0' ]], From fbf99e0548adb2d90d7040e9d1e448c5bd4719f4 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Tue, 31 May 2022 16:29:53 -0400 Subject: [PATCH 07/17] added semver pseudo --- workspaces/arborist/lib/query-selector-all.js | 23 ++++++-- .../arborist/test/query-selector-all.js | 56 +++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 4e4495655970b..a1d5666eeb063 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -61,11 +61,23 @@ const fixupIdSpecs = selector => { String(nextAstNode).indexOf('@') > -1 if (idSpecWithVersion) { foundId = nextAstNode + } else if (nextAstNode.value === ':semver') { + fixupSemverSpecs(nextAstNode) } - }) } +const fixupSemverSpecs = astNode => { + if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { + const children = astNode.nodes[0].nodes + const value = children.reduce((res, i) => + `${res}${String(i)}`, '') + + astNode.semverValue = value + astNode.nodes.length = 0 + } +} + const querySelectorAll = async (targetNode, query) => { // if query is an empty string or any falsy // value, just returns an empty result @@ -233,18 +245,13 @@ const querySelectorAll = async (targetNode, query) => { }, []) }, ':not' () { - console.error(':not PSEUDO SELECTOR') const astNode = currentAstNode pendingPseudo.set( astNode, () => { - console.error('--- process pending pseudo') const childResult = getSelectorsResult(astNode) - console.error('current results', results.get(astNode.parent)) - console.error('childResult', childResult) const res = results.get(astNode.parent).filter(node => !childResult.has(node)) - console.error('filtered res', res) results.set(astNode.parent, res) } ) @@ -259,6 +266,10 @@ const querySelectorAll = async (targetNode, query) => { ':scope' () { return [targetNode] }, + ':semver' () { + return getInitialItems().filter(node => + semver.satisfies(node.version, currentAstNode.semverValue)) + }, })) // retrieves the initial items to which start the filtering / matching diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index a6a6c52777408..7d1dd997d4c76 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -288,6 +288,62 @@ t.test('query-selector-all', async t => { [':missing', [ 'missing-dep@^1.0.0' ]], [':private', [ 'b@1.0.0' ]], + // semver pseudo-class + [':semver()', [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], + [':semver(*)', [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], + [':semver(2.0.0)', [ + '@npmcli/abbrev@2.0.0', + 'bar@2.0.0', + 'dasher@2.0.0', + ]], + [':semver(>=2)', [ + '@npmcli/abbrev@2.0.0', + 'bar@2.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + ]], + [':semver(~2.0.x)', [ + '@npmcli/abbrev@2.0.0', + 'bar@2.0.0', + 'dasher@2.0.0', + ]], + [':semver(2 - 3)', [ + '@npmcli/abbrev@2.0.0', + 'bar@2.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + ]], + [':semver(=1.4.0)', [ 'bar@1.4.0' ]], + [':semver(1.4.0 || 2.2.2)', [ 'foo@2.2.2', 'bar@1.4.0' ]], + // attribute matchers ['[name]', [ 'query-selector-all-tests@1.0.0', From c83ec4e7c66f09ed5967102a4e51517cd9ded24f Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 1 Jun 2022 00:47:10 -0400 Subject: [PATCH 08/17] id parsing refactor --- workspaces/arborist/lib/query-selector-all.js | 112 +++++++++--------- .../arborist/test/query-selector-all.js | 68 ++++++----- 2 files changed, 89 insertions(+), 91 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index a1d5666eeb063..b84320d3bf1c8 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -20,64 +20,66 @@ const convertInventoryItemsToResponses = inventory => { return responses } -const fixupIdSpecs = selector => { - let foundId = null - selector.walk((nextAstNode) => { - const appendAstNodeAsPartOfId = - foundId && - ( - ( - nextAstNode.type === 'class' && - /[0-9]|x/.test(String(nextAstNode)) - ) || ( - nextAstNode.type === 'tag' && - /[0-9]|\-/.test(String(nextAstNode)) - ) || - ( - ( - String(nextAstNode) === '||' || - String(nextAstNode) === ' ' || - ( - ( - String(nextAstNode) === '~' || - String(nextAstNode) === '>' || - String(nextAstNode) === '*' - ) && String(foundId).endsWith('@') - ) - ) && - (!nextAstNode.spaces.before && !nextAstNode.spaces.after) - ) +const fixupIds = astNode => { + const { name, rawSpec: part } = npa(astNode.value) + let versionParts = [{ astNode, part }] + let currentAstNode = astNode.next() + + if (!part) { + return + } + + while (currentAstNode) { + versionParts.push({ astNode: currentAstNode, part: String(currentAstNode) }) + currentAstNode = currentAstNode.next() + } + + let version + let length = versionParts.length; + while (!version && length) { + version = + semver.valid( + versionParts.slice(0, length).reduce((res, i) => `${res}${i.part}`, '') ) + length-- + } - if (appendAstNodeAsPartOfId) { - foundId.value = `${foundId.value}${String(nextAstNode)}` - nextAstNode.remove() - } else { - foundId = null - } + astNode.value = `${name}@${version}` - const idSpecWithVersion = - nextAstNode.type === 'id' && - String(nextAstNode).indexOf('@') > -1 - if (idSpecWithVersion) { - foundId = nextAstNode - } else if (nextAstNode.value === ':semver') { - fixupSemverSpecs(nextAstNode) - } - }) + for (let i = 1; i <= length; i++) { + versionParts[i].astNode.remove() + } } const fixupSemverSpecs = astNode => { if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { const children = astNode.nodes[0].nodes - const value = children.reduce((res, i) => - `${res}${String(i)}`, '') + const value = children.reduce((res, i) => `${res}${String(i)}`, '') astNode.semverValue = value astNode.nodes.length = 0 } } +// a few of the supported ast nodes need to be tweaked in order to properly be +// interpreted as proper arborist query selectors, namely semver ranges from +// both ids and :semver pseudo-class selectors need to be translated from what +// are usually multiple ast nodes, such as: tag:1, class:.0, class:.0 to a +// single `1.0.0` value, other pseudo-class selectors also get preprocessed in +// order to make it simpler to execute later when traversing each ast node +// using rootNode.walk(), such as :path, :type, etc. transformAst handles all +// these modifications to the parsed ast by doing an extra, initial traversal +// of the parsed ast from the query and modifying the parsed nodes accordingly +const transformAst = selector => { + selector.walk((nextAstNode) => { + if (nextAstNode.type === 'id') { + fixupIds(nextAstNode) + } else if (nextAstNode.value === ':semver') { + fixupSemverSpecs(nextAstNode) + } + }) +} + const querySelectorAll = async (targetNode, query) => { // if query is an empty string or any falsy // value, just returns an empty result @@ -87,7 +89,8 @@ const querySelectorAll = async (targetNode, query) => { const ArboristNode = targetNode.constructor const rootAstNode = - parser(fixupIdSpecs).astSync(query, { lossless: false }) + parser(transformAst, { lossless: false }).astSync(query, { lossless: false }) + // console.error(require('util').inspect(rootAstNode, { depth: 10 })) let currentAstNode = rootAstNode let currentAstSelector = null let prevAstNode = null @@ -102,7 +105,6 @@ const querySelectorAll = async (targetNode, query) => { results.get(currentAstSelector) const updateCurrentResult = (value) => { - console.error('update results', value) results.set(currentAstSelector, value) } @@ -261,10 +263,10 @@ const querySelectorAll = async (targetNode, query) => { return getInitialItems().filter(node => node.package.private) }, ':root' () { - return [targetNode.root] + return getInitialItems().filter(node => node === targetNode.root) }, ':scope' () { - return [targetNode] + return getInitialItems().filter(node => node === targetNode) }, ':semver' () { return getInitialItems().filter(node => @@ -313,11 +315,11 @@ const querySelectorAll = async (targetNode, query) => { } const processPendingPseudo = (astNode) => { - console.error('processPendingPseudo', pendingPseudo) + //console.error('processPendingPseudo', pendingPseudo) if (pendingPseudo.has(astNode)) { - console.error('processPendingPseudo', String(astNode.value)) + //console.error('processPendingPseudo', String(astNode.value)) const fn = pendingPseudo.get(astNode) - console.error('pending fn', fn.toString()) + //console.error('pending fn', fn.toString()) results.set(astNode, fn()) pendingPseudo.delete(astNode) } @@ -332,14 +334,12 @@ const querySelectorAll = async (targetNode, query) => { value, } = currentAstNode const prevResult = getCurrentResult() - console.error('initial items', getInitialItems()) const nextResult = getInitialItems().filter(node => { return attributeOperatorsMap.get(operator)({ attribute, value, pkg: node.package, })}) - console.error('attribute next result', nextResult) updateCurrentResult(processPendingCombinator(prevResult, nextResult)) } const classType = () => { @@ -352,7 +352,7 @@ const querySelectorAll = async (targetNode, query) => { pendingCombinator = combinatorsMap.get(String(currentAstNode)) } const id = () => { - const spec = npa(String(currentAstNode).slice(1)) + const spec = npa(currentAstNode.value) const prevResult = getCurrentResult() const nextResult = getInitialItems().filter(node => (node.name === spec.name || node.package.name === spec.name) && @@ -409,7 +409,7 @@ const querySelectorAll = async (targetNode, query) => { // walks through the parsed css query and update the // current result after parsing / executing each ast node - console.error(require('util').inspect(rootAstNode, { depth: 10 })) + // console.error(require('util').inspect(rootAstNode, { depth: 10 })) rootAstNode.walk((nextAstNode) => { // when walking away from a nested selector, make sure to // execute any pending pseudo selectors associated to it @@ -418,7 +418,7 @@ const querySelectorAll = async (targetNode, query) => { prevAstNode = currentAstNode currentAstNode = nextAstNode // console.error('prevAstNode', prevAstNode.type, String(prevAstNode)) - console.error('currentAstNode', currentAstNode.type, String(currentAstNode)) + // console.error('currentAstNode', currentAstNode.type, String(currentAstNode)) const updateResult = retrieveByType.get(currentAstNode.type) diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 7d1dd997d4c76..ca3fc3ecf5692 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -40,7 +40,7 @@ t.test('query-selector-all', async t => { abbrev: { 'package.json': JSON.stringify({ name: '@npmcli/abbrev', - version: '2.0.0' + version: '2.0.0-beta.45' }), }, }, @@ -184,7 +184,7 @@ t.test('query-selector-all', async t => { 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', - '@npmcli/abbrev@2.0.0', + '@npmcli/abbrev@2.0.0-beta.45', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -230,14 +230,16 @@ t.test('query-selector-all', async t => { ['* > :root', []], ['* :root', []], [':root, :root', ['query-selector-all-tests@1.0.0']], - ['#a :root', []], + ['#a *:root', []], + ['#a > :root', []], + ['#a ~ :root', []], // pseudo :not() [':not(#foo)', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', - '@npmcli/abbrev@2.0.0', + '@npmcli/abbrev@2.0.0-beta.45', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -268,7 +270,7 @@ t.test('query-selector-all', async t => { [':empty', [ 'a@1.0.0', 'b@1.0.0', - '@npmcli/abbrev@2.0.0', + '@npmcli/abbrev@2.0.0-beta.45', 'abbrev@1.1.1', 'bar@2.0.0', 'dash-separated-pkg@1.0.0', @@ -281,7 +283,7 @@ t.test('query-selector-all', async t => { 'abbrev@1.1.1', 'bar@2.0.0', ]], - [':extraneous', [ '@npmcli/abbrev@2.0.0' ]], + [':extraneous', [ '@npmcli/abbrev@2.0.0-beta.45' ]], [':invalid', [ 'lorem@1.0.0' ]], [':link', [ 'a@1.0.0', 'b@1.0.0' ]], [':link', [ 'a@1.0.0', 'b@1.0.0' ]], @@ -293,7 +295,6 @@ t.test('query-selector-all', async t => { 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', - '@npmcli/abbrev@2.0.0', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -308,7 +309,6 @@ t.test('query-selector-all', async t => { 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', - '@npmcli/abbrev@2.0.0', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -320,23 +320,19 @@ t.test('query-selector-all', async t => { 'lorem@1.0.0', ]], [':semver(2.0.0)', [ - '@npmcli/abbrev@2.0.0', 'bar@2.0.0', 'dasher@2.0.0', ]], [':semver(>=2)', [ - '@npmcli/abbrev@2.0.0', 'bar@2.0.0', 'dasher@2.0.0', 'foo@2.2.2', ]], [':semver(~2.0.x)', [ - '@npmcli/abbrev@2.0.0', 'bar@2.0.0', 'dasher@2.0.0', ]], [':semver(2 - 3)', [ - '@npmcli/abbrev@2.0.0', 'bar@2.0.0', 'dasher@2.0.0', 'foo@2.2.2', @@ -349,7 +345,7 @@ t.test('query-selector-all', async t => { 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', - '@npmcli/abbrev@2.0.0', + '@npmcli/abbrev@2.0.0-beta.45', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -378,7 +374,7 @@ t.test('query-selector-all', async t => { ['[name*=a]', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', - '@npmcli/abbrev@2.0.0', + '@npmcli/abbrev@2.0.0-beta.45', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -405,32 +401,34 @@ t.test('query-selector-all', async t => { // id selector ['#bar', [ 'bar@2.0.0', 'bar@1.4.0' ]], - ['#bar@2', [ 'bar@2.0.0' ]], - ['#bar@2.0', [ 'bar@2.0.0' ]], ['#bar@2.0.0', [ 'bar@2.0.0' ]], - ['#bar@^2.0.0', [ 'bar@2.0.0' ]], - ['#bar@~2.0.0', [ 'bar@2.0.0' ]], - ['#bar@=2.0.0', [ 'bar@2.0.0' ]], - ['#bar@>=2.0.0', [ 'bar@2.0.0' ]], - ['#bar@<3.0.0', [ 'bar@2.0.0', 'bar@1.4.0' ]], - ['#bar@2 - 3', [ 'bar@2.0.0' ]], - ['#bar@2.0.0 - 3.0.0', [ 'bar@2.0.0' ]], - ['#bar@*', [ 'bar@2.0.0', 'bar@1.4.0' ]], - ['#bar@^2.0.0-beta.0', [ 'bar@2.0.0' ]], - ['#bar@>1.5.0 <3.0.0', [ 'bar@2.0.0' ]], - ['#bar@2.x', [ 'bar@2.0.0' ]], - ['#bar@2.x.x', [ 'bar@2.0.0' ]], - ['#bar@1||2', [ 'bar@2.0.0', 'bar@1.4.0' ]], - ['#bar@1 || 2', [ 'bar@2.0.0', 'bar@1.4.0' ]], - ['#bar@1 || 2.0.0', [ 'bar@2.0.0', 'bar@1.4.0' ]], - ['#bar@1.4.0 || 2', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#@npmcli\\/abbrev@2.0.0-beta.45', [ '@npmcli/abbrev@2.0.0-beta.45' ]], + ['#bar:semver(2.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(2)', [ 'bar@2.0.0' ]], + ['#bar:semver(^2.0.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(~2.0.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(=2.0.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(>=2.0.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(<3.0.0)', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar:semver(2 - 3)', [ 'bar@2.0.0' ]], + ['#bar:semver(2.0.0 - 3.0.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(*)', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar:semver(^2.0.0-beta.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(>1.5.0 <3.0.0)', [ 'bar@2.0.0' ]], + ['#bar:semver(2.x)', [ 'bar@2.0.0' ]], + ['#bar:semver(2.x.x)', [ 'bar@2.0.0' ]], + ['#bar:semver(1||2)', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar:semver(1 || 2)', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar:semver(1 || 2.0.0)', [ 'bar@2.0.0', 'bar@1.4.0' ]], + ['#bar:semver(1.4.0 || 2)', [ 'bar@2.0.0', 'bar@1.4.0' ]], ['#ipsum', [ 'ipsum@npm:sit@1.0.0' ]], ['#bar > *', [ 'dasher@2.0.0' ]], [':root > #bar', [ 'bar@2.0.0' ]], [':root > #bar > *', []], [':root #bar > *', [ 'dasher@2.0.0' ]], - [':root #bar@1 ~ *', [ 'dash-separated-pkg@1.0.0' ]], - ['#bar@2, #foo', [ 'bar@2.0.0', 'foo@2.2.2' ]], - ['#a, #bar@2, #foo', [ 'a@1.0.0', 'bar@2.0.0', 'foo@2.2.2' ]], + [':root #bar:semver(1) > *', [ 'dasher@2.0.0' ]], + [':root #bar:semver(1) ~ *', [ 'dash-separated-pkg@1.0.0' ]], + ['#bar:semver(2), #foo', [ 'bar@2.0.0', 'foo@2.2.2' ]], + ['#a, #bar:semver(2), #foo@2.2.2', [ 'a@1.0.0', 'bar@2.0.0', 'foo@2.2.2' ]], ]) }) From 7d0e8e27ae639269247428cde1064427ad8d680d Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 1 Jun 2022 01:25:28 -0400 Subject: [PATCH 09/17] add type pseudo --- workspaces/arborist/lib/query-selector-all.js | 19 +++++++++++++++++++ .../arborist/test/query-selector-all.js | 15 +++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index b84320d3bf1c8..b61e4bddd2044 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -61,6 +61,16 @@ const fixupSemverSpecs = astNode => { } } +const fixupTypes = astNode => { + if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { + const [valueAstNode] = astNode.nodes[0].nodes + const { value } = valueAstNode + + astNode.typeValue = value + astNode.nodes.length = 0 + } +} + // a few of the supported ast nodes need to be tweaked in order to properly be // interpreted as proper arborist query selectors, namely semver ranges from // both ids and :semver pseudo-class selectors need to be translated from what @@ -76,6 +86,8 @@ const transformAst = selector => { fixupIds(nextAstNode) } else if (nextAstNode.value === ':semver') { fixupSemverSpecs(nextAstNode) + } else if (nextAstNode.value === ':type') { + fixupTypes(nextAstNode) } }) } @@ -272,6 +284,13 @@ const querySelectorAll = async (targetNode, query) => { return getInitialItems().filter(node => semver.satisfies(node.version, currentAstNode.semverValue)) }, + ':type' () { + return getInitialItems() + .flatMap(node => [...node.edgesIn]) + .filter(edge => npa(`${edge.name}@${edge.spec}`).type === currentAstNode.typeValue) + .map(edge => edge.to) + .filter(Boolean) + }, })) // retrieves the initial items to which start the filtering / matching diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index ca3fc3ecf5692..298c2b51a1256 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -60,7 +60,7 @@ t.test('query-selector-all', async t => { name: 'baz', version: '1.0.0', dependencies: { - lorem: '^2.0.0', + lorem: 'latest', }, }), }, @@ -71,7 +71,7 @@ t.test('query-selector-all', async t => { name: 'bar', version: '1.4.0', peerDependencies: { - 'dasher': '2.0.0', + dasher: '2.0.0', }, }), }, @@ -289,6 +289,17 @@ t.test('query-selector-all', async t => { [':link', [ 'a@1.0.0', 'b@1.0.0' ]], [':missing', [ 'missing-dep@^1.0.0' ]], [':private', [ 'b@1.0.0' ]], + [':type(tag)', [ 'lorem@1.0.0' ]], + [':type(alias)', [ 'ipsum@npm:sit@1.0.0' ]], + [':type(range)', [ + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + ]], + [':type(git)', []], // semver pseudo-class [':semver()', [ From f27e58b5323a5269aa30a95489750a696e7521a4 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 1 Jun 2022 21:53:45 -0400 Subject: [PATCH 10/17] add :path pseudo --- node_modules/minimatch/minimatch.js | 5 ++++ node_modules/minimatch/package.json | 2 +- package-lock.json | 16 +++++----- workspaces/arborist/lib/query-selector-all.js | 29 +++++++++++++++++-- workspaces/arborist/package.json | 1 + .../arborist/test/query-selector-all.js | 20 ++++++++++++- 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/node_modules/minimatch/minimatch.js b/node_modules/minimatch/minimatch.js index f3b491dd1073e..71c96a1fb71cc 100644 --- a/node_modules/minimatch/minimatch.js +++ b/node_modules/minimatch/minimatch.js @@ -168,6 +168,11 @@ class Minimatch { this.options = options this.set = [] this.pattern = pattern + this.windowsPathsNoEscape = !!options.windowsPathsNoEscape || + options.allowWindowsEscape === false + if (this.windowsPathsNoEscape) { + this.pattern = this.pattern.replace(/\\/g, '/') + } this.regexp = null this.negate = false this.comment = false diff --git a/node_modules/minimatch/package.json b/node_modules/minimatch/package.json index 2cc856968c0b2..8e1a84285d38f 100644 --- a/node_modules/minimatch/package.json +++ b/node_modules/minimatch/package.json @@ -2,7 +2,7 @@ "author": "Isaac Z. Schlueter (http://blog.izs.me)", "name": "minimatch", "description": "a glob matcher in javascript", - "version": "5.0.1", + "version": "5.1.0", "repository": { "type": "git", "url": "git://github.com/isaacs/minimatch.git" diff --git a/package-lock.json b/package-lock.json index 27f49b841dc44..5a425682d2683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4762,9 +4762,9 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "inBundle": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10022,6 +10022,7 @@ "common-ancestor-path": "^1.0.1", "json-parse-even-better-errors": "^2.3.1", "json-stringify-nice": "^1.1.4", + "minimatch": "^5.1.0", "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "nopt": "^5.0.0", @@ -10748,6 +10749,7 @@ "json-parse-even-better-errors": "^2.3.1", "json-stringify-nice": "^1.1.4", "minify-registry-metadata": "^2.1.0", + "minimatch": "*", "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "nock": "^13.2.0", @@ -10759,7 +10761,7 @@ "npmlog": "^6.0.2", "pacote": "^13.0.5", "parse-conflict-json": "^2.0.1", - "postcss-selector-parser": "*", + "postcss-selector-parser": "^6.0.10", "proc-log": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", @@ -13387,9 +13389,9 @@ "dev": true }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "requires": { "brace-expansion": "^2.0.1" } diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index b61e4bddd2044..e7abee651cd7d 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -1,8 +1,10 @@ 'use strict' +const { resolve } = require('path') const localeCompare = require('@isaacs/string-locale-compare')('en') const parser = require('postcss-selector-parser') const npa = require('npm-package-arg') +const minimatch = require('minimatch') const semver = require('semver') const QuerySelectorAllResponse = require('./query-selector-all-response.js') @@ -20,9 +22,15 @@ const convertInventoryItemsToResponses = inventory => { return responses } +const escapeSlashes = str => + str.replace(/\//g, '\\/') + +const unescapeSlashes = str => + str.replace(/\\\//g, '/') + const fixupIds = astNode => { const { name, rawSpec: part } = npa(astNode.value) - let versionParts = [{ astNode, part }] + const versionParts = [{ astNode, part }] let currentAstNode = astNode.next() if (!part) { @@ -71,6 +79,13 @@ const fixupTypes = astNode => { } } +const fixupPaths = astNode => { + if (astNode.nodes && astNode.nodes[0]) { + astNode.pathValue = unescapeSlashes(String(astNode.nodes[0])) + astNode.nodes.length = 0 + } +} + // a few of the supported ast nodes need to be tweaked in order to properly be // interpreted as proper arborist query selectors, namely semver ranges from // both ids and :semver pseudo-class selectors need to be translated from what @@ -84,6 +99,8 @@ const transformAst = selector => { selector.walk((nextAstNode) => { if (nextAstNode.type === 'id') { fixupIds(nextAstNode) + } else if (nextAstNode.value === ':path') { + fixupPaths(nextAstNode) } else if (nextAstNode.value === ':semver') { fixupSemverSpecs(nextAstNode) } else if (nextAstNode.value === ':type') { @@ -100,8 +117,9 @@ const querySelectorAll = async (targetNode, query) => { } const ArboristNode = targetNode.constructor + // TODO: escape any forward slashes / const rootAstNode = - parser(transformAst, { lossless: false }).astSync(query, { lossless: false }) + parser(transformAst, { lossless: false }).astSync(escapeSlashes(query), { lossless: false }) // console.error(require('util').inspect(rootAstNode, { depth: 10 })) let currentAstNode = rootAstNode let currentAstSelector = null @@ -271,6 +289,13 @@ const querySelectorAll = async (targetNode, query) => { ) return getInitialItems() }, + ':path' () { + return getInitialItems().filter(node => + minimatch( + node.realpath, + resolve(node.root.realpath, currentAstNode.pathValue) + )) + }, ':private' () { return getInitialItems().filter(node => node.package.private) }, diff --git a/workspaces/arborist/package.json b/workspaces/arborist/package.json index e5e25143675b1..f18c45e319ce2 100644 --- a/workspaces/arborist/package.json +++ b/workspaces/arborist/package.json @@ -17,6 +17,7 @@ "common-ancestor-path": "^1.0.1", "json-parse-even-better-errors": "^2.3.1", "json-stringify-nice": "^1.1.4", + "minimatch": "^5.1.0", "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "nopt": "^5.0.0", diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 298c2b51a1256..f903054481116 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -301,6 +301,23 @@ t.test('query-selector-all', async t => { ]], [':type(git)', []], + // path pseudo-class + [':path(node_modules/*)', [ + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], + [':path(node_modules/bar)', [ 'bar@2.0.0' ]], + [':path(./node_modules/bar)', [ 'bar@2.0.0' ]], + [':path(node_modules/foo/node_modules/bar)', [ 'bar@1.4.0' ]], + [':path(**/bar)', [ 'bar@2.0.0', 'bar@1.4.0' ]], + [':path(*)', [ 'a@1.0.0', 'b@1.0.0' ]], + // semver pseudo-class [':semver()', [ 'query-selector-all-tests@1.0.0', @@ -368,6 +385,7 @@ t.test('query-selector-all', async t => { 'lorem@1.0.0', ]], ['[name=a]', [ 'a@1.0.0' ]], + ['[name=@npmcli/abbrev]', [ '@npmcli/abbrev@2.0.0-beta.45' ]], ['[name=a], [name=b]', [ 'a@1.0.0', 'b@1.0.0' ]], ['[name=a], *[name=b]', [ 'a@1.0.0', 'b@1.0.0' ]], ['[name^=a]', [ 'a@1.0.0', 'abbrev@1.1.1' ]], @@ -413,7 +431,7 @@ t.test('query-selector-all', async t => { // id selector ['#bar', [ 'bar@2.0.0', 'bar@1.4.0' ]], ['#bar@2.0.0', [ 'bar@2.0.0' ]], - ['#@npmcli\\/abbrev@2.0.0-beta.45', [ '@npmcli/abbrev@2.0.0-beta.45' ]], + ['#@npmcli/abbrev@2.0.0-beta.45', [ '@npmcli/abbrev@2.0.0-beta.45' ]], ['#bar:semver(2.0)', [ 'bar@2.0.0' ]], ['#bar:semver(2)', [ 'bar@2.0.0' ]], ['#bar:semver(^2.0.0)', [ 'bar@2.0.0' ]], From 2f6364d56eb8a24ea91a5b97bd6ee7bc599b87b9 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Thu, 2 Jun 2022 11:37:40 -0400 Subject: [PATCH 11/17] add check for :has pseudo-class --- .../arborist/test/query-selector-all.js | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index f903054481116..20110fb8c582e 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -234,7 +234,31 @@ t.test('query-selector-all', async t => { ['#a > :root', []], ['#a ~ :root', []], - // pseudo :not() + // pseudo miscelaneous + [':empty', [ + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0-beta.45', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'lorem@1.0.0', + ]], + [':root > :empty', [ + 'a@1.0.0', + 'b@1.0.0', + 'abbrev@1.1.1', + 'bar@2.0.0', + ]], + [':extraneous', [ '@npmcli/abbrev@2.0.0-beta.45' ]], + [':invalid', [ 'lorem@1.0.0' ]], + [':link', [ 'a@1.0.0', 'b@1.0.0' ]], + [':link', [ 'a@1.0.0', 'b@1.0.0' ]], + [':missing', [ 'missing-dep@^1.0.0' ]], + [':private', [ 'b@1.0.0' ]], + + // :not pseudo-class [':not(#foo)', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', @@ -266,29 +290,10 @@ t.test('query-selector-all', async t => { ]], */ - // pseudo miscelaneous - [':empty', [ - 'a@1.0.0', - 'b@1.0.0', - '@npmcli/abbrev@2.0.0-beta.45', - 'abbrev@1.1.1', - 'bar@2.0.0', - 'dash-separated-pkg@1.0.0', - 'dasher@2.0.0', - 'lorem@1.0.0', - ]], - [':root > :empty', [ - 'a@1.0.0', - 'b@1.0.0', - 'abbrev@1.1.1', - 'bar@2.0.0', - ]], - [':extraneous', [ '@npmcli/abbrev@2.0.0-beta.45' ]], - [':invalid', [ 'lorem@1.0.0' ]], - [':link', [ 'a@1.0.0', 'b@1.0.0' ]], - [':link', [ 'a@1.0.0', 'b@1.0.0' ]], - [':missing', [ 'missing-dep@^1.0.0' ]], - [':private', [ 'b@1.0.0' ]], + // has pseudo-class + ['*:has(> #bar@1.4.0)', [ 'foo@2.2.2' ]], + + // type pseudo-class [':type(tag)', [ 'lorem@1.0.0' ]], [':type(alias)', [ 'ipsum@npm:sit@1.0.0' ]], [':type(range)', [ From 06546f79b1bb1ff68d28b68d35aa765594fa2c9b Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Fri, 3 Jun 2022 11:35:04 -0400 Subject: [PATCH 12/17] added :has pseudo --- workspaces/arborist/lib/query-selector-all.js | 275 +++++++++++------- .../arborist/test/query-selector-all.js | 9 +- 2 files changed, 171 insertions(+), 113 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index e7abee651cd7d..85dbab2b1ffe3 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -59,6 +59,28 @@ const fixupIds = astNode => { } } +// fixed up nested pseudo nodes will have their internal selectors moved +// to a new root node that will be referenced by the `nestedNode` property, +// this tweak makes it simpler to reuse `retrieveNodesFromParsedAst` to +// recursively parse and extract results from the internal selectors +const fixupNestedPseudo = astNode => { + // create a new ast root node and relocate any children + // selectors of the current ast node to this new root + const newRootNode = parser.root() + astNode.nestedNode = newRootNode + newRootNode.nodes = [...astNode.nodes] + + // clean up the ast by removing the children nodes from the + // current ast node while also cleaning up their `parent` refs + astNode.nodes.length = 0 + for (const currAstNode of newRootNode.nodes) { + currAstNode.parent = newRootNode + } + + // recursively fixup nodes of any nested selector + transformAst(newRootNode) +} + const fixupSemverSpecs = astNode => { if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { const children = astNode.nodes[0].nodes @@ -99,56 +121,77 @@ const transformAst = selector => { selector.walk((nextAstNode) => { if (nextAstNode.type === 'id') { fixupIds(nextAstNode) - } else if (nextAstNode.value === ':path') { - fixupPaths(nextAstNode) - } else if (nextAstNode.value === ':semver') { - fixupSemverSpecs(nextAstNode) - } else if (nextAstNode.value === ':type') { - fixupTypes(nextAstNode) + } + + switch (nextAstNode.value) { + case ":not": + case ":has": + return fixupNestedPseudo(nextAstNode) + case ":path": + return fixupPaths(nextAstNode) + case ":semver": + return fixupSemverSpecs(nextAstNode) + case ":type": + return fixupTypes(nextAstNode) } }) } -const querySelectorAll = async (targetNode, query) => { - // if query is an empty string or any falsy - // value, just returns an empty result - if (!query) { - return [] +// handle results for parsed query asts, results are stored in a map that +// has a key that points to each ast selector node and stores the resulting +// array of arborist nodes as its value, that is essential to how we handle +// multiple query selectors, e.g: `#a, #b, #c` <- 3 diff ast selector nodes +class Results { + #results = null + #currentAstSelector = null + + constructor (rootAstNode) { + this.#results = new Map() + this.#currentAstSelector = rootAstNode.nodes[0] } - const ArboristNode = targetNode.constructor - // TODO: escape any forward slashes / - const rootAstNode = - parser(transformAst, { lossless: false }).astSync(escapeSlashes(query), { lossless: false }) - // console.error(require('util').inspect(rootAstNode, { depth: 10 })) - let currentAstNode = rootAstNode - let currentAstSelector = null - let prevAstNode = null - let pendingCombinator = null - let pendingPseudo = new Map() - const inventory = [...targetNode.root.inventory.values()] - // results is going to be a Map in which its values are the - // resulting items returned for each parsed css ast selector - const results = new Map([[rootAstNode, inventory]]) + set currentAstSelector (value) { + this.#currentAstSelector = value + } - const getCurrentResult = () => - results.get(currentAstSelector) + get currentResult () { + return this.#results.get(this.#currentAstSelector) + } - const updateCurrentResult = (value) => { - results.set(currentAstSelector, value) + set currentResult (value) { + this.#results.set(this.#currentAstSelector, value) } - const getSelectorsResult = astNode => { + // when collecting results to a root astNode, we traverse the list of + // child selector nodes and collect all of their resulting arborist nodes + // into a single/flat Set of items, this ensures we also deduplicate items + collect(rootAstNode) { const acc = new Set() - for (const n of astNode.nodes) { - if (results.has(n)) { - for (const node of results.get(n)) { + for (const n of rootAstNode.nodes) { + if (this.#results.has(n)) { + for (const node of this.#results.get(n)) { acc.add(node) } } } return acc } +} + +const retrieveNodesFromParsedAst = ({ + initialItems, + inventory, + rootAstNode, + targetNode +}) => { + const ArboristNode = targetNode.constructor + + let results = new Results(rootAstNode) + let currentAstNode = rootAstNode + let prevAstNode = null + let pendingCombinator = null + + results.currentResult = initialItems // maps containing the logic to parse each of the supported css selectors const attributeOperatorsMap = new Map(Object.entries({ @@ -258,6 +301,20 @@ const querySelectorAll = async (targetNode, query) => { ':extraneous' () { return getInitialItems().filter(node => node.extraneous) }, + ':has' () { + const initialItems = getInitialItems() + const hasResults = new Map() + for (const item of initialItems) { + const res = retrieveNodesFromParsedAst({ + initialItems: [item], + inventory, + rootAstNode: currentAstNode.nestedNode, + targetNode: item, + }) + hasResults.set(item, res) + } + return initialItems.filter(node => hasResults.get(node).size > 0) + }, ':invalid' () { return getInitialItems().filter(node => [...node.edgesIn].some(edge => edge.invalid)) @@ -277,17 +334,17 @@ const querySelectorAll = async (targetNode, query) => { }, []) }, ':not' () { - const astNode = currentAstNode - pendingPseudo.set( - astNode, - () => { - const childResult = getSelectorsResult(astNode) - const res = results.get(astNode.parent).filter(node => - !childResult.has(node)) - results.set(astNode.parent, res) - } + const initialItems = getInitialItems() + const internalSelector = new Set( + retrieveNodesFromParsedAst({ + initialItems, + inventory: initialItems, + rootAstNode: currentAstNode.nestedNode, + targetNode: currentAstNode, + }) ) - return getInitialItems() + return initialItems.filter(node => + !internalSelector.has(node)) }, ':path' () { return getInitialItems().filter(node => @@ -329,20 +386,14 @@ const querySelectorAll = async (targetNode, query) => { // have the complete inventory list in order to use the left-hand side // ast node as a filter combined with the element on its right-hand side const getInitialItems = () => { - const firstAstNode = - currentAstNode.parent.nodes[0] === currentAstNode - - const firstParsed = firstAstNode && + const firstParsed = currentAstNode.parent.nodes[0] === currentAstNode && currentAstNode.parent.parent.type === 'root' - const firstSelector = firstAstNode && - currentAstNode.parent.type === 'selector' - - return firstParsed || prevAstNode.type === 'combinator' - ? inventory - : firstSelector - ? results.get(currentAstNode.parent.parent.parent) - : getCurrentResult() + return firstParsed + ? initialItems + : currentAstNode.prev().type === 'combinator' + ? inventory + : results.currentResult } // combinators need information about previously filtered items along @@ -358,17 +409,6 @@ const querySelectorAll = async (targetNode, query) => { return nextResults } - const processPendingPseudo = (astNode) => { - //console.error('processPendingPseudo', pendingPseudo) - if (pendingPseudo.has(astNode)) { - //console.error('processPendingPseudo', String(astNode.value)) - const fn = pendingPseudo.get(astNode) - //console.error('pending fn', fn.toString()) - results.set(astNode, fn()) - pendingPseudo.delete(astNode) - } - } - // below are the functions containing the logic to // parse each of the recognized css selectors types const attribute = () => { @@ -377,50 +417,63 @@ const querySelectorAll = async (targetNode, query) => { operator = '', value, } = currentAstNode - const prevResult = getCurrentResult() - const nextResult = getInitialItems().filter(node => { + const prevResults = results.currentResult + const nextResults = getInitialItems().filter(node => { return attributeOperatorsMap.get(operator)({ attribute, value, pkg: node.package, })}) - updateCurrentResult(processPendingCombinator(prevResult, nextResult)) + results.currentResult = processPendingCombinator(prevResults, nextResults) } const classType = () => { - const prevResult = getCurrentResult() - const nextResult = - classesMap.get(String(currentAstNode))(getInitialItems()) - updateCurrentResult(processPendingCombinator(prevResult, nextResult)) + const classFn = classesMap.get(String(currentAstNode)) + if (!classFn) { + throw Object.assign( + new Error(`\`${String(currentAstNode)}\` is not a supported class.`), + { code: 'EQUERYNOCLASS' } + ) + } + const prevResults = results.currentResult + const nextResults = classFn(getInitialItems()) + results.currentResult = processPendingCombinator(prevResults, nextResults) } const combinator = () => { pendingCombinator = combinatorsMap.get(String(currentAstNode)) } const id = () => { const spec = npa(currentAstNode.value) - const prevResult = getCurrentResult() - const nextResult = getInitialItems().filter(node => + const prevResults = results.currentResult + const nextResults = getInitialItems().filter(node => (node.name === spec.name || node.package.name === spec.name) && (semver.satisfies(node.version, spec.fetchSpec) || !spec.rawSpec)) - updateCurrentResult(processPendingCombinator(prevResult, nextResult)) + results.currentResult = processPendingCombinator(prevResults, nextResults) } const pseudo = () => { - const currentAstNodeFn = pseudoMap.get(currentAstNode.value) - if (currentAstNodeFn) { - const prevResult = getCurrentResult() - const nextResult = currentAstNodeFn() - updateCurrentResult(processPendingCombinator(prevResult, nextResult)) + const pseudoFn = pseudoMap.get(currentAstNode.value) + if (!pseudoFn) { + throw Object.assign( + new Error(`\`${currentAstNode.value + }\` is not a supported pseudo-class.`), + { code: 'EQUERYNOPSEUDOCLASS' } + ) } + const prevResults = results.currentResult + const nextResults = pseudoFn() + results.currentResult = processPendingCombinator(prevResults, nextResults) } const selector = () => { - currentAstSelector = currentAstNode + results.currentAstSelector = currentAstNode // starts a new array in which resulting items // can be stored for each given ast selector - updateCurrentResult([]) + if (!results.currentResult) { + results.currentResult = [] + } } const universal = () => { - const prevResult = getCurrentResult() - const nextResult = getInitialItems() - updateCurrentResult(processPendingCombinator(prevResult, nextResult)) + const prevResults = results.currentResult + const nextResults = getInitialItems() + results.currentResult = processPendingCombinator(prevResults, nextResults) } // maps each of the recognized css selectors @@ -435,30 +488,10 @@ const querySelectorAll = async (targetNode, query) => { universal, })) - const maybeProcessPendingPseudo = (nextAstNode) => { - const doneParsingAst = !nextAstNode - const doneParsingSelector = () => - nextAstNode.parent !== prevAstNode.parent && - nextAstNode.parent !== prevAstNode - const followingAstNode = () => - pendingPseudo.has(prevAstNode.parent) - const shouldProcessPendingPseudo = - prevAstNode && - (doneParsingAst || doneParsingSelector()) && - followingAstNode() - if (shouldProcessPendingPseudo) { - processPendingPseudo(prevAstNode.parent) - } - } - // walks through the parsed css query and update the // current result after parsing / executing each ast node - // console.error(require('util').inspect(rootAstNode, { depth: 10 })) + // console.error(require('util').inspect(astNode, { depth: 10 })) rootAstNode.walk((nextAstNode) => { - // when walking away from a nested selector, make sure to - // execute any pending pseudo selectors associated to it - maybeProcessPendingPseudo(nextAstNode) - prevAstNode = currentAstNode currentAstNode = nextAstNode // console.error('prevAstNode', prevAstNode.type, String(prevAstNode)) @@ -470,11 +503,35 @@ const querySelectorAll = async (targetNode, query) => { updateResult() } }) - maybeProcessPendingPseudo() + + return results.collect(rootAstNode) +} + +const querySelectorAll = async (targetNode, query) => { + // if query is an empty string or any falsy + // value, just returns an empty result + if (!query) { + return [] + } + + const rootAstNode = + parser(transformAst, { lossless: false }).astSync(escapeSlashes(query), { lossless: false }) + //console.error(require('util').inspect(rootAstNode, { depth: 10 })) + + // results is going to be a Map in which its values are the + // resulting items returned for each parsed css ast selector + const inventory = [...targetNode.root.inventory.values()] + const res = retrieveNodesFromParsedAst({ + initialItems: inventory, + inventory, + rootAstNode, + targetNode + }) // returns normalized json output return JSON.parse(JSON.stringify( - convertInventoryItemsToResponses(getSelectorsResult(rootAstNode)) + convertInventoryItemsToResponses(res) + // TODO: sort by realpath once normalization is removed from here .sort((a, b) => localeCompare(a.path, b.path)))) } diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 20110fb8c582e..57cad09dfd69d 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -208,6 +208,7 @@ t.test('query-selector-all', async t => { 'ipsum@npm:sit@1.0.0', 'lorem@1.0.0', ]], + ['> #a', [ 'a@1.0.0' ]], // pseudo :root [':root', ['query-selector-all-tests@1.0.0']], @@ -281,17 +282,17 @@ t.test('query-selector-all', async t => { 'ipsum@npm:sit@1.0.0', ]], ['*:root > *:empty:not(*[name^=a], #b)', [ 'bar@2.0.0', ]], - /* TODO: should this be supported? - * apparently only "simple selectors" are supported in css negation - * ref: https://drafts.csswg.org/selectors/#negation [':not(:not(:link))', [ 'a@1.0.0', 'b@1.0.0', ]], - */ // has pseudo-class + [':root > *:has(* > #bar@1.4.0)', [ 'foo@2.2.2' ]], + ['*:has(* > #bar@1.4.0)', [ 'foo@2.2.2' ]], ['*:has(> #bar@1.4.0)', [ 'foo@2.2.2' ]], + ['.workspace:has(> * > #lorem)', [ 'a@1.0.0' ]], + ['.workspace:has(* #lorem, ~ #b)', [ 'a@1.0.0' ]], // type pseudo-class [':type(tag)', [ 'lorem@1.0.0' ]], From ec58533be43de260133efc1165490b2d88b1b61a Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Fri, 3 Jun 2022 14:40:53 -0400 Subject: [PATCH 13/17] add :is pseudo --- workspaces/arborist/lib/query-selector-all.js | 15 ++++++++++++--- workspaces/arborist/test/query-selector-all.js | 13 +++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 85dbab2b1ffe3..965af60d7136a 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -94,8 +94,7 @@ const fixupSemverSpecs = astNode => { const fixupTypes = astNode => { if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { const [valueAstNode] = astNode.nodes[0].nodes - const { value } = valueAstNode - + const { value } = valueAstNode || {} astNode.typeValue = value astNode.nodes.length = 0 } @@ -124,8 +123,9 @@ const transformAst = selector => { } switch (nextAstNode.value) { - case ":not": + case ":is": case ":has": + case ":not": return fixupNestedPseudo(nextAstNode) case ":path": return fixupPaths(nextAstNode) @@ -319,6 +319,15 @@ const retrieveNodesFromParsedAst = ({ return getInitialItems().filter(node => [...node.edgesIn].some(edge => edge.invalid)) }, + ':is' () { + const initialItems = getInitialItems() + return [...retrieveNodesFromParsedAst({ + initialItems, + inventory, + rootAstNode: currentAstNode.nestedNode, + targetNode: currentAstNode, + })] + }, ':link' () { return getInitialItems().filter(node => node.isLink || (node.isTop && !node.isRoot)) }, diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 57cad09dfd69d..9d579a22cbe71 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -294,6 +294,19 @@ t.test('query-selector-all', async t => { ['.workspace:has(> * > #lorem)', [ 'a@1.0.0' ]], ['.workspace:has(* #lorem, ~ #b)', [ 'a@1.0.0' ]], + // is pseudo-class + [':is(#a, #b) > *', [ 'bar@2.0.0', 'baz@1.0.0' ]], + // TODO: ipsum is not empty but it's child is missing + // so it doesn't return a result here + [':root > *:is(.prod:not(:empty), .dev > [name=bar]) > *', [ + 'dasher@2.0.0' + ]], + [':is(*:semver(2.0.0), :semver(=2.0.0-beta.45))', [ + '@npmcli/abbrev@2.0.0-beta.45', + 'bar@2.0.0', + 'dasher@2.0.0', + ]], + // type pseudo-class [':type(tag)', [ 'lorem@1.0.0' ]], [':type(alias)', [ 'ipsum@npm:sit@1.0.0' ]], From a918822597cb944b0edb33d1be6809a42d223dce Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Fri, 3 Jun 2022 16:28:40 -0400 Subject: [PATCH 14/17] add attr initial support --- workspaces/arborist/lib/query-selector-all.js | 53 ++++++++++++++++++- .../arborist/test/query-selector-all.js | 24 +++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 965af60d7136a..63974ef72a670 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -59,6 +59,35 @@ const fixupIds = astNode => { } } +// +const fixupAttr = astNode => { + const properties = [] + for (const selectorAstNode of astNode.nodes) { + const [firstAstNode] = selectorAstNode.nodes + if (firstAstNode.type === 'tag') { + properties.push(firstAstNode.value) + } + } + + const lastSelectorAstNode = astNode.nodes.pop() + const [attributeAstNode] = lastSelectorAstNode.nodes + if (attributeAstNode.type !== 'attribute') { + throw Object.assign( + new Error('`:attr` pseudo-class expects an attribute matcher as the last value'), + { code: 'EQUERYATTR' } + ) + } + + astNode.lookupProperties = properties + astNode.attributeMatcher = { + attribute: attributeAstNode.qualifiedAttribute, + operator: attributeAstNode.operator || '', + value: attributeAstNode.value, + } + + astNode.nodes.length = 0 +} + // fixed up nested pseudo nodes will have their internal selectors moved // to a new root node that will be referenced by the `nestedNode` property, // this tweak makes it simpler to reuse `retrieveNodesFromParsedAst` to @@ -123,6 +152,8 @@ const transformAst = selector => { } switch (nextAstNode.value) { + case ":attr": + return fixupAttr(nextAstNode) case ":is": case ":has": case ":not": @@ -295,6 +326,26 @@ const retrieveNodesFromParsedAst = ({ }, })) const pseudoMap = new Map(Object.entries({ + ':attr' () { + const initialItems = getInitialItems() + const { lookupProperties, attributeMatcher } = currentAstNode + return initialItems.filter(node => { + let obj = node.package + for (const prop of lookupProperties) { + obj = obj[prop] + if (!obj || !obj[attributeMatcher.attribute]) { + return false + } + } + // execute on the attribute matchers + const res = attributeOperatorsMap.get(attributeMatcher.operator)({ + attribute: attributeMatcher.attribute, + value: attributeMatcher.value, + pkg: obj, + }) + return res + }) + }, ':empty' () { return getInitialItems().filter(node => node.edgesOut.size === 0) }, @@ -525,7 +576,7 @@ const querySelectorAll = async (targetNode, query) => { const rootAstNode = parser(transformAst, { lossless: false }).astSync(escapeSlashes(query), { lossless: false }) - //console.error(require('util').inspect(rootAstNode, { depth: 10 })) + // console.error(require('util').inspect(rootAstNode, { depth: 10 })) // results is going to be a Map in which its values are the // resulting items returned for each parsed css ast selector diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 9d579a22cbe71..d0fcef1fee43d 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -53,6 +53,19 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'bar', version: '2.0.0', + arbitrary: { + foo: [ + 'foo', + 'bar', + { funding: { type: 'GH' } }, + { funding: { type: 'MS' } }, + 10000, + 13000, + 15000, + ], + bar: 'bar', + baz: [ 'a', 'b', 'c' ], + } }), }, baz: { @@ -103,6 +116,7 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'sit', version: '1.0.0', + keywords: ['lorem', 'ipsum', 'dolor'], dependencies: { 'missing-dep': '^1.0.0', }, @@ -112,6 +126,10 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'lorem', version: '1.0.0', + keywords: ['lorem', 'ipsum', 'sit', 'amet'], + funding: [ + { type: 'GitHub', url: 'https://github.com/sponsors' } + ], }), }, }, @@ -387,6 +405,12 @@ t.test('query-selector-all', async t => { [':semver(=1.4.0)', [ 'bar@1.4.0' ]], [':semver(1.4.0 || 2.2.2)', [ 'foo@2.2.2', 'bar@1.4.0' ]], + // attr pseudo-class + [':attr([name=dasher])', [ 'dasher@2.0.0' ]], + [':attr(dependencies, [bar="^1.0.0"])', [ 'foo@2.2.2' ]], + //[':attr(funding, :attr([type=GitHub]))', [ 'lorem@1.0.0' ]], + //[':attr(arbirtrary, foo, :attr(funding, [type=GH]))', [ 'bar@2.0.0' ]], + // attribute matchers ['[name]', [ 'query-selector-all-tests@1.0.0', From 06f1d4123a0e4ecb7293cfc07d4107bb8721adab Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Tue, 7 Jun 2022 15:54:42 -0500 Subject: [PATCH 15/17] add :attr support --- workspaces/arborist/lib/query-selector-all.js | 118 +++++++++++++----- .../arborist/test/query-selector-all.js | 9 +- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 63974ef72a670..3265a5fd90178 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -4,10 +4,14 @@ const { resolve } = require('path') const localeCompare = require('@isaacs/string-locale-compare')('en') const parser = require('postcss-selector-parser') const npa = require('npm-package-arg') +const readPackageJson = require('read-package-json') +const rpj = require('util').promisify(readPackageJson) const minimatch = require('minimatch') const semver = require('semver') const QuerySelectorAllResponse = require('./query-selector-all-response.js') +const isArray = Symbol('isArray') + // retrieves a normalized inventory const convertInventoryItemsToResponses = inventory => { const responses = [] @@ -59,9 +63,10 @@ const fixupIds = astNode => { } } -// +// recursively fixes up any :attr pseudo-class found const fixupAttr = astNode => { const properties = [] + const matcher = {} for (const selectorAstNode of astNode.nodes) { const [firstAstNode] = selectorAstNode.nodes if (firstAstNode.type === 'tag') { @@ -71,21 +76,30 @@ const fixupAttr = astNode => { const lastSelectorAstNode = astNode.nodes.pop() const [attributeAstNode] = lastSelectorAstNode.nodes - if (attributeAstNode.type !== 'attribute') { - throw Object.assign( - new Error('`:attr` pseudo-class expects an attribute matcher as the last value'), - { code: 'EQUERYATTR' } - ) - } - astNode.lookupProperties = properties - astNode.attributeMatcher = { - attribute: attributeAstNode.qualifiedAttribute, - operator: attributeAstNode.operator || '', - value: attributeAstNode.value, + if (attributeAstNode.value === ':attr') { + const appendParts = fixupAttr(attributeAstNode) + properties.push(isArray, ...appendParts.lookupProperties) + matcher.attribute = appendParts.attributeMatcher.attribute + matcher.operator = appendParts.attributeMatcher.operator + matcher.value = appendParts.attributeMatcher.value + } else { + matcher.attribute = attributeAstNode.qualifiedAttribute + matcher.operator = attributeAstNode.operator || '' + matcher.value = attributeAstNode.value + + if (attributeAstNode.type !== 'attribute') { + throw Object.assign( + new Error('`:attr` pseudo-class expects an attribute matcher as the last value'), + { code: 'EQUERYATTR' } + ) + } } + astNode.lookupProperties = properties + astNode.attributeMatcher = matcher astNode.nodes.length = 0 + return astNode } // fixed up nested pseudo nodes will have their internal selectors moved @@ -227,20 +241,22 @@ const retrieveNodesFromParsedAst = ({ // maps containing the logic to parse each of the supported css selectors const attributeOperatorsMap = new Map(Object.entries({ '' ({ attribute, value, pkg }) { return Boolean(pkg[attribute]) }, - '=' ({ attribute, value, pkg }) { return pkg[attribute] === value }, + '=' ({ attribute, value, pkg }) { return String(pkg[attribute] || '') === value }, '~=' ({ attribute, value, pkg }) { - return String(pkg[attribute]).match(/\w+/g).includes(value) + return (String(pkg[attribute] || '').match(/\w+/g) || []).includes(value) }, '*=' ({ attribute, value, pkg }) { - return pkg[attribute].indexOf(value) > -1 + return String(pkg[attribute] || '').indexOf(value) > -1 }, '|=' ({ attribute, value, pkg }) { - return pkg[attribute].split('-')[0] === value + return String(pkg[attribute] || '').split('-')[0] === value }, '^=' ({ attribute, value, pkg }) { - return pkg[attribute].startsWith(value) + return String(pkg[attribute] || '').startsWith(value) + }, + '$=' ({ attribute, value, pkg }) { + return String(pkg[attribute] || '').endsWith(value) }, - '$=' ({ attribute, value, pkg }) { return pkg[attribute].endsWith(value) }, })) const classesMap = new Map(Object.entries({ '.prod' (prevResults) { @@ -329,21 +345,65 @@ const retrieveNodesFromParsedAst = ({ ':attr' () { const initialItems = getInitialItems() const { lookupProperties, attributeMatcher } = currentAstNode - return initialItems.filter(node => { - let obj = node.package - for (const prop of lookupProperties) { - obj = obj[prop] - if (!obj || !obj[attributeMatcher.attribute]) { - return false - } + + const match = (attributeMatcher, obj) => { + // in case the current object is an array + // then we try to match every item in the array + if (Array.isArray(obj[attributeMatcher.attribute])) { + return obj[attributeMatcher.attribute].find((i, index) => + attributeOperatorsMap.get(attributeMatcher.operator)({ + attribute: index, + value: attributeMatcher.value, + pkg: obj[attributeMatcher.attribute], + }) + ) } - // execute on the attribute matchers - const res = attributeOperatorsMap.get(attributeMatcher.operator)({ + + return attributeOperatorsMap.get(attributeMatcher.operator)({ attribute: attributeMatcher.attribute, value: attributeMatcher.value, pkg: obj, }) - return res + } + + return initialItems.filter(node => { + let objs = [node.package] + for (const prop of lookupProperties) { + // if an isArray symbol is found that means we'll need to iterate + // over the previous found array to basically make sure we traverse + // all its indexes testing for possible objects that may eventually + // hold more keys specified in a selector + if (prop === isArray) { + const newObjs = [] + for (const obj of objs) { + if (Array.isArray(obj)) { + obj.forEach((i, index) => { + newObjs.push(obj[index]) + }) + } else { + newObjs.push(obj) + } + } + objs = newObjs + continue + } else { + // otherwise just maps all currently found objs + // to the next prop from the lookup properties list, + // filters out any empty key lookup + objs = objs.map(obj => obj[prop]).filter(Boolean) + } + + // in case there's no property found in the lookup + // just filters that item out + const noAttr = objs.every(obj => !obj) + if (noAttr) { + return false + } + } + + // if any of the potential object matches + // that item should be in the final result + return objs.some(obj => match(attributeMatcher, obj)) }) }, ':empty' () { @@ -576,7 +636,7 @@ const querySelectorAll = async (targetNode, query) => { const rootAstNode = parser(transformAst, { lossless: false }).astSync(escapeSlashes(query), { lossless: false }) - // console.error(require('util').inspect(rootAstNode, { depth: 10 })) + //return console.error(require('util').inspect(rootAstNode, { depth: 10 })) // results is going to be a Map in which its values are the // resulting items returned for each parsed css ast selector diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index d0fcef1fee43d..91aa54582bc67 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -92,6 +92,7 @@ t.test('query-selector-all', async t => { 'package.json': JSON.stringify({ name: 'foo', version: '2.2.2', + arbitrary: 'foo key', dependencies: { bar: '^1.0.0', 'dash-separated-pkg': '^1.0.0', @@ -167,6 +168,7 @@ t.test('query-selector-all', async t => { const opts = { path, + fullMetadata: true, } const arb = new Arborist(opts) const tree = await arb.loadActual(opts) @@ -408,8 +410,10 @@ t.test('query-selector-all', async t => { // attr pseudo-class [':attr([name=dasher])', [ 'dasher@2.0.0' ]], [':attr(dependencies, [bar="^1.0.0"])', [ 'foo@2.2.2' ]], - //[':attr(funding, :attr([type=GitHub]))', [ 'lorem@1.0.0' ]], - //[':attr(arbirtrary, foo, :attr(funding, [type=GH]))', [ 'bar@2.0.0' ]], + [':attr([keywords=lorem])', [ 'ipsum@npm:sit@1.0.0', 'lorem@1.0.0' ]], + [':attr(arbitrary, [foo$=oo])', [ 'bar@2.0.0' ]], + [':attr(funding, :attr([type=GitHub]))', [ 'lorem@1.0.0' ]], + [':attr(arbitrary, foo, :attr(funding, [type=GH]))', [ 'bar@2.0.0' ]], // attribute matchers ['[name]', [ @@ -454,6 +458,7 @@ t.test('query-selector-all', async t => { 'dasher@2.0.0', 'bar@1.4.0', ]], + ['[arbitrary^=foo]', [ 'foo@2.2.2' ]], // classes ['.workspace', [ 'a@1.0.0', 'b@1.0.0' ]], From 07841141f26891b563318e4b41f52fc3eae855ad Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Tue, 7 Jun 2022 16:25:07 -0500 Subject: [PATCH 16/17] return arborist nodes refactor --- lib/commands/query.js | 18 +++++++++++++- .../utils}/query-selector-all-response.js | 0 workspaces/arborist/lib/query-selector-all.js | 24 +++---------------- .../arborist/test/query-selector-all.js | 19 +++++++-------- 4 files changed, 28 insertions(+), 33 deletions(-) rename {workspaces/arborist/lib => lib/utils}/query-selector-all-response.js (100%) diff --git a/lib/commands/query.js b/lib/commands/query.js index 55cf3b6ff031c..429a827ddf63d 100644 --- a/lib/commands/query.js +++ b/lib/commands/query.js @@ -3,6 +3,21 @@ const { resolve } = require('path') const Arborist = require('@npmcli/arborist') const BaseCommand = require('../base-command.js') +const QuerySelectorAllResponse = require('../utils/query-selector-all-response.js') + +// retrieves a normalized inventory +const convertInventoryItemsToResponses = inventory => { + const responses = [] + const responsesSeen = new Set() + for (const node of inventory) { + if (!responsesSeen.has(node.target.path)) { + const item = new QuerySelectorAllResponse(node) + responses.push(item) + responsesSeen.add(item.path) + } + } + return responses +} class Query extends BaseCommand { static description = 'Retrieve a filtered list of packages' @@ -23,7 +38,8 @@ class Query extends BaseCommand { const arb = new Arborist(opts) const tree = await arb.loadActual(opts) const items = await tree.querySelectorAll(args[0]) - const res = JSON.stringify(items, null, 2) + const res = + JSON.stringify(convertInventoryItemsToResponses(items), null, 2) return this.npm.output(res) } diff --git a/workspaces/arborist/lib/query-selector-all-response.js b/lib/utils/query-selector-all-response.js similarity index 100% rename from workspaces/arborist/lib/query-selector-all-response.js rename to lib/utils/query-selector-all-response.js diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 3265a5fd90178..8e177b65accf5 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -8,24 +8,9 @@ const readPackageJson = require('read-package-json') const rpj = require('util').promisify(readPackageJson) const minimatch = require('minimatch') const semver = require('semver') -const QuerySelectorAllResponse = require('./query-selector-all-response.js') const isArray = Symbol('isArray') -// retrieves a normalized inventory -const convertInventoryItemsToResponses = inventory => { - const responses = [] - const responsesSeen = new Set() - for (const node of inventory) { - if (!responsesSeen.has(node.target.path)) { - const item = new QuerySelectorAllResponse(node) - responses.push(item) - responsesSeen.add(item.path) - } - } - return responses -} - const escapeSlashes = str => str.replace(/\//g, '\\/') @@ -635,7 +620,7 @@ const querySelectorAll = async (targetNode, query) => { } const rootAstNode = - parser(transformAst, { lossless: false }).astSync(escapeSlashes(query), { lossless: false }) + parser(transformAst).astSync(escapeSlashes(query), { lossless: false }) //return console.error(require('util').inspect(rootAstNode, { depth: 10 })) // results is going to be a Map in which its values are the @@ -648,11 +633,8 @@ const querySelectorAll = async (targetNode, query) => { targetNode }) - // returns normalized json output - return JSON.parse(JSON.stringify( - convertInventoryItemsToResponses(res) - // TODO: sort by realpath once normalization is removed from here - .sort((a, b) => localeCompare(a.path, b.path)))) + // returns nodes ordered by realpath + return [...res].sort((a, b) => localeCompare(a.realpath, b.realpath)) } module.exports = querySelectorAll diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 91aa54582bc67..0b79d4f3055d6 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -3,18 +3,15 @@ const util = require('util') const t = require('tap') const Arborist = require('..') -const QuerySelectorAllResponse = require('../lib/query-selector-all-response.js') -const querySelectorAll = t.mock('../lib/query-selector-all.js', { - '../lib/query-selector-all-response.js': class extends QuerySelectorAllResponse { - // tweaking the output to be used in assertions for - // readability's sake, please keep in mind that the - // querySelectorAll normally returns a json object instead - toJSON () { - return this.pkgid - } - } -}) +const q = require('../lib/query-selector-all.js') + +// test helper that spits out pkgid for readability +// and deduplicates link/target from results +const querySelectorAll = async (tree, query) => { + const res = await q(tree, query) + return [...new Set(res.map(i => i.pkgid))] +} t.test('query-selector-all', async t => { /* From ff370db8a692922562c36857f5f036d1828942dc Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Tue, 7 Jun 2022 17:12:34 -0500 Subject: [PATCH 17/17] added missing tests and cmd test --- .../test/lib/commands/query.js.test.cjs | 41 +++++++++++ test/lib/commands/query.js | 43 ++++++++++++ workspaces/arborist/lib/query-selector-all.js | 68 +++++++++--------- .../arborist/test/query-selector-all.js | 70 +++++++++++++++++-- 4 files changed, 182 insertions(+), 40 deletions(-) create mode 100644 tap-snapshots/test/lib/commands/query.js.test.cjs create mode 100644 test/lib/commands/query.js diff --git a/tap-snapshots/test/lib/commands/query.js.test.cjs b/tap-snapshots/test/lib/commands/query.js.test.cjs new file mode 100644 index 0000000000000..2ccef7e0cfd94 --- /dev/null +++ b/tap-snapshots/test/lib/commands/query.js.test.cjs @@ -0,0 +1,41 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/lib/commands/query.js TAP simple query > should return expected object 1`] = ` +[ + { + "name": "tap-testdir-query-simple-query", + "version": "", + "pkgid": "project@", + "location": "", + "path": "/Users/ruyadorno/Documents/workspace/cli/main/test/lib/commands/tap-testdir-query-simple-query", + "resolved": null, + "isLink": false, + "isWorkspace": false + }, + { + "name": "a", + "version": "", + "pkgid": "a@", + "location": "node_modules/a", + "path": "/Users/ruyadorno/Documents/workspace/cli/main/test/lib/commands/tap-testdir-query-simple-query/node_modules/a", + "resolved": null, + "isLink": false, + "isWorkspace": false + }, + { + "name": "b", + "version": "", + "pkgid": "b@", + "location": "node_modules/b", + "path": "/Users/ruyadorno/Documents/workspace/cli/main/test/lib/commands/tap-testdir-query-simple-query/node_modules/b", + "resolved": null, + "isLink": false, + "isWorkspace": false + } +] +` diff --git a/test/lib/commands/query.js b/test/lib/commands/query.js new file mode 100644 index 0000000000000..38b1a1f11ed01 --- /dev/null +++ b/test/lib/commands/query.js @@ -0,0 +1,43 @@ +const t = require('tap') +const { fake: mockNpm } = require('../../fixtures/mock-npm') +const Query = require('../../../lib/commands/query.js') + +const config = { + global: false, +} + +const npm = mockNpm({ + config, + globalDir: '', + flatOptions: {}, + output: () => {}, +}) + +const query = new Query(npm) + +t.test('simple query', async t => { + const path = t.testdir({ + node_modules: { + a: { + name: 'a', + version: '1.0.0', + }, + b: { + name: 'b', + version: '^2.0.0', + }, + }, + 'package.json': JSON.stringify({ + name: 'project', + dependencies: { + a: '^1.0.0', + b: '^1.0.0', + }, + }) + }) + npm.prefix = path + npm.output = (res) => { + t.matchSnapshot(res, 'should return expected object') + } + await query.exec(['*']) +}) diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 8e177b65accf5..a772bc48afb51 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -110,29 +110,23 @@ const fixupNestedPseudo = astNode => { } const fixupSemverSpecs = astNode => { - if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { - const children = astNode.nodes[0].nodes - const value = children.reduce((res, i) => `${res}${String(i)}`, '') + const children = astNode.nodes[0].nodes + const value = children.reduce((res, i) => `${res}${String(i)}`, '') - astNode.semverValue = value - astNode.nodes.length = 0 - } + astNode.semverValue = value + astNode.nodes.length = 0 } const fixupTypes = astNode => { - if (astNode.nodes && astNode.nodes[0] && astNode.nodes[0].nodes) { - const [valueAstNode] = astNode.nodes[0].nodes - const { value } = valueAstNode || {} - astNode.typeValue = value - astNode.nodes.length = 0 - } + const [valueAstNode] = astNode.nodes[0].nodes + const { value } = valueAstNode || {} + astNode.typeValue = value + astNode.nodes.length = 0 } const fixupPaths = astNode => { - if (astNode.nodes && astNode.nodes[0]) { - astNode.pathValue = unescapeSlashes(String(astNode.nodes[0])) - astNode.nodes.length = 0 - } + astNode.pathValue = unescapeSlashes(String(astNode.nodes[0])) + astNode.nodes.length = 0 } // a few of the supported ast nodes need to be tweaked in order to properly be @@ -198,10 +192,8 @@ class Results { collect(rootAstNode) { const acc = new Set() for (const n of rootAstNode.nodes) { - if (this.#results.has(n)) { - for (const node of this.#results.get(n)) { - acc.add(node) - } + for (const node of this.#results.get(n)) { + acc.add(node) } } return acc @@ -312,13 +304,12 @@ const retrieveNodesFromParsedAst = ({ return nextResults.filter(nextItem => { const seenNodes = new Set() const possibleParentNodes = - (prevResults.isTop && prevResults.resolveParent) || prevResults .flatMap(node => { seenNodes.add(node) return [...node.edgesIn] }) - .map(edge => edge ? edge.from : null) + .map(edge => edge.from) .filter(Boolean) return !seenNodes.has(nextItem) && @@ -453,10 +444,13 @@ const retrieveNodesFromParsedAst = ({ }, ':path' () { return getInitialItems().filter(node => - minimatch( - node.realpath, - resolve(node.root.realpath, currentAstNode.pathValue) - )) + currentAstNode.pathValue + ? minimatch( + node.realpath, + resolve(node.root.realpath, currentAstNode.pathValue) + ) + : true + ) }, ':private' () { return getInitialItems().filter(node => node.package.private) @@ -468,15 +462,19 @@ const retrieveNodesFromParsedAst = ({ return getInitialItems().filter(node => node === targetNode) }, ':semver' () { - return getInitialItems().filter(node => - semver.satisfies(node.version, currentAstNode.semverValue)) + return currentAstNode.semverValue + ? getInitialItems().filter(node => + semver.satisfies(node.version, currentAstNode.semverValue)) + : getInitialItems() }, ':type' () { - return getInitialItems() - .flatMap(node => [...node.edgesIn]) - .filter(edge => npa(`${edge.name}@${edge.spec}`).type === currentAstNode.typeValue) - .map(edge => edge.to) - .filter(Boolean) + return currentAstNode.typeValue + ? getInitialItems() + .flatMap(node => [...node.edgesIn]) + .filter(edge => npa(`${edge.name}@${edge.spec}`).type === currentAstNode.typeValue) + .map(edge => edge.to) + .filter(Boolean) + : getInitialItems() }, })) @@ -604,9 +602,7 @@ const retrieveNodesFromParsedAst = ({ const updateResult = retrieveByType.get(currentAstNode.type) - if (updateResult) { - updateResult() - } + updateResult() }) return results.collect(rootAstNode) diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 0b79d4f3055d6..10459f62c2f6c 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -169,12 +169,34 @@ t.test('query-selector-all', async t => { } const arb = new Arborist(opts) const tree = await arb.loadActual(opts) - console.error(require('child_process').execSync('tree', { cwd: path }).toString()) - //console.error(require('child_process').execSync('npm ls --all', { cwd: path }).toString()) + + // empty query? + const emptyRes = await q(tree, '') + t.same(emptyRes, [], 'empty query') + + // missing pseudo-class + t.rejects( + q(tree, ':foo'), + { code: 'EQUERYNOPSEUDOCLASS' }, + 'should throw on missing pseudo-class' + ) + + // missing class + t.rejects( + q(tree, '.foo'), + { code: 'EQUERYNOCLASS' }, + 'should throw on missing class' + ) + + // missing attribute matcher on :attr + t.rejects( + q(tree, ':attr(foo, bar)'), + { code: 'EQUERYATTR' }, + 'should throw on missing attribute matcher on :attr pseudo-class' + ) // :scope pseudo-class - const nodeFoo = [...tree.edgesOut.values()] - .find(node => node.name === 'foo').to + const [nodeFoo] = await q(tree, '#foo') const scopeRes = await querySelectorAll(nodeFoo, ':scope') t.same(scopeRes, [ 'foo@2.2.2' ], ':scope') @@ -325,6 +347,21 @@ t.test('query-selector-all', async t => { ]], // type pseudo-class + [':type()', [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0-beta.45', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], [':type(tag)', [ 'lorem@1.0.0' ]], [':type(alias)', [ 'ipsum@npm:sit@1.0.0' ]], [':type(range)', [ @@ -353,12 +390,28 @@ t.test('query-selector-all', async t => { [':path(node_modules/foo/node_modules/bar)', [ 'bar@1.4.0' ]], [':path(**/bar)', [ 'bar@2.0.0', 'bar@1.4.0' ]], [':path(*)', [ 'a@1.0.0', 'b@1.0.0' ]], + [':path()', [ + 'query-selector-all-tests@1.0.0', + 'a@1.0.0', + 'b@1.0.0', + '@npmcli/abbrev@2.0.0-beta.45', + 'abbrev@1.1.1', + 'bar@2.0.0', + 'baz@1.0.0', + 'dash-separated-pkg@1.0.0', + 'dasher@2.0.0', + 'foo@2.2.2', + 'bar@1.4.0', + 'ipsum@npm:sit@1.0.0', + 'lorem@1.0.0', + ]], // semver pseudo-class [':semver()', [ 'query-selector-all-tests@1.0.0', 'a@1.0.0', 'b@1.0.0', + '@npmcli/abbrev@2.0.0-beta.45', 'abbrev@1.1.1', 'bar@2.0.0', 'baz@1.0.0', @@ -407,8 +460,11 @@ t.test('query-selector-all', async t => { // attr pseudo-class [':attr([name=dasher])', [ 'dasher@2.0.0' ]], [':attr(dependencies, [bar="^1.0.0"])', [ 'foo@2.2.2' ]], + [':attr(dependencies, :attr([bar="^1.0.0"]))', [ 'foo@2.2.2' ]], [':attr([keywords=lorem])', [ 'ipsum@npm:sit@1.0.0', 'lorem@1.0.0' ]], [':attr(arbitrary, [foo$=oo])', [ 'bar@2.0.0' ]], + [':attr(arbitrary, [foo*=oo])', [ 'bar@2.0.0' ]], + [':attr(arbitrary, [foo|=oo])', []], [':attr(funding, :attr([type=GitHub]))', [ 'lorem@1.0.0' ]], [':attr(arbitrary, foo, :attr(funding, [type=GH]))', [ 'bar@2.0.0' ]], @@ -460,6 +516,12 @@ t.test('query-selector-all', async t => { // classes ['.workspace', [ 'a@1.0.0', 'b@1.0.0' ]], ['.workspace > *', [ 'bar@2.0.0', 'baz@1.0.0' ]], + ['.workspace ~ *', [ + 'abbrev@1.1.1', + 'bar@2.0.0', + 'foo@2.2.2', + 'ipsum@npm:sit@1.0.0' + ]], ['.dev', [ 'foo@2.2.2' ]], ['.dev *', [ 'dash-separated-pkg@1.0.0', 'dasher@2.0.0', 'bar@1.4.0' ]], ['.peer', [ 'dasher@2.0.0' ]],