diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 62cdf4c..0000000 --- a/.clang-format +++ /dev/null @@ -1,12 +0,0 @@ -BasedOnStyle: Google -AlignAfterOpenBracket: AlwaysBreak -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -BinPackArguments: false -# This breaks async functions sometimes, see -# https://github.com/Polymer/polymer-analyzer/pull/393 -# BinPackParameters: false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..db036cf --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,46 @@ +name: Tests + +on: [push, pull_request] + +jobs: + tests: + strategy: + # We support Node Current, LTS, and Maintenance. See + # https://nodejs.org/en/about/releases/ for release schedule. + # + # We test all supported Node versions on Linux, and the oldest and newest + # on macOS/Windows. + matrix: + include: + - node: 18 + os: ubuntu-22.04 + - node: 18 + os: macos-12 + - node: 18 + os: windows-2022 + - node: 20 + os: ubuntu-22.04 + - node: 20 + os: macos-12 + - node: 20 + os: windows-2022 + + # Allow all matrix configurations to complete, instead of cancelling as + # soon as one fails. Useful because we often have different kinds of + # failures depending on the OS. + fail-fast: false + + runs-on: ${{ matrix.os }} + + env: + WIREIT_LOGGER: "quiet-ci" + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - uses: google/wireit@setup-github-actions-caching/v1 + - run: npm ci + - run: npm test diff --git a/.gitignore b/.gitignore index de4d1f0..eebb713 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -dist -node_modules +/dist/ +/node_modules/ +/.wireit/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bacb3af..0000000 --- a/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: node_js -node_js: - - "6" - - "node" diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d7c8f..9b2aba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). - +## Unreleased + +- Add a `getChildren` API as a more specific method similar to `iterateOverAst` that only gets the direct children of a node. +- Add a `getNodesAtLocation` API to get the nodes whose ranges contain a given location. + ## [0.1.0] - 2017-07-18 -* Added ranges to AST nodes, which specify offsets into the source code for the +- Added ranges to AST nodes, which specify offsets into the source code for the start and end of AST. -* Added the `iterateOverAst` method, which returns an iterable over every +- Added the `iterateOverAst` method, which returns an iterable over every node in an AST. diff --git a/README.md b/README.md index b5e94ef..7d53b47 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ fast and flexible CSS parsing and transformation is a critical feature. ### Goals - - Feasibility of being used in conjunction with Polymer or Polymer -Designer. - - Parse CSS loosely and flexibly. This parser is not spec-compliant, however - it will parse all spec-compliant CSS. - - Parse CSS quickly and efficiently. This parser is a suitable tool to aide in - the design and implementation of runtime transformations. - - Graceful error recovery. Malformed CSS will be parsed by this -parser as closely as possible to the way a browser would parse it. +- Feasibility of being used in conjunction with Polymer or Polymer + Designer. +- Parse CSS loosely and flexibly. This parser is not spec-compliant, however + it will parse all spec-compliant CSS. +- Parse CSS quickly and efficiently. This parser is a suitable tool to aide in + the design and implementation of runtime transformations. +- Graceful error recovery. Malformed CSS will be parsed by this + parser as closely as possible to the way a browser would parse it. ### Installing @@ -50,7 +50,6 @@ const ast = parser.parse(css); ```js /* Step 1: Inherit from NodeFactory */ class CustomNodeFactory extends shadyCss.NodeFactory { - /* * Step 2: Implement a custom node factory method. Here we override the * default factory for Expression nodes @@ -89,13 +88,12 @@ from parsed CSS. ```js /* Step 1: Inherit from Stringifier. */ class CustomStringifier extends shadyCss.Stringifier { - /** - * Step 2: Implement a stringification method named after the type of the node - * you are interested in stringifying. In this case, we are implementing - * stringification for the Darken Expression nodes we implemented parsing for - * above. - */ + * Step 2: Implement a stringification method named after the type of the node + * you are interested in stringifying. In this case, we are implementing + * stringification for the Darken Expression nodes we implemented parsing for + * above. + */ darkenExpression(darkenExpression) { // For the sake of brevity, please assume that the darken function returns // a darker version of the color parameter: @@ -117,6 +115,7 @@ const css = stringifier.stringify(ast); --nog: blue; } ``` + ```js { "type": 1, /* stylesheet */ @@ -151,6 +150,7 @@ ruleset { }; } ``` + ```js { "type": 1, /* stylesheet */ @@ -185,9 +185,10 @@ ruleset { ```css .title { - @apply(--my-toolbar-title-theme); + @apply (--my-toolbar-title-theme); } ``` + ```js { "type": 1, /* stylesheet */ @@ -222,6 +223,7 @@ ruleset { }; } ``` + ```js { "type": 1, /* stylesheet */ @@ -258,9 +260,11 @@ ruleset { /* before */ body { margin: 0; - padding: 0px + padding: 0px; } ``` + + ```css /* after */ body{margin:0;padding:0px;} @@ -278,6 +282,8 @@ body{margin:0;padding:0px;} @charset 'foo'; ``` + + ```css /* after */ @import url('foo.css');@font-face{font-family:foo;}@charset 'foo'; @@ -296,9 +302,11 @@ body{margin:0;padding:0px;} #target { gak: var(--qux); - @apply(--foo); + @apply (--foo); } ``` + + ```css /* after */ :root{--qux:vim;--foo:{bar:baz;};}#target{gak:var(--qux);@apply (--foo);} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d28dd7e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1282 @@ +{ + "name": "shady-css-parser", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "shady-css-parser", + "version": "0.1.0", + "license": "BSD-3-Clause", + "devDependencies": { + "@types/chai": "^4.0.1", + "@types/chai-subset": "^1.3.0", + "@types/mocha": "^2.2.39", + "@types/node": "^8.0.13", + "chai": "^4.1.0", + "chai-subset": "^1.5.0", + "mocha": "^3.0.0", + "prettier": "^3.2.5", + "source-map-support": "^0.4.15", + "tslint": "^5.5.0", + "typescript": "^5.3.3", + "wireit": "^0.14.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.5.tgz", + "integrity": "sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha512-7Rfk377tpSM9TWBEeHs0FlDZGoAIei2V/4MdZJoFMBFAK6BqLpxAIUepGRHGdPFgGsLb02PXovC4qddyHvQqTg==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-subset": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chai-subset/-/chai-subset-1.6.0.tgz", + "integrity": "sha512-K3d+KmqdS5XKW5DWPd5sgNffL3uxdDe+6GdnJh3AYPhwnBGRY5urfvfcbRtWIvvpz+KxkL9FeBB6MZewLUNwug==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", + "dev": true, + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha512-E22fsyWPt/lr4/UgQLt/pXqerGMDsanhbnmqIS3VAXuDi1v3IpiwXe2oncEIondHSBuPDWRoK/pMjlvi8FuOXQ==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha512-597ykPFhtJYaXqPq6fF7Vl1fXTKgPdLOntyxpmdzUOKiYGqK7zcnbplj5088+8qJnWdzXhyeau5iVr8HVo9dgg==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-glob/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/fast-glob/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/fast-glob/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/fastq": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "dev": true + }, + "node_modules/growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha512-RTBwDHhNuOx4F0hqzItc/siXCasGfC4DeWcBamclWd+6jWtBaeB/SGbMkGf0eiQoW7ib8JpvOgnUsmgMHI3Mfw==", + "dev": true + }, + "node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha512-I5YLeauH3rIaE99EE++UeH2M2gSYo8/2TqDac7oZEH6D/DSQ4Woa628Qrfj1X9/OY5Mk5VvIDQaKCDchXaKrmA==", + "deprecated": "Please use the native JSON object instead of JSON 3", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ==", + "dev": true, + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ==", + "dev": true + }, + "node_modules/lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha512-EDem6C9iQpn7fxnGdmhXmqYGjCkStmDXT4AeyB2Ph8WKbglg4aJZczNkQglj+zWXcOEEkViK8THuV2JvugW47g==", + "dev": true + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==", + "dev": true + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ==", + "dev": true + }, + "node_modules/lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha512-IUfOYwDEbI8JbhW6psW+Ig01BOVK67dTSCUAbS58M0HBkPcAv/jHuxD+oJVP2tUCo3H9L6f/8GM6rxwY+oc7/w==", + "dev": true, + "dependencies": { + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "dev": true + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", + "dev": true + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", + "dev": true, + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "dependencies": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 0.10.x", + "npm": ">= 1.4.x" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-mRyN/EsN2SyNhKWykF3eEGhDpeNplMWaW18Bmh76tnOqk5TbELAVwFAYOCmKVssOYFrYvvLMguiA+NXO3ZTuVA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha512-F8dvPrZJtNzvDRX26eNXT4a7AecAvTGljmmnI39xEgSpbHKhQ7N0dO/NTxUExd0wuLHp4zbwYY7lvHq0aKpwrA==", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev" + } + }, + "node_modules/tslint/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/tslint/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/wireit": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/wireit/-/wireit-0.14.4.tgz", + "integrity": "sha512-WNAXEw2cJs1nSRNJNRcPypARZNumgtsRTJFTNpd6turCA6JZ6cEwl4ZU3C1IHc/3IaXoPu9LdxcI5TBTdD6/pg==", + "dev": true, + "workspaces": [ + "vscode-extension", + "website" + ], + "dependencies": { + "braces": "^3.0.2", + "chokidar": "^3.5.3", + "fast-glob": "^3.2.11", + "jsonc-parser": "^3.0.0", + "proper-lockfile": "^4.1.2" + }, + "bin": { + "wireit": "bin/wireit.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/wireit/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wireit/node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wireit/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wireit/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/wireit/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wireit/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/wireit/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/wireit/node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wireit/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wireit/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wireit/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/wireit/node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wireit/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/wireit/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json index a206189..d2e69d5 100644 --- a/package.json +++ b/package.json @@ -11,21 +11,20 @@ "@types/node": "^8.0.13", "chai": "^4.1.0", "chai-subset": "^1.5.0", - "clang-format": "^1.0.53", "mocha": "^3.0.0", + "prettier": "^3.2.5", "source-map-support": "^0.4.15", "tslint": "^5.5.0", - "typescript": "^2.4.1", - "watchy": "^0.6.7" + "typescript": "^5.3.3", + "wireit": "^0.14.4" }, "scripts": { "prepublishOnly": "npm run build", - "build": "tsc", - "test": "npm run build && mocha --require source-map-support/register dist/test/*.js", + "build": "wireit", + "test": "wireit", "test:sloppy": "tsc || echo '' && mocha --require source-map-support/register dist/test/*.js", - "test:watch": "watchy -w src/ -- npm test --loglevel=silent", - "lint": "tslint -p ./", - "format": "find src | grep '\\.js$\\|\\.ts$' | xargs clang-format --style=file -i" + "lint": "wireit", + "format": "wireit" }, "repository": { "type": "git", @@ -38,5 +37,69 @@ "bugs": { "url": "https://github.com/PolymerLabs/shady-css-parser/issues" }, - "homepage": "https://github.com/PolymerLabs/shady-css-parser#readme" + "homepage": "https://github.com/PolymerLabs/shady-css-parser#readme", + "wireit": { + "build": { + "files": [ + "src/**/*.ts", + "tscconfig.json" + ], + "output": [ + "dist" + ], + "command": "tsc" + }, + "test": { + "dependencies": [ + "test:actual", + "lint", + "format:check" + ] + }, + "test:actual": { + "dependencies": [ + "build" + ], + "command": "mocha --require source-map-support/register dist/test/*.js" + }, + "lint": { + "files": [ + "src/**/*.ts", + "tslint.json" + ], + "output": [], + "command": "tslint -p ./" + }, + "format": { + "files": [ + "src/", + "*.json", + "*.md" + ], + "command": "prettier -w src *.md *.json" + }, + "format:check": { + "files": [ + "src/", + "*.json", + "*.md" + ], + "command": "prettier --list-different src *.md *.json" + } + }, + "prettier": { + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "preserve", + "bracketSpacing": false, + "trailingComma": "all", + "arrowParens": "always", + "bracketSameLine": true, + "singleAttributePerLine": false, + "htmlWhitespaceSensitivity": "strict", + "endOfLine": "auto" + } } diff --git a/src/shady-css.ts b/src/shady-css.ts index c18e3dd..9161ea7 100644 --- a/src/shady-css.ts +++ b/src/shady-css.ts @@ -10,7 +10,20 @@ */ export {iterateOverAst} from './shady-css/ast-iterator'; -export {AtRule, Comment, Declaration, Discarded, Expression, Node, nodeType, Range, Rule, Rulelist, Ruleset, Stylesheet} from './shady-css/common'; +export { + AtRule, + Comment, + Declaration, + Discarded, + Expression, + Node, + nodeType, + Range, + Rule, + Rulelist, + Ruleset, + Stylesheet, +} from './shady-css/common'; export {NodeFactory} from './shady-css/node-factory'; export {NodeVisitor} from './shady-css/node-visitor'; export {Parser} from './shady-css/parser'; diff --git a/src/shady-css/ast-iterator.ts b/src/shady-css/ast-iterator.ts index da88944..6efeccd 100644 --- a/src/shady-css/ast-iterator.ts +++ b/src/shady-css/ast-iterator.ts @@ -9,41 +9,42 @@ * rights grant found at http://polymer.github.io/PATENTS.txt */ -import * as util from 'util'; - import {Node, nodeType} from './common'; export function* iterateOverAst(node: Node): Iterable { yield node; + for (const child of getChildren(node)) { + yield* iterateOverAst(child); + } +} + +export function* getChildren(node: Node) { switch (node.type) { case nodeType.stylesheet: - for (const rule of node.rules) { - yield* iterateOverAst(rule); - } + yield* node.rules; return; case nodeType.ruleset: - return yield* iterateOverAst(node.rulelist); + yield node.rulelist; + return; case nodeType.rulelist: - for (const rule of node.rules) { - yield* iterateOverAst(rule); - } + yield* node.rules; return; case nodeType.declaration: - if (node.value !== undefined) { - yield* iterateOverAst(node.value); + if (node.value != null) { + yield node.value; } return; case nodeType.atRule: - if (node.rulelist) { - yield* iterateOverAst(node.rulelist); + if (node.rulelist != null) { + yield node.rulelist; } return; case nodeType.expression: case nodeType.comment: case nodeType.discarded: - return; // no child nodes + return; // no child nodes default: const never: never = node; - console.error(`Got a node of unknown type: ${util.inspect(never)}`); + console.error(`Got a node of unknown type: `, never); } } diff --git a/src/shady-css/common.ts b/src/shady-css/common.ts index 1e1d011..c111689 100644 --- a/src/shady-css/common.ts +++ b/src/shady-css/common.ts @@ -17,7 +17,7 @@ const matcher = { whitespaceGreedy: /(\s+)/g, commentGreedy: /(\*\/)/g, boundary: /[\(\)\{\}'"@;:\s]/, - stringBoundary: /['"]/ + stringBoundary: /['"]/, }; /** @@ -31,12 +31,18 @@ export enum nodeType { expression = 'expression', declaration = 'declaration', rulelist = 'rulelist', - discarded = 'discarded' + discarded = 'discarded', } - -export type Node = Stylesheet | AtRule | Comment | Rulelist | Ruleset | - Expression | Declaration | Discarded; +export type Node = + | Stylesheet + | AtRule + | Comment + | Rulelist + | Ruleset + | Expression + | Declaration + | Discarded; export type Rule = Ruleset | Declaration | AtRule | Discarded | Comment; /** A Stylesheet node. */ @@ -58,9 +64,9 @@ export interface AtRule { nameRange: Range; /** The "parameters" of the At Rule (e.g., `utf8`) */ parameters: string; - parametersRange: Range|undefined; + parametersRange: Range | undefined; /** The Rulelist node (if any) of the At Rule. */ - rulelist: Rulelist|undefined; + rulelist: Rulelist | undefined; range: Range; } @@ -123,7 +129,7 @@ export interface Declaration { * Either an Expression node, or a Rulelist node, that * corresponds to the value of the Declaration. */ - value: Expression|Rulelist|undefined; + value: Expression | Rulelist | undefined; range: Range; } @@ -160,14 +166,14 @@ export interface Range { } export interface NodeTypeMap { - 'stylesheet': Stylesheet; - 'atRule': AtRule; - 'comment': Comment; - 'rulelist': Rulelist; - 'ruleset': Ruleset; - 'declaration': Declaration; - 'expression': Expression; - 'discarded': Discarded; + stylesheet: Stylesheet; + atRule: AtRule; + comment: Comment; + rulelist: Rulelist; + ruleset: Ruleset; + declaration: Declaration; + expression: Expression; + discarded: Discarded; } export {matcher}; diff --git a/src/shady-css/get-nodes-at-location.ts b/src/shady-css/get-nodes-at-location.ts new file mode 100644 index 0000000..a30447c --- /dev/null +++ b/src/shady-css/get-nodes-at-location.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright (c) 2022 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt The complete set of authors may be found + * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may + * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by + * Google as part of the polymer project is also subject to an additional IP + * rights grant found at http://polymer.github.io/PATENTS.txt + */ + +import {getChildren} from './ast-iterator'; +import {Node} from './common'; + +/** + * @returns An array of nodes whose range encompases the given location. + * The nodes go from less to more specific, and each node after the first is a + * child of its predecessor. + */ +export function getNodesAtLocation( + initialNode: Node, + location: number, + path: Node[] = [], +): Node[] { + let current = initialNode; + outer: while (true) { + for (const child of getChildren(current)) { + if (contains(child, location)) { + path.push(child); + current = child; + continue outer; + } + } + // no children found that contain the location, so we can go no deeper. + break; + } + return path; +} + +function contains(node: Node, location: number) { + return location >= node.range.start && location < node.range.end; +} diff --git a/src/shady-css/node-factory.ts b/src/shady-css/node-factory.ts index ae2cb4c..b1fd979 100644 --- a/src/shady-css/node-factory.ts +++ b/src/shady-css/node-factory.ts @@ -9,7 +9,19 @@ * rights grant found at http://polymer.github.io/PATENTS.txt */ -import {AtRule, Comment, Declaration, Discarded, Expression, nodeType, Range, Rule, Rulelist, Ruleset, Stylesheet} from './common'; +import { + AtRule, + Comment, + Declaration, + Discarded, + Expression, + nodeType, + Range, + Rule, + Rulelist, + Ruleset, + Stylesheet, +} from './common'; /** * Class used for generating nodes in a CSS AST. Extend this class to implement @@ -33,9 +45,13 @@ class NodeFactory { * @param rulelist The Rulelist node (if any) of the At Rule. */ atRule( - name: string, parameters: string, - rulelist: Rulelist|undefined = undefined, nameRange: Range, - parametersRange: Range|undefined, range: Range): AtRule { + name: string, + parameters: string, + rulelist: Rulelist | undefined = undefined, + nameRange: Range, + parametersRange: Range | undefined, + range: Range, + ): AtRule { return { type: nodeType.atRule, name, @@ -43,7 +59,7 @@ class NodeFactory { rulelist, nameRange, parametersRange, - range + range, }; } @@ -71,8 +87,11 @@ class NodeFactory { * @param rulelist The Rulelist node that corresponds to the Selector. */ ruleset( - selector: string, rulelist: Rulelist, selectorRange: Range, - range: Range): Ruleset { + selector: string, + rulelist: Rulelist, + selectorRange: Range, + range: Range, + ): Ruleset { return {type: nodeType.ruleset, selector, rulelist, selectorRange, range}; } @@ -83,8 +102,11 @@ class NodeFactory { * corresponds to the value of the Declaration. */ declaration( - name: string, value: Expression|Rulelist|undefined, nameRange: Range, - range: Range): Declaration { + name: string, + value: Expression | Rulelist | undefined, + nameRange: Range, + range: Range, + ): Declaration { return {type: nodeType.declaration, name, value, nameRange, range}; } diff --git a/src/shady-css/node-visitor.ts b/src/shady-css/node-visitor.ts index 5ae2e12..3b348b5 100644 --- a/src/shady-css/node-visitor.ts +++ b/src/shady-css/node-visitor.ts @@ -9,12 +9,24 @@ * rights grant found at http://polymer.github.io/PATENTS.txt */ +import { + AtRule, + Comment, + Declaration, + Discarded, + Expression, + nodeType, + Rulelist, + Ruleset, + Stylesheet, +} from './common'; + /** * Class that implements a visitor pattern for ASTs produced by the Parser. * Extend the NodeVisitor class to implement useful tree traversal operations * such as stringification. */ -class NodeVisitor { +class NodeVisitor { private path_: Node[]; /** * Create a NodeVisitor instance. @@ -40,16 +52,27 @@ class NodeVisitor { * @return The return value of the method visiting the node, if any. */ visit(node: Node) { - let result: ReturnValue|undefined; - const callback: ((token: Node) => ReturnValue)|undefined = - (this as any)[node.type]; + let result: ReturnValue | undefined; + const callback = this[node.type]; if (callback) { this.path_.push(node); - result = (this as any)[node.type](node); + result = (callback as unknown as (node: Node) => ReturnValue).call( + this, + node, + ); this.path_.pop(); } return result; } + + [nodeType.stylesheet]?(node: Stylesheet): ReturnValue; + [nodeType.atRule]?(node: AtRule): ReturnValue; + [nodeType.rulelist]?(node: Rulelist): ReturnValue; + [nodeType.ruleset]?(node: Ruleset): ReturnValue; + [nodeType.expression]?(node: Expression): ReturnValue; + [nodeType.declaration]?(node: Declaration): ReturnValue; + [nodeType.comment]?(node: Comment): ReturnValue; + [nodeType.discarded]?(node: Discarded): ReturnValue; } export {NodeVisitor}; diff --git a/src/shady-css/parser.ts b/src/shady-css/parser.ts index 7b35e3f..eb7c4fa 100644 --- a/src/shady-css/parser.ts +++ b/src/shady-css/parser.ts @@ -9,7 +9,16 @@ * rights grant found at http://polymer.github.io/PATENTS.txt */ -import {AtRule, Comment, Declaration, Discarded, Rule, Rulelist, Ruleset, Stylesheet} from './common'; +import { + AtRule, + Comment, + Declaration, + Discarded, + Rule, + Rulelist, + Ruleset, + Stylesheet, +} from './common'; import {NodeFactory} from './node-factory'; import {Token} from './token'; import {Tokenizer} from './tokenizer'; @@ -44,8 +53,10 @@ class Parser { * @param tokenizer A Tokenizer instance. */ parseStylesheet(tokenizer: Tokenizer): Stylesheet { - return this.nodeFactory.stylesheet( - this.parseRules(tokenizer), {start: 0, end: tokenizer.cssText.length}); + return this.nodeFactory.stylesheet(this.parseRules(tokenizer), { + start: 0, + end: tokenizer.cssText.length, + }); } /** @@ -75,7 +86,7 @@ class Parser { * @return If the current token in the Tokenizer is whitespace, * returns null. Otherwise, returns the next parseable node. */ - parseRule(tokenizer: Tokenizer): Rule|null { + parseRule(tokenizer: Tokenizer): Rule | null { // Trim leading whitespace: const token = tokenizer.currentToken; if (token === null) { @@ -84,19 +95,14 @@ class Parser { if (token.is(Token.type.whitespace)) { tokenizer.advance(); return null; - } else if (token.is(Token.type.comment)) { return this.parseComment(tokenizer); - } else if (token.is(Token.type.word)) { return this.parseDeclarationOrRuleset(tokenizer); - } else if (token.is(Token.type.propertyBoundary)) { return this.parseUnknown(tokenizer); - } else if (token.is(Token.type.at)) { return this.parseAtRule(tokenizer); - } else { return this.parseUnknown(tokenizer); } @@ -106,13 +112,15 @@ class Parser { * Consumes tokens from a Tokenizer to parse a Comment node. * @param tokenizer A Tokenizer instance. */ - parseComment(tokenizer: Tokenizer): Comment|null { + parseComment(tokenizer: Tokenizer): Comment | null { const token = tokenizer.advance(); if (token === null) { return null; } - return this.nodeFactory.comment( - tokenizer.slice(token), {start: token.start, end: token.end}); + return this.nodeFactory.comment(tokenizer.slice(token), { + start: token.start, + end: token.end, + }); } /** @@ -121,7 +129,7 @@ class Parser { * malformed CSS conditions. * @param tokenizer A Tokenizer instance. */ - parseUnknown(tokenizer: Tokenizer): Discarded|null { + parseUnknown(tokenizer: Tokenizer): Discarded | null { const start = tokenizer.advance(); let end; @@ -129,23 +137,27 @@ class Parser { return null; } - while (tokenizer.currentToken && - tokenizer.currentToken.is(Token.type.boundary)) { + while ( + tokenizer.currentToken && + tokenizer.currentToken.is(Token.type.boundary) + ) { end = tokenizer.advance(); } return this.nodeFactory.discarded( - tokenizer.slice(start, end), tokenizer.getRange(start, end)); + tokenizer.slice(start, end), + tokenizer.getRange(start, end), + ); } /** * Consumes tokens from a Tokenizer to parse an At Rule node. * @param tokenizer A Tokenizer instance. */ - parseAtRule(tokenizer: Tokenizer): AtRule|null { + parseAtRule(tokenizer: Tokenizer): AtRule | null { let name = undefined; let nameRange = undefined; - let rulelist: Rulelist|undefined = undefined; + let rulelist: Rulelist | undefined = undefined; let parametersStart = undefined; let parametersEnd = undefined; @@ -163,8 +175,10 @@ class Parser { const start = tokenizer.currentToken; let end; - while (tokenizer.currentToken && - tokenizer.currentToken.is(Token.type.word)) { + while ( + tokenizer.currentToken && + tokenizer.currentToken.is(Token.type.word) + ) { end = tokenizer.advance(); } nameRange = tokenizer.getRange(start, end); @@ -191,15 +205,25 @@ class Parser { let parameters = ''; if (parametersStart) { parametersRange = tokenizer.trimRange( - tokenizer.getRange(parametersStart, parametersEnd)); - parameters = - tokenizer.cssText.slice(parametersRange.start, parametersRange.end); + tokenizer.getRange(parametersStart, parametersEnd), + ); + parameters = tokenizer.cssText.slice( + parametersRange.start, + parametersRange.end, + ); } - const end = tokenizer.currentToken ? tokenizer.currentToken.previous!.end : - tokenizer.cssText.length; + const end = tokenizer.currentToken + ? tokenizer.currentToken.previous!.end + : tokenizer.cssText.length; return this.nodeFactory.atRule( - name, parameters, rulelist, nameRange, parametersRange, {start, end}); + name, + parameters, + rulelist, + nameRange, + parametersRange, + {start, end}, + ); } /** @@ -238,7 +262,9 @@ class Parser { * a Ruleset node, as appropriate. * @param tokenizer A Tokenizer node. */ - parseDeclarationOrRuleset(tokenizer: Tokenizer): Declaration|Ruleset|null { + parseDeclarationOrRuleset( + tokenizer: Tokenizer, + ): Declaration | Ruleset | null { let ruleStart = null; let ruleEnd = null; let colon = null; @@ -252,13 +278,16 @@ class Parser { tokenizer.advance(); } else if (tokenizer.currentToken.is(Token.type.openParenthesis)) { // skip until close paren - while (tokenizer.currentToken && - !tokenizer.currentToken.is(Token.type.closeParenthesis)) { + while ( + tokenizer.currentToken && + !tokenizer.currentToken.is(Token.type.closeParenthesis) + ) { tokenizer.advance(); } } else if ( - tokenizer.currentToken.is(Token.type.openBrace) || - tokenizer.currentToken.is(Token.type.propertyBoundary)) { + tokenizer.currentToken.is(Token.type.openBrace) || + tokenizer.currentToken.is(Token.type.propertyBoundary) + ) { break; } else { if (tokenizer.currentToken.is(Token.type.colon)) { @@ -281,32 +310,47 @@ class Parser { // A ruleset never contains or ends with a semi-colon. if (tokenizer.currentToken.is(Token.type.propertyBoundary)) { - const nameRange = - tokenizer.getRange(ruleStart!, colon ? colon.previous : ruleEnd); - const declarationName = - tokenizer.cssText.slice(nameRange.start, nameRange.end); + const nameRange = tokenizer.getRange( + ruleStart!, + colon ? colon.previous : ruleEnd, + ); + const declarationName = tokenizer.cssText.slice( + nameRange.start, + nameRange.end, + ); let expression = undefined; if (colon && colon.next) { const rawExpressionRange = tokenizer.getRange(colon.next, ruleEnd); const expressionRange = tokenizer.trimRange(rawExpressionRange); - const expressionValue = - tokenizer.cssText.slice(expressionRange.start, expressionRange.end); - expression = - this.nodeFactory.expression(expressionValue, expressionRange); + const expressionValue = tokenizer.cssText.slice( + expressionRange.start, + expressionRange.end, + ); + expression = this.nodeFactory.expression( + expressionValue, + expressionRange, + ); } if (tokenizer.currentToken.is(Token.type.semicolon)) { tokenizer.advance(); } - const range = tokenizer.trimRange(tokenizer.getRange( + const range = tokenizer.trimRange( + tokenizer.getRange( ruleStart!, - tokenizer.currentToken && tokenizer.currentToken.previous || - ruleEnd)); + (tokenizer.currentToken && tokenizer.currentToken.previous) || + ruleEnd, + ), + ); return this.nodeFactory.declaration( - declarationName, expression, nameRange, range); + declarationName, + expression, + nameRange, + range, + ); // This is the case for a mixin-like structure: } else if (colon && colon === ruleEnd) { const rulelist = this.parseRulelist(tokenizer); @@ -316,35 +360,48 @@ class Parser { } const nameRange = tokenizer.getRange(ruleStart!, ruleEnd.previous); - const declarationName = - tokenizer.cssText.slice(nameRange.start, nameRange.end); + const declarationName = tokenizer.cssText.slice( + nameRange.start, + nameRange.end, + ); - const range = tokenizer.trimRange(tokenizer.getRange( + const range = tokenizer.trimRange( + tokenizer.getRange( ruleStart!, - tokenizer.currentToken && tokenizer.currentToken.previous || - ruleEnd)); + (tokenizer.currentToken && tokenizer.currentToken.previous) || + ruleEnd, + ), + ); return this.nodeFactory.declaration( - declarationName, rulelist, nameRange, range); + declarationName, + rulelist, + nameRange, + range, + ); // Otherwise, this is a ruleset: } else { const selectorRange = tokenizer.getRange(ruleStart!, ruleEnd); - const selector = - tokenizer.cssText.slice(selectorRange.start, selectorRange.end); + const selector = tokenizer.cssText.slice( + selectorRange.start, + selectorRange.end, + ); const rulelist = this.parseRulelist(tokenizer); const start = ruleStart!.start; let end; if (tokenizer.currentToken) { - end = tokenizer.currentToken.previous ? - tokenizer.currentToken.previous.end : - ruleStart!.end; + end = tokenizer.currentToken.previous + ? tokenizer.currentToken.previous.end + : ruleStart!.end; } else { // no current token? must have reached the end of input, so go up // until there end = tokenizer.cssText.length; } - return this.nodeFactory.ruleset( - selector, rulelist, selectorRange, {start, end}); + return this.nodeFactory.ruleset(selector, rulelist, selectorRange, { + start, + end, + }); } } } diff --git a/src/shady-css/stringifier.ts b/src/shady-css/stringifier.ts index a3e9b85..450f7ec 100644 --- a/src/shady-css/stringifier.ts +++ b/src/shady-css/stringifier.ts @@ -9,7 +9,18 @@ * rights grant found at http://polymer.github.io/PATENTS.txt */ -import {AtRule, Comment, Declaration, Discarded, Expression, Node, nodeType, Rulelist, Ruleset, Stylesheet} from './common'; +import { + AtRule, + Comment, + Declaration, + Discarded, + Expression, + Node, + nodeType, + Rulelist, + Ruleset, + Stylesheet, +} from './common'; import {NodeVisitor} from './node-visitor'; /** @@ -46,9 +57,11 @@ class Stringifier extends NodeVisitor { * @return The stringified CSS of the At Rule. */ [nodeType.atRule](atRule: AtRule) { - return `@${atRule.name}` + - (atRule.parameters ? ` ${atRule.parameters}` : '') + - (atRule.rulelist ? `${this.visit(atRule.rulelist)}` : ';'); + return ( + `@${atRule.name}` + + (atRule.parameters ? ` ${atRule.parameters}` : '') + + (atRule.rulelist ? `${this.visit(atRule.rulelist)}` : ';') + ); } /** @@ -90,9 +103,9 @@ class Stringifier extends NodeVisitor { * @return The stringified CSS of the Declaration. */ [nodeType.declaration](declaration: Declaration) { - return declaration.value != null ? - `${declaration.name}:${this.visit(declaration.value)};` : - `${declaration.name};`; + return declaration.value != null + ? `${declaration.name}:${this.visit(declaration.value)};` + : `${declaration.name};`; } /** diff --git a/src/shady-css/token.ts b/src/shady-css/token.ts index ae1c920..47e7f1c 100644 --- a/src/shady-css/token.ts +++ b/src/shady-css/token.ts @@ -14,12 +14,12 @@ */ export enum TokenType { none = 0, - whitespace = (2 ** 0), - string = (2 ** 1), - comment = (2 ** 2), - word = (2 ** 3), - boundary = (2 ** 4), - propertyBoundary = (2 ** 5), + whitespace = 2 ** 0, + string = 2 ** 1, + comment = 2 ** 2, + word = 2 ** 3, + boundary = 2 ** 4, + propertyBoundary = 2 ** 5, // Special cases for boundary: openParenthesis = (2 ** 6) | TokenType.boundary, closeParenthesis = (2 ** 7) | TokenType.boundary, @@ -35,11 +35,10 @@ export enum TokenType { // TODO: are these two boundaries? I mean, sometimes they are I guess? Or // maybe they shouldn't exist in the boundaryTokenTypes map. - hyphen = (2 ** 13), - underscore = (2 ** 14) + hyphen = 2 ** 13, + underscore = 2 ** 14, } - /** * Class that describes individual tokens as produced by the Tokenizer. */ @@ -49,8 +48,8 @@ class Token { readonly type: TokenType; readonly start: number; readonly end: number; - previous: Token|null; - next: Token|null; + previous: Token | null; + next: Token | null; /** * Create a Token instance. @@ -92,7 +91,7 @@ const boundaryTokenTypes: {[boundaryText: string]: TokenType | undefined} = { '}': Token.type.closeBrace, ';': Token.type.semicolon, '-': Token.type.hyphen, - '_': Token.type.underscore + _: Token.type.underscore, }; export {Token, boundaryTokenTypes}; diff --git a/src/shady-css/tokenizer.ts b/src/shady-css/tokenizer.ts index ac9e77e..26d0813 100644 --- a/src/shady-css/tokenizer.ts +++ b/src/shady-css/tokenizer.ts @@ -29,7 +29,7 @@ class Tokenizer { * Holds a reference to a Token that is "next" in the source string, often * due to having been peeked at. */ - private currentToken_: null|Token = null; + private currentToken_: null | Token = null; /** * Create a Tokenizer instance. @@ -49,7 +49,7 @@ class Tokenizer { * reference is useful for "peeking" at the next token ahead in the sequence. * If the entire CSS text has been tokenized, the `currentToken` will be null. */ - get currentToken(): Token|null { + get currentToken(): Token | null { if (this.currentToken_ == null) { this.currentToken_ = this.getNextToken_(); } @@ -62,7 +62,7 @@ class Tokenizer { * @return The current token prior to the call to `advance`, or null * if the entire CSS text has been tokenized. */ - advance(): Token|null { + advance(): Token | null { let token; if (this.currentToken_ != null) { token = this.currentToken_; @@ -85,7 +85,10 @@ class Tokenizer { * @return The substring of the CSS text corresponding to the * startToken and endToken. */ - slice(startToken: Token, endToken: Token|undefined|null = undefined): string { + slice( + startToken: Token, + endToken: Token | undefined | null = undefined, + ): string { const {start, end} = this.getRange(startToken, endToken); return this.cssText.substring(start, end); } @@ -94,7 +97,7 @@ class Tokenizer { * Like `slice`, but returns the offsets into the source, rather than the * substring itself. */ - getRange(startToken: Token, endToken: Token|undefined|null = undefined) { + getRange(startToken: Token, endToken: Token | undefined | null = undefined) { return {start: startToken.start, end: (endToken || startToken).end}; } @@ -125,7 +128,7 @@ class Tokenizer { * @return A Token instance, or null if the entire CSS text has beeen * tokenized. */ - private getNextToken_(): Token|null { + private getNextToken_(): Token | null { const character = this.cssText[this.offset]; let token; @@ -165,7 +168,7 @@ class Tokenizer { const start = offset; let character; - while (character = this.cssText[++offset]) { + while ((character = this.cssText[++offset])) { if (escaped) { escaped = false; continue; @@ -195,8 +198,10 @@ class Tokenizer { const start = offset; let character; // TODO(cdata): change to greedy regex match? - while ((character = this.cssText[offset]) && - !matcher.boundary.test(character)) { + while ( + (character = this.cssText[offset]) && + !matcher.boundary.test(character) + ) { offset++; } @@ -255,7 +260,7 @@ class Tokenizer { tokenizeBoundary(offset: number): Token { // TODO(cdata): Evaluate if this is faster than a switch statement: const type = - boundaryTokenTypes[this.cssText[offset]] || Token.type.boundary; + boundaryTokenTypes[this.cssText[offset]] || Token.type.boundary; return new Token(type, offset, offset + 1); } diff --git a/src/test/helpers.ts b/src/test/helpers.ts index 639cf83..f53a60b 100644 --- a/src/test/helpers.ts +++ b/src/test/helpers.ts @@ -20,11 +20,13 @@ function expectTokenType(token: Token, type: TokenType) { } function expectTokenSequence( - lexer: Tokenizer, sequence: Array) { + lexer: Tokenizer, + sequence: Array, +) { const lexedSequence = []; let token; - while (token = lexer.advance()) { + while ((token = lexer.advance())) { lexedSequence.push(token.type, lexer.slice(token)); } @@ -32,17 +34,20 @@ function expectTokenSequence( } function linkedTokens(tokens: Token[]) { - tokens.reduce(function(l, r) { - if (l) { - l.next = r; - } - - if (r) { - r.previous = l; - } - - return r; - }, new Token(Token.type.none, 0, 0)); + tokens.reduce( + function (l, r) { + if (l) { + l.next = r; + } + + if (r) { + r.previous = l; + } + + return r; + }, + new Token(Token.type.none, 0, 0), + ); return tokens; } diff --git a/src/test/node-visitor-test.ts b/src/test/node-visitor-test.ts index 01bd934..7df286b 100644 --- a/src/test/node-visitor-test.ts +++ b/src/test/node-visitor-test.ts @@ -10,16 +10,18 @@ */ import {expect} from 'chai'; + +import {nodeType} from '../shady-css/common'; import {NodeVisitor} from '../shady-css/node-visitor'; -type TestNode = TestNodeA|TestNodeB; +type TestNode = TestNodeA | TestNodeB; interface TestNodeA { - type: 'a'; + type: nodeType.stylesheet; callback?: () => void; } interface TestNodeB { - type: 'b'; + type: nodeType.rulelist; child?: TestNode; } @@ -34,53 +36,53 @@ class TestNodeVisitor extends NodeVisitor { this.bCallCount = 0; } - a(a: TestNodeA) { + stylesheet(a: TestNodeA) { this.aCallCount++; if (a.callback) { a.callback(); } - return 'a'; + return 'stylesheet'; } - b(b: TestNodeB) { + rulelist(b: TestNodeB) { this.bCallCount++; if (b.child) { this.visit(b.child); } - return 'b'; + return 'rulelist'; } } describe('NodeVisitor', () => { let nodeVisitor: TestNodeVisitor; - beforeEach(function() { + beforeEach(function () { nodeVisitor = new TestNodeVisitor(); }); it('visits nodes based on their type property', () => { - nodeVisitor.visit({type: 'a'}); + nodeVisitor.visit({type: nodeType.stylesheet}); expect(nodeVisitor.aCallCount).to.be.eql(1); expect(nodeVisitor.bCallCount).to.be.eql(0); - nodeVisitor.visit({type: 'b'}); + nodeVisitor.visit({type: nodeType.rulelist}); expect(nodeVisitor.aCallCount).to.be.eql(1); expect(nodeVisitor.bCallCount).to.be.eql(1); }); it('reveals the path of the recursive visitation of nodes', () => { const a1 = { - type: 'a' as 'a', - callback: function() { + type: nodeType.stylesheet as const, + callback: function () { expect(nodeVisitor.path).to.be.eql([a1]); - } + }, }; const a2: TestNodeA = { - type: 'a' as 'a', - callback: function() { + type: nodeType.stylesheet as const, + callback: function () { expect(nodeVisitor.path).to.be.eql([b, a2]); - } + }, }; - const b = {type: 'b' as 'b', child: a2}; + const b = {type: nodeType.rulelist as const, child: a2}; nodeVisitor.visit(a1); expect(nodeVisitor.aCallCount).to.be.eql(1); diff --git a/src/test/parser-test.ts b/src/test/parser-test.ts index 0ed28d7..4716c81 100644 --- a/src/test/parser-test.ts +++ b/src/test/parser-test.ts @@ -20,7 +20,6 @@ import {TestNodeFactory} from './test-node-factory'; use(require('chai-subset')); - describe('Parser', () => { let parser: Parser; let nodeFactory: TestNodeFactory; @@ -32,120 +31,191 @@ describe('Parser', () => { describe('when parsing css', () => { it('can parse a basic ruleset', () => { - expect(parser.parse(fixtures.basicRuleset)) - .to.be.containSubset(nodeFactory.stylesheet([ - nodeFactory.ruleset('body', nodeFactory.rulelist([ + expect(parser.parse(fixtures.basicRuleset)).to.be.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + 'body', + nodeFactory.rulelist([ nodeFactory.declaration('margin', nodeFactory.expression('0')), - nodeFactory.declaration('padding', nodeFactory.expression('0px')) - ])) - ])); + nodeFactory.declaration('padding', nodeFactory.expression('0px')), + ]), + ), + ]), + ); }); it('can parse at rules', () => { - expect(parser.parse(fixtures.atRules)) - .to.containSubset(nodeFactory.stylesheet([ - nodeFactory.atRule('import', 'url(\'foo.css\')', undefined), - nodeFactory.atRule( - 'font-face', '', nodeFactory.rulelist([nodeFactory.declaration( - 'font-family', - nodeFactory.expression('foo'))])), - nodeFactory.atRule('charset', '\'foo\'', undefined) - ])); + expect(parser.parse(fixtures.atRules)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.atRule('import', "url('foo.css')", undefined), + nodeFactory.atRule( + 'font-face', + '', + nodeFactory.rulelist([ + nodeFactory.declaration( + 'font-family', + nodeFactory.expression('foo'), + ), + ]), + ), + nodeFactory.atRule('charset', "'foo'", undefined), + ]), + ); }); it('can parse keyframes', () => { - expect(parser.parse(fixtures.keyframes)) - .to.containSubset(nodeFactory.stylesheet([ - nodeFactory.atRule('keyframes', 'foo', nodeFactory.rulelist([ + expect(parser.parse(fixtures.keyframes)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.atRule( + 'keyframes', + 'foo', + nodeFactory.rulelist([ + nodeFactory.ruleset( + 'from', + nodeFactory.rulelist([ + nodeFactory.declaration('fiz', nodeFactory.expression('0%')), + ]), + ), nodeFactory.ruleset( - 'from', nodeFactory.rulelist([nodeFactory.declaration( - 'fiz', nodeFactory.expression('0%'))])), - nodeFactory.ruleset('99.9%', nodeFactory.rulelist([ - nodeFactory.declaration('fiz', nodeFactory.expression('100px')), - nodeFactory.declaration('buz', nodeFactory.expression('true')) - ])) - ])) - ])); + '99.9%', + nodeFactory.rulelist([ + nodeFactory.declaration( + 'fiz', + nodeFactory.expression('100px'), + ), + nodeFactory.declaration( + 'buz', + nodeFactory.expression('true'), + ), + ]), + ), + ]), + ), + ]), + ); }); it('can parse custom properties', () => { - expect(parser.parse(fixtures.customProperties)) - .to.containSubset(nodeFactory.stylesheet( - [nodeFactory.ruleset(':root', nodeFactory.rulelist([ - nodeFactory.declaration('--qux', nodeFactory.expression('vim')), - nodeFactory.declaration( - '--foo', nodeFactory.rulelist([nodeFactory.declaration( - 'bar', nodeFactory.expression('baz'))])) - ]))])); + expect(parser.parse(fixtures.customProperties)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + ':root', + nodeFactory.rulelist([ + nodeFactory.declaration('--qux', nodeFactory.expression('vim')), + nodeFactory.declaration( + '--foo', + nodeFactory.rulelist([ + nodeFactory.declaration('bar', nodeFactory.expression('baz')), + ]), + ), + ]), + ), + ]), + ); }); it('can parse declarations with no value', () => { - expect(parser.parse(fixtures.declarationsWithNoValue)) - .to.containSubset(nodeFactory.stylesheet([ - nodeFactory.declaration('foo', undefined), - nodeFactory.declaration('bar 20px', undefined), - nodeFactory.ruleset( - 'div', nodeFactory.rulelist([nodeFactory.declaration( - 'baz', undefined)])) - ])); + expect(parser.parse(fixtures.declarationsWithNoValue)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.declaration('foo', undefined), + nodeFactory.declaration('bar 20px', undefined), + nodeFactory.ruleset( + 'div', + nodeFactory.rulelist([nodeFactory.declaration('baz', undefined)]), + ), + ]), + ); }); it('can parse minified rulelists', () => { - expect(parser.parse(fixtures.minifiedRuleset)) - .to.containSubset(nodeFactory.stylesheet([ - nodeFactory.ruleset( - '.foo', nodeFactory.rulelist([nodeFactory.declaration( - 'bar', nodeFactory.expression('baz'))])), - nodeFactory.ruleset( - 'div .qux', nodeFactory.rulelist([nodeFactory.declaration( - 'vim', nodeFactory.expression('fet'))])) - ])); + expect(parser.parse(fixtures.minifiedRuleset)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + '.foo', + nodeFactory.rulelist([ + nodeFactory.declaration('bar', nodeFactory.expression('baz')), + ]), + ), + nodeFactory.ruleset( + 'div .qux', + nodeFactory.rulelist([ + nodeFactory.declaration('vim', nodeFactory.expression('fet')), + ]), + ), + ]), + ); }); it('can parse psuedo rulesets', () => { - expect(parser.parse(fixtures.psuedoRuleset)) - .to.containSubset(nodeFactory.stylesheet([nodeFactory.ruleset( - '.foo:bar:not(#rif)', - nodeFactory.rulelist([nodeFactory.declaration( - 'baz', nodeFactory.expression('qux'))]))])); + expect(parser.parse(fixtures.psuedoRuleset)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + '.foo:bar:not(#rif)', + nodeFactory.rulelist([ + nodeFactory.declaration('baz', nodeFactory.expression('qux')), + ]), + ), + ]), + ); }); it('can parse rulelists with data URIs', () => { - expect(parser.parse(fixtures.dataUriRuleset)) - .to.containSubset(nodeFactory.stylesheet([nodeFactory.ruleset( - '.foo', nodeFactory.rulelist([nodeFactory.declaration( - 'bar', nodeFactory.expression('url(qux;gib)'))]))])); + expect(parser.parse(fixtures.dataUriRuleset)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + '.foo', + nodeFactory.rulelist([ + nodeFactory.declaration( + 'bar', + nodeFactory.expression('url(qux;gib)'), + ), + ]), + ), + ]), + ); }); it('can parse pathological comments', () => { - expect(parser.parse(fixtures.pathologicalComments)) - .to.containSubset(nodeFactory.stylesheet([ - nodeFactory.ruleset( - '.foo', nodeFactory.rulelist([nodeFactory.declaration( - 'bar', nodeFactory.expression('/*baz*/vim'))])), - nodeFactory.comment( - '/* unclosed\n@fiz {\n --huk: {\n /* buz */'), - nodeFactory.declaration('baz', nodeFactory.expression('lur')), - nodeFactory.discarded('};'), - nodeFactory.discarded('}'), - nodeFactory.atRule('gak', 'wiz', undefined) - ])); + expect(parser.parse(fixtures.pathologicalComments)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + '.foo', + nodeFactory.rulelist([ + nodeFactory.declaration( + 'bar', + nodeFactory.expression('/*baz*/vim'), + ), + ]), + ), + nodeFactory.comment('/* unclosed\n@fiz {\n --huk: {\n /* buz */'), + nodeFactory.declaration('baz', nodeFactory.expression('lur')), + nodeFactory.discarded('};'), + nodeFactory.discarded('}'), + nodeFactory.atRule('gak', 'wiz', undefined), + ]), + ); }); it('disregards extra semi-colons', () => { - expect(parser.parse(fixtures.extraSemicolons)) - .to.containSubset(nodeFactory.stylesheet([ - nodeFactory.ruleset(':host', nodeFactory.rulelist([ + expect(parser.parse(fixtures.extraSemicolons)).to.containSubset( + nodeFactory.stylesheet([ + nodeFactory.ruleset( + ':host', + nodeFactory.rulelist([ nodeFactory.declaration('margin', nodeFactory.expression('0')), nodeFactory.discarded(';;'), nodeFactory.declaration('padding', nodeFactory.expression('0')), nodeFactory.discarded(';'), nodeFactory.discarded(';'), nodeFactory.declaration( - 'display', nodeFactory.expression('block')), - ])), - nodeFactory.discarded(';') - ])); + 'display', + nodeFactory.expression('block'), + ), + ]), + ), + nodeFactory.discarded(';'), + ]), + ); }); }); @@ -169,211 +239,240 @@ describe('Parser', () => { }); it('extracts the correct ranges for declarations', () => { - const expectDeclarationRanges = - (cssText: string, - expectedRangeStrings: string[], - expectedNameRangeStrings: string[]) => { - const ast = parser.parse(cssText); - const nodes = Array.from(getNodesOfType(ast, 'declaration')); - const rangeStrings = nodes.map((n) => { - return cssText.substring(n.range.start, n.range.end); - }); - const nameRangeStrings = nodes.map((n) => { - return cssText.substring(n.nameRange.start, n.nameRange.end); - }); - - expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); - expect(nameRangeStrings).to.be.deep.equal(expectedNameRangeStrings); - }; + const expectDeclarationRanges = ( + cssText: string, + expectedRangeStrings: string[], + expectedNameRangeStrings: string[], + ) => { + const ast = parser.parse(cssText); + const nodes = Array.from(getNodesOfType(ast, 'declaration')); + const rangeStrings = nodes.map((n) => { + return cssText.substring(n.range.start, n.range.end); + }); + const nameRangeStrings = nodes.map((n) => { + return cssText.substring(n.nameRange.start, n.nameRange.end); + }); + + expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); + expect(nameRangeStrings).to.be.deep.equal(expectedNameRangeStrings); + }; expectDeclarationRanges( - fixtures.basicRuleset, - ['margin: 0;', 'padding: 0px'], - ['margin', 'padding']); - expectDeclarationRanges( - fixtures.atRules, ['font-family: foo;'], ['font-family']); + fixtures.basicRuleset, + ['margin: 0;', 'padding: 0px'], + ['margin', 'padding'], + ); expectDeclarationRanges( - fixtures.keyframes, - ['fiz: 0%;', 'fiz: 100px;', 'buz: true;'], - ['fiz', 'fiz', 'buz']); + fixtures.atRules, + ['font-family: foo;'], + ['font-family'], + ); expectDeclarationRanges( - fixtures.customProperties, - ['--qux: vim;', '--foo: {\n bar: baz;\n };', 'bar: baz;'], - ['--qux', '--foo', 'bar']); + fixtures.keyframes, + ['fiz: 0%;', 'fiz: 100px;', 'buz: true;'], + ['fiz', 'fiz', 'buz'], + ); expectDeclarationRanges( - fixtures.extraSemicolons, - [ - 'margin: 0;', - 'padding: 0;', - 'display: block;', - ], - ['margin', 'padding', 'display']); + fixtures.customProperties, + ['--qux: vim;', '--foo: {\n bar: baz;\n };', 'bar: baz;'], + ['--qux', '--foo', 'bar'], + ); expectDeclarationRanges( - fixtures.declarationsWithNoValue, - ['foo;', 'bar 20px;', 'baz;'], - ['foo', 'bar 20px', 'baz']); + fixtures.extraSemicolons, + ['margin: 0;', 'padding: 0;', 'display: block;'], + ['margin', 'padding', 'display'], + ); expectDeclarationRanges( - fixtures.minifiedRuleset, ['bar:baz', 'vim:fet;'], ['bar', 'vim']); + fixtures.declarationsWithNoValue, + ['foo;', 'bar 20px;', 'baz;'], + ['foo', 'bar 20px', 'baz'], + ); expectDeclarationRanges( - fixtures.psuedoRuleset, - ['baz:qux'], - ['baz'], + fixtures.minifiedRuleset, + ['bar:baz', 'vim:fet;'], + ['bar', 'vim'], ); + expectDeclarationRanges(fixtures.psuedoRuleset, ['baz:qux'], ['baz']); expectDeclarationRanges( - fixtures.dataUriRuleset, ['bar:url(qux;gib)'], ['bar']); + fixtures.dataUriRuleset, + ['bar:url(qux;gib)'], + ['bar'], + ); expectDeclarationRanges( - fixtures.pathologicalComments, - ['bar: /*baz*/vim;', 'baz: lur;'], - ['bar', 'baz']); + fixtures.pathologicalComments, + ['bar: /*baz*/vim;', 'baz: lur;'], + ['bar', 'baz'], + ); }); it('extracts the correct ranges for rulesets', () => { - const expectRulesetRanges = - (cssText: string, - expectedRangeStrings: string[], - expectedSelectorRangeStrings: string[]) => { - const ast = parser.parse(cssText); - const nodes = Array.from(getNodesOfType(ast, 'ruleset')); - const rangeStrings = nodes.map((n) => { - return cssText.substring(n.range.start, n.range.end); - }); - const selectorRangeStrings = nodes.map((n) => { - return cssText.substring( - n.selectorRange.start, n.selectorRange.end); - }); - - expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); - expect(selectorRangeStrings) - .to.be.deep.equal(expectedSelectorRangeStrings); - }; + const expectRulesetRanges = ( + cssText: string, + expectedRangeStrings: string[], + expectedSelectorRangeStrings: string[], + ) => { + const ast = parser.parse(cssText); + const nodes = Array.from(getNodesOfType(ast, 'ruleset')); + const rangeStrings = nodes.map((n) => { + return cssText.substring(n.range.start, n.range.end); + }); + const selectorRangeStrings = nodes.map((n) => { + return cssText.substring(n.selectorRange.start, n.selectorRange.end); + }); + + expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); + expect(selectorRangeStrings).to.be.deep.equal( + expectedSelectorRangeStrings, + ); + }; expectRulesetRanges( - fixtures.basicRuleset, - [`body {\n margin: 0;\n padding: 0px\n}`], - ['body']); + fixtures.basicRuleset, + [`body {\n margin: 0;\n padding: 0px\n}`], + ['body'], + ); expectRulesetRanges(fixtures.atRules, [], []); expectRulesetRanges( - fixtures.keyframes, - [ - 'from {\n fiz: 0%;\n }', - '99.9% {\n fiz: 100px;\n buz: true;\n }' - ], - ['from', '99.9%']); + fixtures.keyframes, + [ + 'from {\n fiz: 0%;\n }', + '99.9% {\n fiz: 100px;\n buz: true;\n }', + ], + ['from', '99.9%'], + ); expectRulesetRanges( - fixtures.customProperties, - [':root {\n --qux: vim;\n --foo: {\n bar: baz;\n };\n}'], - [':root']); + fixtures.customProperties, + [':root {\n --qux: vim;\n --foo: {\n bar: baz;\n };\n}'], + [':root'], + ); expectRulesetRanges( - fixtures.extraSemicolons, - [':host {\n margin: 0;;;\n padding: 0;;\n ;display: block;\n}'], - [':host']); + fixtures.extraSemicolons, + [':host {\n margin: 0;;;\n padding: 0;;\n ;display: block;\n}'], + [':host'], + ); expectRulesetRanges( - fixtures.declarationsWithNoValue, ['div {\n baz;\n}'], ['div']); + fixtures.declarationsWithNoValue, + ['div {\n baz;\n}'], + ['div'], + ); expectRulesetRanges( - fixtures.minifiedRuleset, - ['.foo{bar:baz}', 'div .qux{vim:fet;}'], - ['.foo', 'div .qux']); + fixtures.minifiedRuleset, + ['.foo{bar:baz}', 'div .qux{vim:fet;}'], + ['.foo', 'div .qux'], + ); expectRulesetRanges( - fixtures.psuedoRuleset, - ['.foo:bar:not(#rif){baz:qux}'], - ['.foo:bar:not(#rif)'], + fixtures.psuedoRuleset, + ['.foo:bar:not(#rif){baz:qux}'], + ['.foo:bar:not(#rif)'], ); expectRulesetRanges( - fixtures.dataUriRuleset, ['.foo{bar:url(qux;gib)}'], ['.foo']); + fixtures.dataUriRuleset, + ['.foo{bar:url(qux;gib)}'], + ['.foo'], + ); expectRulesetRanges( - fixtures.pathologicalComments, - ['.foo {\n bar: /*baz*/vim;\n}'], - ['.foo']); + fixtures.pathologicalComments, + ['.foo {\n bar: /*baz*/vim;\n}'], + ['.foo'], + ); }); it('extracts the correct ranges for rulelists', () => { - const expectRulelistRanges = - (cssText: string, expectedRangeStrings: string[]) => { - const ast = parser.parse(cssText); - const nodes = Array.from(getNodesOfType(ast, 'rulelist')); - const rangeStrings = nodes.map((n) => { - return cssText.substring(n.range.start, n.range.end); - }); - - expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); - }; - - expectRulelistRanges( - fixtures.basicRuleset, [`{\n margin: 0;\n padding: 0px\n}`]); - expectRulelistRanges( - fixtures.atRules, - ['{\n font-family: foo;\n}'], - ); + const expectRulelistRanges = ( + cssText: string, + expectedRangeStrings: string[], + ) => { + const ast = parser.parse(cssText); + const nodes = Array.from(getNodesOfType(ast, 'rulelist')); + const rangeStrings = nodes.map((n) => { + return cssText.substring(n.range.start, n.range.end); + }); + + expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); + }; + + expectRulelistRanges(fixtures.basicRuleset, [ + `{\n margin: 0;\n padding: 0px\n}`, + ]); + expectRulelistRanges(fixtures.atRules, ['{\n font-family: foo;\n}']); expectRulelistRanges(fixtures.keyframes, [ '{\n from {\n fiz: 0%;\n }\n\n 99.9% ' + - '{\n fiz: 100px;\n buz: true;\n }\n}', + '{\n fiz: 100px;\n buz: true;\n }\n}', '{\n fiz: 0%;\n }', - '{\n fiz: 100px;\n buz: true;\n }' + '{\n fiz: 100px;\n buz: true;\n }', ]); expectRulelistRanges(fixtures.customProperties, [ '{\n --qux: vim;\n --foo: {\n bar: baz;\n };\n}', - '{\n bar: baz;\n }' + '{\n bar: baz;\n }', + ]); + expectRulelistRanges(fixtures.extraSemicolons, [ + '{\n margin: 0;;;\n padding: 0;;\n ;display: block;\n}', ]); - expectRulelistRanges( - fixtures.extraSemicolons, - ['{\n margin: 0;;;\n padding: 0;;\n ;display: block;\n}']); expectRulelistRanges(fixtures.declarationsWithNoValue, ['{\n baz;\n}']); - expectRulelistRanges( - fixtures.minifiedRuleset, ['{bar:baz}', '{vim:fet;}']); + expectRulelistRanges(fixtures.minifiedRuleset, [ + '{bar:baz}', + '{vim:fet;}', + ]); expectRulelistRanges(fixtures.psuedoRuleset, ['{baz:qux}']); expectRulelistRanges(fixtures.dataUriRuleset, ['{bar:url(qux;gib)}']); - expectRulelistRanges( - fixtures.pathologicalComments, ['{\n bar: /*baz*/vim;\n}']); + expectRulelistRanges(fixtures.pathologicalComments, [ + '{\n bar: /*baz*/vim;\n}', + ]); }); it('extracts the correct ranges for atRules', () => { - const expectAtRuleRanges = - (cssText: string, - expectedRangeStrings: string[], - expectedNameRangeStrings: string[], - expectedParameterRangeStrings: string[]) => { - const ast = parser.parse(cssText); - const nodes = Array.from(getNodesOfType(ast, 'atRule')); - const rangeStrings = nodes.map((n) => { - return cssText.substring(n.range.start, n.range.end); - }); - const rangeNameStrings = nodes.map((n) => { - return cssText.substring(n.nameRange.start, n.nameRange.end); - }); - const rangeParametersStrings = nodes.map((n) => { - if (!n.parametersRange) { - return '[no parameters]'; - } - return cssText.substring( - n.parametersRange.start, n.parametersRange.end); - }); - - - expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); - expect(rangeNameStrings).to.be.deep.equal(expectedNameRangeStrings); - - expect(rangeParametersStrings) - .to.be.deep.equal(expectedParameterRangeStrings); - }; + const expectAtRuleRanges = ( + cssText: string, + expectedRangeStrings: string[], + expectedNameRangeStrings: string[], + expectedParameterRangeStrings: string[], + ) => { + const ast = parser.parse(cssText); + const nodes = Array.from(getNodesOfType(ast, 'atRule')); + const rangeStrings = nodes.map((n) => { + return cssText.substring(n.range.start, n.range.end); + }); + const rangeNameStrings = nodes.map((n) => { + return cssText.substring(n.nameRange.start, n.nameRange.end); + }); + const rangeParametersStrings = nodes.map((n) => { + if (!n.parametersRange) { + return '[no parameters]'; + } + return cssText.substring( + n.parametersRange.start, + n.parametersRange.end, + ); + }); + + expect(rangeStrings).to.be.deep.equal(expectedRangeStrings); + expect(rangeNameStrings).to.be.deep.equal(expectedNameRangeStrings); + + expect(rangeParametersStrings).to.be.deep.equal( + expectedParameterRangeStrings, + ); + }; expectAtRuleRanges(fixtures.basicRuleset, [], [], []); expectAtRuleRanges( - fixtures.atRules, - [ - `@import url('foo.css');`, - `@font-face {\n font-family: foo;\n}`, - `@charset 'foo';` - ], - ['import', 'font-face', 'charset'], - [`url('foo.css')`, `[no parameters]`, `'foo'`]); + fixtures.atRules, + [ + `@import url('foo.css');`, + `@font-face {\n font-family: foo;\n}`, + `@charset 'foo';`, + ], + ['import', 'font-face', 'charset'], + [`url('foo.css')`, `[no parameters]`, `'foo'`], + ); expectAtRuleRanges( - fixtures.keyframes, - [ - '@keyframes foo {\n from {\n fiz: 0%;\n }\n\n 99.9% ' + - '{\n fiz: 100px;\n buz: true;\n }\n}', - ], - ['keyframes'], - ['foo']); + fixtures.keyframes, + [ + '@keyframes foo {\n from {\n fiz: 0%;\n }\n\n 99.9% ' + + '{\n fiz: 100px;\n buz: true;\n }\n}', + ], + ['keyframes'], + ['foo'], + ); expectAtRuleRanges(fixtures.customProperties, [], [], []); expectAtRuleRanges(fixtures.extraSemicolons, [], [], []); expectAtRuleRanges(fixtures.declarationsWithNoValue, [], [], []); @@ -381,17 +480,29 @@ describe('Parser', () => { expectAtRuleRanges(fixtures.psuedoRuleset, [], [], []); expectAtRuleRanges(fixtures.dataUriRuleset, [], [], []); expectAtRuleRanges( - fixtures.pathologicalComments, ['@gak wiz;'], ['gak'], ['wiz']); + fixtures.pathologicalComments, + ['@gak wiz;'], + ['gak'], + ['wiz'], + ); }); }); }); -function* - getNodesOfType(node: Node, type: K): - Iterable { +function* getNodesOfType( + node: Node, + type: K, +): Iterable { for (const n of iterateOverAst(node)) { - if (n.type === type as any as nodeType) { + if (nodeHasType(n, type)) { yield n; } } } + +function nodeHasType( + node: Node, + type: K, +): node is NodeTypeMap[K] { + return node.type === (type as unknown as nodeType); +} diff --git a/src/test/stringifier-test.ts b/src/test/stringifier-test.ts index e6d7149..7928a57 100644 --- a/src/test/stringifier-test.ts +++ b/src/test/stringifier-test.ts @@ -33,15 +33,17 @@ describe('Stringifier', () => { }); it('can stringify an At Rule without a Rulelist', () => { - const cssText = - stringifier.stringify(nodeFactory.atRule('foo', '("bar")')); + const cssText = stringifier.stringify( + nodeFactory.atRule('foo', '("bar")'), + ); expect(cssText).to.be.eql('@foo ("bar");'); }); it('can stringify an At Rule with a Rulelist', () => { const cssText = stringifier.stringify( - nodeFactory.atRule('foo', '("bar")', nodeFactory.rulelist([]))); + nodeFactory.atRule('foo', '("bar")', nodeFactory.rulelist([])), + ); expect(cssText).to.be.eql('@foo ("bar"){}'); }); @@ -53,19 +55,22 @@ describe('Stringifier', () => { it('can stringify Rulesets', () => { const cssText = stringifier.stringify( - nodeFactory.ruleset('.fiz #buz', nodeFactory.rulelist([]))); + nodeFactory.ruleset('.fiz #buz', nodeFactory.rulelist([])), + ); expect(cssText).to.be.eql('.fiz #buz{}'); }); it('can stringify Declarations with Expression values', () => { const cssText = stringifier.stringify( - nodeFactory.declaration('color', nodeFactory.expression('red'))); + nodeFactory.declaration('color', nodeFactory.expression('red')), + ); expect(cssText).to.be.eql('color:red;'); }); it('can stringify Declarations with Rulelist values', () => { const cssText = stringifier.stringify( - nodeFactory.declaration('--mixin', nodeFactory.rulelist([]))); + nodeFactory.declaration('--mixin', nodeFactory.rulelist([])), + ); expect(cssText).to.be.eql('--mixin:{};'); }); }); @@ -78,41 +83,48 @@ describe('Stringifier', () => { }); it('can stringify a basic ruleset', () => { - const cssText = - stringifier.stringify(parser.parse(fixtures.basicRuleset)); + const cssText = stringifier.stringify( + parser.parse(fixtures.basicRuleset), + ); expect(cssText).to.be.eql('body{margin:0;padding:0px;}'); }); it('can stringify at rules', () => { const cssText = stringifier.stringify(parser.parse(fixtures.atRules)); expect(cssText).to.be.eql( - '@import url(\'foo.css\');@font-face{font-family:foo;}@charset \'foo\';'); + "@import url('foo.css');@font-face{font-family:foo;}@charset 'foo';", + ); }); it('can stringify keyframes', () => { const cssText = stringifier.stringify(parser.parse(fixtures.keyframes)); expect(cssText).to.be.eql( - '@keyframes foo{from{fiz:0%;}99.9%{fiz:100px;buz:true;}}'); + '@keyframes foo{from{fiz:0%;}99.9%{fiz:100px;buz:true;}}', + ); }); it('can stringify declarations without value', () => { - const cssText = - stringifier.stringify(parser.parse(fixtures.declarationsWithNoValue)); + const cssText = stringifier.stringify( + parser.parse(fixtures.declarationsWithNoValue), + ); expect(cssText).to.be.eql('foo;bar 20px;div{baz;}'); }); it('can stringify custom properties', () => { - const cssText = - stringifier.stringify(parser.parse(fixtures.customProperties)); + const cssText = stringifier.stringify( + parser.parse(fixtures.customProperties), + ); expect(cssText).to.be.eql(':root{--qux:vim;--foo:{bar:baz;};}'); }); describe('with discarded nodes', () => { it('stringifies to a corrected stylesheet', () => { - const cssText = - stringifier.stringify(parser.parse(fixtures.pathologicalComments)); + const cssText = stringifier.stringify( + parser.parse(fixtures.pathologicalComments), + ); expect(cssText).to.be.eql( - '.foo{bar:/*baz*/vim;}/* unclosed\n@fiz {\n --huk: {\n /* buz */baz:lur;@gak wiz;'); + '.foo{bar:/*baz*/vim;}/* unclosed\n@fiz {\n --huk: {\n /* buz */baz:lur;@gak wiz;', + ); }); }); }); diff --git a/src/test/test-node-factory.ts b/src/test/test-node-factory.ts index 7d1fdd0..4e9b4ae 100644 --- a/src/test/test-node-factory.ts +++ b/src/test/test-node-factory.ts @@ -14,13 +14,18 @@ */ import {nodeType} from '../shady-css'; +// tslint:disable:no-any + export class TestNodeFactory { stylesheet(rules: any[]): any { return {type: nodeType.stylesheet, rules}; } - atRule(name: string, parameters: string, rulelist: any|undefined = undefined): - any { + atRule( + name: string, + parameters: string, + rulelist: any | undefined = undefined, + ): any { return {type: nodeType.atRule, name, parameters, rulelist}; } @@ -36,7 +41,7 @@ export class TestNodeFactory { return {type: nodeType.ruleset, selector, rulelist}; } - declaration(name: string, value: any|undefined): any { + declaration(name: string, value: any | undefined): any { return {type: nodeType.declaration, name, value}; } expression(text: string): any { diff --git a/src/test/token-test.ts b/src/test/token-test.ts index 3392050..de56e55 100644 --- a/src/test/token-test.ts +++ b/src/test/token-test.ts @@ -10,7 +10,8 @@ */ import {expect} from 'chai'; -import {Token} from '../shady-css/token'; + +import {Token, TokenType} from '../shady-css/token'; describe('Token', () => { it('supports bitfield type comparison', () => { @@ -19,6 +20,6 @@ describe('Token', () => { expect(token.is(4)).to.not.be.ok; expect(token.is(32 | 2)).to.be.ok; expect(token.is(4 | 64)).to.not.be.ok; - expect(token.is(128)).to.be.ok; + expect(token.is(128 as TokenType)).to.be.ok; }); }); diff --git a/src/test/tokenizer-test.ts b/src/test/tokenizer-test.ts index 9baa0ec..9e13b0b 100644 --- a/src/test/tokenizer-test.ts +++ b/src/test/tokenizer-test.ts @@ -20,46 +20,76 @@ import * as helpers from './helpers'; describe('Tokenizer', () => { describe('when tokenizing basic structures', () => { it('can identify strings', () => { - expect(new Tokenizer('"foo"').flush()).to.be.eql(helpers.linkedTokens([ - new Token(Token.type.string, 0, 5) - ])); + expect(new Tokenizer('"foo"').flush()).to.be.eql( + helpers.linkedTokens([new Token(Token.type.string, 0, 5)]), + ); }); it('can identify comments', () => { - expect(new Tokenizer('/*foo*/').flush()).to.be.eql(helpers.linkedTokens([ - new Token(Token.type.comment, 0, 7) - ])); + expect(new Tokenizer('/*foo*/').flush()).to.be.eql( + helpers.linkedTokens([new Token(Token.type.comment, 0, 7)]), + ); }); it('can identify words', () => { - expect(new Tokenizer('font-family').flush()) - .to.be.eql(helpers.linkedTokens([new Token(Token.type.word, 0, 11)])); + expect(new Tokenizer('font-family').flush()).to.be.eql( + helpers.linkedTokens([new Token(Token.type.word, 0, 11)]), + ); }); it('can identify boundaries', () => { - expect(new Tokenizer('@{};()').flush()).to.be.eql(helpers.linkedTokens([ - new Token(Token.type.at, 0, 1), - new Token(Token.type.openBrace, 1, 2), - new Token(Token.type.closeBrace, 2, 3), - new Token(Token.type.semicolon, 3, 4), - new Token(Token.type.openParenthesis, 4, 5), - new Token(Token.type.closeParenthesis, 5, 6) - ])); + expect(new Tokenizer('@{};()').flush()).to.be.eql( + helpers.linkedTokens([ + new Token(Token.type.at, 0, 1), + new Token(Token.type.openBrace, 1, 2), + new Token(Token.type.closeBrace, 2, 3), + new Token(Token.type.semicolon, 3, 4), + new Token(Token.type.openParenthesis, 4, 5), + new Token(Token.type.closeParenthesis, 5, 6), + ]), + ); }); }); describe('when tokenizing standard CSS structures', () => { it('can tokenize a basic ruleset', () => { helpers.expectTokenSequence(new Tokenizer(fixtures.basicRuleset), [ - Token.type.whitespace, '\n', Token.type.word, 'body', - Token.type.whitespace, ' ', Token.type.openBrace, '{', - Token.type.whitespace, '\n ', Token.type.word, 'margin', - Token.type.colon, ':', Token.type.whitespace, ' ', - Token.type.word, '0', Token.type.semicolon, ';', - Token.type.whitespace, '\n ', Token.type.word, 'padding', - Token.type.colon, ':', Token.type.whitespace, ' ', - Token.type.word, '0px', Token.type.whitespace, '\n', - Token.type.closeBrace, '}', Token.type.whitespace, '\n' + Token.type.whitespace, + '\n', + Token.type.word, + 'body', + Token.type.whitespace, + ' ', + Token.type.openBrace, + '{', + Token.type.whitespace, + '\n ', + Token.type.word, + 'margin', + Token.type.colon, + ':', + Token.type.whitespace, + ' ', + Token.type.word, + '0', + Token.type.semicolon, + ';', + Token.type.whitespace, + '\n ', + Token.type.word, + 'padding', + Token.type.colon, + ':', + Token.type.whitespace, + ' ', + Token.type.word, + '0px', + Token.type.whitespace, + '\n', + Token.type.closeBrace, + '}', + Token.type.whitespace, + '\n', ]); }); @@ -78,7 +108,7 @@ describe('Tokenizer', () => { Token.type.openParenthesis, '(', Token.type.string, - '\'foo.css\'', + "'foo.css'", Token.type.closeParenthesis, ')', Token.type.semicolon, @@ -118,32 +148,78 @@ describe('Tokenizer', () => { Token.type.whitespace, ' ', Token.type.string, - '\'foo\'', + "'foo'", Token.type.semicolon, ';', Token.type.whitespace, - '\n' + '\n', ]); }); it('navigates pathological boundary usage', () => { helpers.expectTokenSequence(new Tokenizer(fixtures.extraSemicolons), [ - Token.type.whitespace, '\n', Token.type.colon, ':', - Token.type.word, 'host', Token.type.whitespace, ' ', - Token.type.openBrace, '{', Token.type.whitespace, '\n ', - Token.type.word, 'margin', Token.type.colon, ':', - Token.type.whitespace, ' ', Token.type.word, '0', - Token.type.semicolon, ';', Token.type.semicolon, ';', - Token.type.semicolon, ';', Token.type.whitespace, '\n ', - Token.type.word, 'padding', Token.type.colon, ':', - Token.type.whitespace, ' ', Token.type.word, '0', - Token.type.semicolon, ';', Token.type.semicolon, ';', - Token.type.whitespace, '\n ', Token.type.semicolon, ';', - Token.type.word, 'display', Token.type.colon, ':', - Token.type.whitespace, ' ', Token.type.word, 'block', - Token.type.semicolon, ';', Token.type.whitespace, '\n', - Token.type.closeBrace, '}', Token.type.semicolon, ';', - Token.type.whitespace, '\n' + Token.type.whitespace, + '\n', + Token.type.colon, + ':', + Token.type.word, + 'host', + Token.type.whitespace, + ' ', + Token.type.openBrace, + '{', + Token.type.whitespace, + '\n ', + Token.type.word, + 'margin', + Token.type.colon, + ':', + Token.type.whitespace, + ' ', + Token.type.word, + '0', + Token.type.semicolon, + ';', + Token.type.semicolon, + ';', + Token.type.semicolon, + ';', + Token.type.whitespace, + '\n ', + Token.type.word, + 'padding', + Token.type.colon, + ':', + Token.type.whitespace, + ' ', + Token.type.word, + '0', + Token.type.semicolon, + ';', + Token.type.semicolon, + ';', + Token.type.whitespace, + '\n ', + Token.type.semicolon, + ';', + Token.type.word, + 'display', + Token.type.colon, + ':', + Token.type.whitespace, + ' ', + Token.type.word, + 'block', + Token.type.semicolon, + ';', + Token.type.whitespace, + '\n', + Token.type.closeBrace, + '}', + Token.type.semicolon, + ';', + Token.type.whitespace, + '\n', ]); }); }); @@ -152,7 +228,9 @@ describe('Tokenizer', () => { it('can slice the string using tokens', () => { const tokenizer = new Tokenizer('foo bar'); const substring = tokenizer.slice( - new Token(Token.type.word, 2, 3), new Token(Token.type.word, 5, 6)); + new Token(Token.type.word, 2, 3), + new Token(Token.type.word, 5, 6), + ); expect(substring).to.be.eql('o ba'); }); }); diff --git a/tsconfig.json b/tsconfig.json index 925e95d..d66eb64 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,7 @@ { "compilerOptions": { "target": "es6", - "lib": [ - "es2016" - ], + "lib": ["es2016"], "module": "commonjs", "moduleResolution": "node", "isolatedModules": false, @@ -18,7 +16,5 @@ "rootDir": "src", "sourceMap": true }, - "include": [ - "src/**/*.ts" - ] + "include": ["src/**/*.ts"] } diff --git a/tslint.json b/tslint.json index 5b8cc6c..9b374b8 100644 --- a/tslint.json +++ b/tslint.json @@ -1,60 +1,38 @@ { "rules": { - "arrow-parens": true, - "class-name": true, - "indent": [ - true, - "spaces" - ], - "prefer-const": true, - "no-duplicate-variable": true, - "no-eval": true, - "no-internal-module": true, - "no-trailing-whitespace": true, - "no-var-keyword": true, - "one-line": [ - true, - "check-open-brace", - "check-whitespace" - ], - "quotemark": [ - true, - "single", - "avoid-escape" - ], - "semicolon": [ - true, - "always" - ], - "trailing-comma": [ - true, - "multiline" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "variable-name": [ - true, - "ban-keywords" - ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] + "arrow-parens": true, + "class-name": true, + "indent": [true, "spaces"], + "prefer-const": true, + "no-duplicate-variable": true, + "no-eval": true, + "no-internal-module": true, + "no-trailing-whitespace": true, + "no-var-keyword": true, + "one-line": [true, "check-open-brace", "check-whitespace"], + "quotemark": [true, "single", "avoid-escape"], + "semicolon": [true, "always"], + "trailing-comma": [true, "multiline"], + "triple-equals": [true, "allow-null-check"], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [true, "ban-keywords"], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-any": true } }