├── .eslintrc.cjs ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── package-lock.json ├── package.json ├── test ├── assert.js ├── index.html ├── index.js ├── mocha-support.js ├── mocha.css ├── mocha.js ├── puppeteer.js └── tests │ └── webgpu-avoid-redundant-state-setting-tests.js ├── types.d.ts ├── webgpu-avoid-redundant-state-setting.js └── webgpu-check-redundant-state-setting.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | module.exports = { 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | browser: true, 6 | es2022: true, 7 | }, 8 | parserOptions: { 9 | sourceType: 'module', 10 | ecmaVersion: 2022, 11 | }, 12 | plugins: [ 13 | 'eslint-plugin-html', 14 | 'eslint-plugin-optional-comma-spacing', 15 | 'eslint-plugin-one-variable-per-var', 16 | 'eslint-plugin-require-trailing-comma', 17 | ], 18 | extends: [ 19 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 20 | ], 21 | rules: { 22 | 'brace-style': [2, '1tbs', { allowSingleLine: false }], 23 | camelcase: [0], 24 | 'comma-dangle': 0, 25 | 'comma-spacing': 0, 26 | 'comma-style': [2, 'last'], 27 | 'consistent-return': 2, 28 | curly: [2, 'all'], 29 | 'dot-notation': 0, 30 | 'eol-last': [0], 31 | eqeqeq: 2, 32 | 'global-strict': [0], 33 | 'key-spacing': [0], 34 | 'keyword-spacing': [1, { before: true, after: true, overrides: {} }], 35 | 'new-cap': 2, 36 | 'new-parens': 2, 37 | 'no-alert': 2, 38 | 'no-array-constructor': 2, 39 | 'no-caller': 2, 40 | 'no-catch-shadow': 2, 41 | 'no-comma-dangle': [0], 42 | 'no-const-assign': 2, 43 | 'no-eval': 2, 44 | 'no-extend-native': 2, 45 | 'no-extra-bind': 2, 46 | 'no-extra-parens': [2, 'functions'], 47 | 'no-implied-eval': 2, 48 | 'no-irregular-whitespace': 2, 49 | 'no-iterator': 2, 50 | 'no-label-var': 2, 51 | 'no-labels': 2, 52 | 'no-lone-blocks': 2, 53 | 'no-loop-func': 2, 54 | 'no-multi-spaces': [0], 55 | 'no-multi-str': 2, 56 | 'no-native-reassign': 2, 57 | 'no-new-func': 2, 58 | 'no-new-object': 2, 59 | 'no-new-wrappers': 2, 60 | 'no-new': 2, 61 | 'no-obj-calls': 2, 62 | 'no-octal-escape': 2, 63 | 'no-process-exit': 2, 64 | 'no-proto': 2, 65 | 'no-return-assign': 2, 66 | 'no-script-url': 2, 67 | 'no-sequences': 2, 68 | 'no-shadow-restricted-names': 2, 69 | 'no-shadow': [0], 70 | 'no-spaced-func': 2, 71 | 'no-trailing-spaces': 2, 72 | 'no-undef-init': 2, 73 | 'no-undef': 2, 74 | 'no-underscore-dangle': 2, 75 | 'no-unreachable': 2, 76 | 'no-unused-expressions': 2, 77 | 'no-use-before-define': 0, 78 | 'no-var': 2, 79 | 'no-with': 2, 80 | 'one-variable-per-var/one-variable-per-var': [2], 81 | 'optional-comma-spacing/optional-comma-spacing': [2, { after: true }], 82 | 'prefer-const': 2, 83 | 'require-trailing-comma/require-trailing-comma': [2], 84 | 'semi-spacing': [2, { before: false, after: true }], 85 | semi: [2, 'always'], 86 | 'space-before-function-paren': [ 87 | 2, 88 | { 89 | anonymous: 'always', 90 | named: 'never', 91 | asyncArrow: 'always', 92 | }, 93 | ], 94 | 'space-infix-ops': 2, 95 | 'space-unary-ops': [2, { words: true, nonwords: false }], 96 | strict: [2, 'function'], 97 | yoda: [2, 'never'], 98 | '@typescript-eslint/no-empty-function': 'off', 99 | '@typescript-eslint/no-explicit-any': 'off', // TODO: Reenable this and figure out how to fix code. 100 | '@typescript-eslint/no-non-null-assertion': 'off', 101 | '@typescript-eslint/no-unused-vars': 2, 102 | }, 103 | overrides: [ 104 | { 105 | files: ['*.ts', '*.tsx', 'src/samples/**/*', 'example/**/*'], 106 | rules: { 107 | 'no-undef': 'off', 108 | }, 109 | }, 110 | ], 111 | }; 112 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gregg Tavares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webgpu-avoid-redundant-state-setting 2 | 3 | Setting state multiple times can be a source of lost performance. 4 | There's overhead in calling from JavaScript into the WebGPU api. 5 | There's also no guarantee the implementation optimizes this stuff 6 | under the hood. 7 | 8 | So, this is an attempt at 9 | 10 | 1. ## Trivially doing it for you 11 | 12 | In this case just add 13 | 14 | ```js 15 | import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-avoid-redundant-state-setting.js'; 16 | ``` 17 | 18 | or 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | To the top of your app and see if there is any perf difference. 25 | 26 | note: there is overhead in checking for redundant state calls, especially for `setBindGroup` with dynamic offsets 27 | as the library has to check each offset so there is some possibility perf will be worse 28 | with this library. Measure for yourself or use (2) below to check your own code. 29 | 30 | 2. ## Making it easy to check if you're submitting redundant state 31 | 32 | This way you can optimize your code. 33 | 34 | In this case use 35 | 36 | ```js 37 | import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; 38 | ``` 39 | 40 | or 41 | 42 | ```html 43 | 44 | ``` 45 | 46 | Then check the JavaScript console. 47 | 48 | Note: this assumes you're using `requestAnimationFrame` to render. If you are using something else to render 49 | like `requestVideoFrameCallback` or `setTimeout` or other events, 50 | [look at the source](https://github.com/greggman/webgpu-avoid-redundant-state-setting/blob/main/webgpu-check-redundant-state-setting.js) 51 | to see how to do this check for your specific situation. 52 | 53 | If you see lots of redundant state setting, refactor your code to avoid it. You can look at the source code of this library 54 | for some ideas. The easiest to avoid are redundant `setVertexBuffer` and `setIndexBuffer` calls. `setBindGroup` is harder 55 | if it has to check dynamic offsets. 56 | 57 | ## Important! 58 | 59 | You do NOT need to avoid setting all redundant state. Rather, this library is just meant to check 60 | if you're setting 100s or 1000s of redundant state per pass. 61 | 62 | ## Notes 63 | 64 | There isn't very much state in WebGPU. The most (all?) state comes in `GPURenderPassEncoder` and `GPUComputePassEncoder`. 65 | For example, you set bindGroups by calling `setBindGroup` and those are sticky until you `end` the pass encoder. 66 | 67 | * compute and render pass state 68 | 69 | * pipeline: set via `setPipeline` 70 | * bindGroups: set via `setBindGroup` 71 | 72 | * render pass state 73 | 74 | * vertexBuffers: set via `setVertexBuffer` 75 | * indexBuffer: set via `setIndexBuffer` 76 | * viewport: set via `setViewport` 77 | * scissor: set via `setScissor` 78 | * blendConstant: set via `setBlendConstant` 79 | * stencilReference: set via `setStencilReference` 80 | 81 | The one exception is in a render pass, `executeBundles` resets some of the state, both before and after. 82 | The states reset are the `bindGroups`, `vertexBuffers`, `indexBuffer`, and `pipeline`. 83 | 84 | ## Testing 85 | 86 | [Live Tests](https://greggman.github.io/webgpu-avoid-redundant-state-setting/test/). 87 | 88 | During dev, serve the repo as in `npx servez .` then open a page to [`http://locahost:8080/test/`](http://locahost:8080/test/). 89 | 90 | ## License 91 | 92 | MIT 93 | 94 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgpu-avoid-redundant-state-setting", 3 | "version": "0.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "webgpu-avoid-redundant-state-setting", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "eslint": "^8.40.0", 13 | "eslint-plugin-html": "^7.1.0", 14 | "eslint-plugin-one-variable-per-var": "^0.0.3", 15 | "eslint-plugin-optional-comma-spacing": "^0.0.4", 16 | "eslint-plugin-require-trailing-comma": "^0.0.1" 17 | } 18 | }, 19 | "node_modules/@eslint-community/eslint-utils": { 20 | "version": "4.4.0", 21 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", 22 | "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", 23 | "dev": true, 24 | "dependencies": { 25 | "eslint-visitor-keys": "^3.3.0" 26 | }, 27 | "engines": { 28 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 29 | }, 30 | "peerDependencies": { 31 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 32 | } 33 | }, 34 | "node_modules/@eslint-community/regexpp": { 35 | "version": "4.5.1", 36 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", 37 | "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", 38 | "dev": true, 39 | "engines": { 40 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 41 | } 42 | }, 43 | "node_modules/@eslint/eslintrc": { 44 | "version": "2.0.3", 45 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", 46 | "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", 47 | "dev": true, 48 | "dependencies": { 49 | "ajv": "^6.12.4", 50 | "debug": "^4.3.2", 51 | "espree": "^9.5.2", 52 | "globals": "^13.19.0", 53 | "ignore": "^5.2.0", 54 | "import-fresh": "^3.2.1", 55 | "js-yaml": "^4.1.0", 56 | "minimatch": "^3.1.2", 57 | "strip-json-comments": "^3.1.1" 58 | }, 59 | "engines": { 60 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 61 | }, 62 | "funding": { 63 | "url": "https://opencollective.com/eslint" 64 | } 65 | }, 66 | "node_modules/@eslint/js": { 67 | "version": "8.42.0", 68 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", 69 | "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", 70 | "dev": true, 71 | "engines": { 72 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 73 | } 74 | }, 75 | "node_modules/@humanwhocodes/config-array": { 76 | "version": "0.11.10", 77 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", 78 | "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", 79 | "dev": true, 80 | "dependencies": { 81 | "@humanwhocodes/object-schema": "^1.2.1", 82 | "debug": "^4.1.1", 83 | "minimatch": "^3.0.5" 84 | }, 85 | "engines": { 86 | "node": ">=10.10.0" 87 | } 88 | }, 89 | "node_modules/@humanwhocodes/module-importer": { 90 | "version": "1.0.1", 91 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 92 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 93 | "dev": true, 94 | "engines": { 95 | "node": ">=12.22" 96 | }, 97 | "funding": { 98 | "type": "github", 99 | "url": "https://github.com/sponsors/nzakas" 100 | } 101 | }, 102 | "node_modules/@humanwhocodes/object-schema": { 103 | "version": "1.2.1", 104 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 105 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", 106 | "dev": true 107 | }, 108 | "node_modules/@nodelib/fs.scandir": { 109 | "version": "2.1.5", 110 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 111 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 112 | "dev": true, 113 | "dependencies": { 114 | "@nodelib/fs.stat": "2.0.5", 115 | "run-parallel": "^1.1.9" 116 | }, 117 | "engines": { 118 | "node": ">= 8" 119 | } 120 | }, 121 | "node_modules/@nodelib/fs.stat": { 122 | "version": "2.0.5", 123 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 124 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 125 | "dev": true, 126 | "engines": { 127 | "node": ">= 8" 128 | } 129 | }, 130 | "node_modules/@nodelib/fs.walk": { 131 | "version": "1.2.8", 132 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 133 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 134 | "dev": true, 135 | "dependencies": { 136 | "@nodelib/fs.scandir": "2.1.5", 137 | "fastq": "^1.6.0" 138 | }, 139 | "engines": { 140 | "node": ">= 8" 141 | } 142 | }, 143 | "node_modules/acorn": { 144 | "version": "8.8.2", 145 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", 146 | "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", 147 | "dev": true, 148 | "bin": { 149 | "acorn": "bin/acorn" 150 | }, 151 | "engines": { 152 | "node": ">=0.4.0" 153 | } 154 | }, 155 | "node_modules/acorn-jsx": { 156 | "version": "5.3.2", 157 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 158 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 159 | "dev": true, 160 | "peerDependencies": { 161 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 162 | } 163 | }, 164 | "node_modules/ajv": { 165 | "version": "6.12.6", 166 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 167 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 168 | "dev": true, 169 | "dependencies": { 170 | "fast-deep-equal": "^3.1.1", 171 | "fast-json-stable-stringify": "^2.0.0", 172 | "json-schema-traverse": "^0.4.1", 173 | "uri-js": "^4.2.2" 174 | }, 175 | "funding": { 176 | "type": "github", 177 | "url": "https://github.com/sponsors/epoberezkin" 178 | } 179 | }, 180 | "node_modules/ansi-regex": { 181 | "version": "5.0.1", 182 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 183 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 184 | "dev": true, 185 | "engines": { 186 | "node": ">=8" 187 | } 188 | }, 189 | "node_modules/ansi-styles": { 190 | "version": "4.3.0", 191 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 192 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 193 | "dev": true, 194 | "dependencies": { 195 | "color-convert": "^2.0.1" 196 | }, 197 | "engines": { 198 | "node": ">=8" 199 | }, 200 | "funding": { 201 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 202 | } 203 | }, 204 | "node_modules/argparse": { 205 | "version": "2.0.1", 206 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 207 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 208 | "dev": true 209 | }, 210 | "node_modules/balanced-match": { 211 | "version": "1.0.2", 212 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 213 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 214 | "dev": true 215 | }, 216 | "node_modules/brace-expansion": { 217 | "version": "1.1.11", 218 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 219 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 220 | "dev": true, 221 | "dependencies": { 222 | "balanced-match": "^1.0.0", 223 | "concat-map": "0.0.1" 224 | } 225 | }, 226 | "node_modules/callsites": { 227 | "version": "3.1.0", 228 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 229 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 230 | "dev": true, 231 | "engines": { 232 | "node": ">=6" 233 | } 234 | }, 235 | "node_modules/chalk": { 236 | "version": "4.1.2", 237 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 238 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 239 | "dev": true, 240 | "dependencies": { 241 | "ansi-styles": "^4.1.0", 242 | "supports-color": "^7.1.0" 243 | }, 244 | "engines": { 245 | "node": ">=10" 246 | }, 247 | "funding": { 248 | "url": "https://github.com/chalk/chalk?sponsor=1" 249 | } 250 | }, 251 | "node_modules/color-convert": { 252 | "version": "2.0.1", 253 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 254 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 255 | "dev": true, 256 | "dependencies": { 257 | "color-name": "~1.1.4" 258 | }, 259 | "engines": { 260 | "node": ">=7.0.0" 261 | } 262 | }, 263 | "node_modules/color-name": { 264 | "version": "1.1.4", 265 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 266 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 267 | "dev": true 268 | }, 269 | "node_modules/concat-map": { 270 | "version": "0.0.1", 271 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 272 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 273 | "dev": true 274 | }, 275 | "node_modules/cross-spawn": { 276 | "version": "7.0.3", 277 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 278 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 279 | "dev": true, 280 | "dependencies": { 281 | "path-key": "^3.1.0", 282 | "shebang-command": "^2.0.0", 283 | "which": "^2.0.1" 284 | }, 285 | "engines": { 286 | "node": ">= 8" 287 | } 288 | }, 289 | "node_modules/debug": { 290 | "version": "4.3.4", 291 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 292 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 293 | "dev": true, 294 | "dependencies": { 295 | "ms": "2.1.2" 296 | }, 297 | "engines": { 298 | "node": ">=6.0" 299 | }, 300 | "peerDependenciesMeta": { 301 | "supports-color": { 302 | "optional": true 303 | } 304 | } 305 | }, 306 | "node_modules/deep-is": { 307 | "version": "0.1.4", 308 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 309 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 310 | "dev": true 311 | }, 312 | "node_modules/doctrine": { 313 | "version": "3.0.0", 314 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 315 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 316 | "dev": true, 317 | "dependencies": { 318 | "esutils": "^2.0.2" 319 | }, 320 | "engines": { 321 | "node": ">=6.0.0" 322 | } 323 | }, 324 | "node_modules/dom-serializer": { 325 | "version": "2.0.0", 326 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 327 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 328 | "dev": true, 329 | "dependencies": { 330 | "domelementtype": "^2.3.0", 331 | "domhandler": "^5.0.2", 332 | "entities": "^4.2.0" 333 | }, 334 | "funding": { 335 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 336 | } 337 | }, 338 | "node_modules/domelementtype": { 339 | "version": "2.3.0", 340 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 341 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 342 | "dev": true, 343 | "funding": [ 344 | { 345 | "type": "github", 346 | "url": "https://github.com/sponsors/fb55" 347 | } 348 | ] 349 | }, 350 | "node_modules/domhandler": { 351 | "version": "5.0.3", 352 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 353 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 354 | "dev": true, 355 | "dependencies": { 356 | "domelementtype": "^2.3.0" 357 | }, 358 | "engines": { 359 | "node": ">= 4" 360 | }, 361 | "funding": { 362 | "url": "https://github.com/fb55/domhandler?sponsor=1" 363 | } 364 | }, 365 | "node_modules/domutils": { 366 | "version": "3.1.0", 367 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 368 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 369 | "dev": true, 370 | "dependencies": { 371 | "dom-serializer": "^2.0.0", 372 | "domelementtype": "^2.3.0", 373 | "domhandler": "^5.0.3" 374 | }, 375 | "funding": { 376 | "url": "https://github.com/fb55/domutils?sponsor=1" 377 | } 378 | }, 379 | "node_modules/entities": { 380 | "version": "4.5.0", 381 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 382 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 383 | "dev": true, 384 | "engines": { 385 | "node": ">=0.12" 386 | }, 387 | "funding": { 388 | "url": "https://github.com/fb55/entities?sponsor=1" 389 | } 390 | }, 391 | "node_modules/escape-string-regexp": { 392 | "version": "4.0.0", 393 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 394 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 395 | "dev": true, 396 | "engines": { 397 | "node": ">=10" 398 | }, 399 | "funding": { 400 | "url": "https://github.com/sponsors/sindresorhus" 401 | } 402 | }, 403 | "node_modules/eslint": { 404 | "version": "8.42.0", 405 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", 406 | "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", 407 | "dev": true, 408 | "dependencies": { 409 | "@eslint-community/eslint-utils": "^4.2.0", 410 | "@eslint-community/regexpp": "^4.4.0", 411 | "@eslint/eslintrc": "^2.0.3", 412 | "@eslint/js": "8.42.0", 413 | "@humanwhocodes/config-array": "^0.11.10", 414 | "@humanwhocodes/module-importer": "^1.0.1", 415 | "@nodelib/fs.walk": "^1.2.8", 416 | "ajv": "^6.10.0", 417 | "chalk": "^4.0.0", 418 | "cross-spawn": "^7.0.2", 419 | "debug": "^4.3.2", 420 | "doctrine": "^3.0.0", 421 | "escape-string-regexp": "^4.0.0", 422 | "eslint-scope": "^7.2.0", 423 | "eslint-visitor-keys": "^3.4.1", 424 | "espree": "^9.5.2", 425 | "esquery": "^1.4.2", 426 | "esutils": "^2.0.2", 427 | "fast-deep-equal": "^3.1.3", 428 | "file-entry-cache": "^6.0.1", 429 | "find-up": "^5.0.0", 430 | "glob-parent": "^6.0.2", 431 | "globals": "^13.19.0", 432 | "graphemer": "^1.4.0", 433 | "ignore": "^5.2.0", 434 | "import-fresh": "^3.0.0", 435 | "imurmurhash": "^0.1.4", 436 | "is-glob": "^4.0.0", 437 | "is-path-inside": "^3.0.3", 438 | "js-yaml": "^4.1.0", 439 | "json-stable-stringify-without-jsonify": "^1.0.1", 440 | "levn": "^0.4.1", 441 | "lodash.merge": "^4.6.2", 442 | "minimatch": "^3.1.2", 443 | "natural-compare": "^1.4.0", 444 | "optionator": "^0.9.1", 445 | "strip-ansi": "^6.0.1", 446 | "strip-json-comments": "^3.1.0", 447 | "text-table": "^0.2.0" 448 | }, 449 | "bin": { 450 | "eslint": "bin/eslint.js" 451 | }, 452 | "engines": { 453 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 454 | }, 455 | "funding": { 456 | "url": "https://opencollective.com/eslint" 457 | } 458 | }, 459 | "node_modules/eslint-plugin-html": { 460 | "version": "7.1.0", 461 | "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz", 462 | "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==", 463 | "dev": true, 464 | "dependencies": { 465 | "htmlparser2": "^8.0.1" 466 | } 467 | }, 468 | "node_modules/eslint-plugin-one-variable-per-var": { 469 | "version": "0.0.3", 470 | "resolved": "https://registry.npmjs.org/eslint-plugin-one-variable-per-var/-/eslint-plugin-one-variable-per-var-0.0.3.tgz", 471 | "integrity": "sha512-gdqNqxyuGNjiJFIbOFMV5rs+cAR8TwQmldwsHINFNtgsm7/D5ogFmplibJL4UzQpINb5RwiXdOY0Ixed71PtIQ==", 472 | "dev": true, 473 | "peerDependencies": { 474 | "eslint": ">=0.8.0" 475 | } 476 | }, 477 | "node_modules/eslint-plugin-optional-comma-spacing": { 478 | "version": "0.0.4", 479 | "resolved": "https://registry.npmjs.org/eslint-plugin-optional-comma-spacing/-/eslint-plugin-optional-comma-spacing-0.0.4.tgz", 480 | "integrity": "sha512-VXTJW7LgMxjJngnCrPl/oLxw74aJvDoNLYYXjOedqq4qIuEb9JBqFJolAIHQFSqWPY0teQEkl8HKxfphu3eczg==", 481 | "dev": true, 482 | "peerDependencies": { 483 | "eslint": ">=0.8.0" 484 | } 485 | }, 486 | "node_modules/eslint-plugin-require-trailing-comma": { 487 | "version": "0.0.1", 488 | "resolved": "https://registry.npmjs.org/eslint-plugin-require-trailing-comma/-/eslint-plugin-require-trailing-comma-0.0.1.tgz", 489 | "integrity": "sha512-PFNp10AQZsFUbbt70esqj2WW6wd0mJbZaKZMSdWsvTqRPjOdzQd9Cn1xx7i8YCrAGYZCZpJIO8O7/22sQvuCTA==", 490 | "dev": true, 491 | "peerDependencies": { 492 | "eslint": ">=0.8.0" 493 | } 494 | }, 495 | "node_modules/eslint-scope": { 496 | "version": "7.2.0", 497 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", 498 | "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", 499 | "dev": true, 500 | "dependencies": { 501 | "esrecurse": "^4.3.0", 502 | "estraverse": "^5.2.0" 503 | }, 504 | "engines": { 505 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 506 | }, 507 | "funding": { 508 | "url": "https://opencollective.com/eslint" 509 | } 510 | }, 511 | "node_modules/eslint-visitor-keys": { 512 | "version": "3.4.1", 513 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", 514 | "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", 515 | "dev": true, 516 | "engines": { 517 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 518 | }, 519 | "funding": { 520 | "url": "https://opencollective.com/eslint" 521 | } 522 | }, 523 | "node_modules/espree": { 524 | "version": "9.5.2", 525 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", 526 | "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", 527 | "dev": true, 528 | "dependencies": { 529 | "acorn": "^8.8.0", 530 | "acorn-jsx": "^5.3.2", 531 | "eslint-visitor-keys": "^3.4.1" 532 | }, 533 | "engines": { 534 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 535 | }, 536 | "funding": { 537 | "url": "https://opencollective.com/eslint" 538 | } 539 | }, 540 | "node_modules/esquery": { 541 | "version": "1.5.0", 542 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", 543 | "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", 544 | "dev": true, 545 | "dependencies": { 546 | "estraverse": "^5.1.0" 547 | }, 548 | "engines": { 549 | "node": ">=0.10" 550 | } 551 | }, 552 | "node_modules/esrecurse": { 553 | "version": "4.3.0", 554 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 555 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 556 | "dev": true, 557 | "dependencies": { 558 | "estraverse": "^5.2.0" 559 | }, 560 | "engines": { 561 | "node": ">=4.0" 562 | } 563 | }, 564 | "node_modules/estraverse": { 565 | "version": "5.3.0", 566 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 567 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 568 | "dev": true, 569 | "engines": { 570 | "node": ">=4.0" 571 | } 572 | }, 573 | "node_modules/esutils": { 574 | "version": "2.0.3", 575 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 576 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 577 | "dev": true, 578 | "engines": { 579 | "node": ">=0.10.0" 580 | } 581 | }, 582 | "node_modules/fast-deep-equal": { 583 | "version": "3.1.3", 584 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 585 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 586 | "dev": true 587 | }, 588 | "node_modules/fast-json-stable-stringify": { 589 | "version": "2.1.0", 590 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 591 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 592 | "dev": true 593 | }, 594 | "node_modules/fast-levenshtein": { 595 | "version": "2.0.6", 596 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 597 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 598 | "dev": true 599 | }, 600 | "node_modules/fastq": { 601 | "version": "1.15.0", 602 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 603 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 604 | "dev": true, 605 | "dependencies": { 606 | "reusify": "^1.0.4" 607 | } 608 | }, 609 | "node_modules/file-entry-cache": { 610 | "version": "6.0.1", 611 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 612 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 613 | "dev": true, 614 | "dependencies": { 615 | "flat-cache": "^3.0.4" 616 | }, 617 | "engines": { 618 | "node": "^10.12.0 || >=12.0.0" 619 | } 620 | }, 621 | "node_modules/find-up": { 622 | "version": "5.0.0", 623 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 624 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 625 | "dev": true, 626 | "dependencies": { 627 | "locate-path": "^6.0.0", 628 | "path-exists": "^4.0.0" 629 | }, 630 | "engines": { 631 | "node": ">=10" 632 | }, 633 | "funding": { 634 | "url": "https://github.com/sponsors/sindresorhus" 635 | } 636 | }, 637 | "node_modules/flat-cache": { 638 | "version": "3.0.4", 639 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 640 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 641 | "dev": true, 642 | "dependencies": { 643 | "flatted": "^3.1.0", 644 | "rimraf": "^3.0.2" 645 | }, 646 | "engines": { 647 | "node": "^10.12.0 || >=12.0.0" 648 | } 649 | }, 650 | "node_modules/flatted": { 651 | "version": "3.2.7", 652 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", 653 | "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", 654 | "dev": true 655 | }, 656 | "node_modules/fs.realpath": { 657 | "version": "1.0.0", 658 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 659 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 660 | "dev": true 661 | }, 662 | "node_modules/glob": { 663 | "version": "7.2.3", 664 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 665 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 666 | "dev": true, 667 | "dependencies": { 668 | "fs.realpath": "^1.0.0", 669 | "inflight": "^1.0.4", 670 | "inherits": "2", 671 | "minimatch": "^3.1.1", 672 | "once": "^1.3.0", 673 | "path-is-absolute": "^1.0.0" 674 | }, 675 | "engines": { 676 | "node": "*" 677 | }, 678 | "funding": { 679 | "url": "https://github.com/sponsors/isaacs" 680 | } 681 | }, 682 | "node_modules/glob-parent": { 683 | "version": "6.0.2", 684 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 685 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 686 | "dev": true, 687 | "dependencies": { 688 | "is-glob": "^4.0.3" 689 | }, 690 | "engines": { 691 | "node": ">=10.13.0" 692 | } 693 | }, 694 | "node_modules/globals": { 695 | "version": "13.20.0", 696 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", 697 | "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", 698 | "dev": true, 699 | "dependencies": { 700 | "type-fest": "^0.20.2" 701 | }, 702 | "engines": { 703 | "node": ">=8" 704 | }, 705 | "funding": { 706 | "url": "https://github.com/sponsors/sindresorhus" 707 | } 708 | }, 709 | "node_modules/graphemer": { 710 | "version": "1.4.0", 711 | "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 712 | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 713 | "dev": true 714 | }, 715 | "node_modules/has-flag": { 716 | "version": "4.0.0", 717 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 718 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 719 | "dev": true, 720 | "engines": { 721 | "node": ">=8" 722 | } 723 | }, 724 | "node_modules/htmlparser2": { 725 | "version": "8.0.2", 726 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", 727 | "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", 728 | "dev": true, 729 | "funding": [ 730 | "https://github.com/fb55/htmlparser2?sponsor=1", 731 | { 732 | "type": "github", 733 | "url": "https://github.com/sponsors/fb55" 734 | } 735 | ], 736 | "dependencies": { 737 | "domelementtype": "^2.3.0", 738 | "domhandler": "^5.0.3", 739 | "domutils": "^3.0.1", 740 | "entities": "^4.4.0" 741 | } 742 | }, 743 | "node_modules/ignore": { 744 | "version": "5.2.4", 745 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", 746 | "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", 747 | "dev": true, 748 | "engines": { 749 | "node": ">= 4" 750 | } 751 | }, 752 | "node_modules/import-fresh": { 753 | "version": "3.3.0", 754 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 755 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 756 | "dev": true, 757 | "dependencies": { 758 | "parent-module": "^1.0.0", 759 | "resolve-from": "^4.0.0" 760 | }, 761 | "engines": { 762 | "node": ">=6" 763 | }, 764 | "funding": { 765 | "url": "https://github.com/sponsors/sindresorhus" 766 | } 767 | }, 768 | "node_modules/imurmurhash": { 769 | "version": "0.1.4", 770 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 771 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 772 | "dev": true, 773 | "engines": { 774 | "node": ">=0.8.19" 775 | } 776 | }, 777 | "node_modules/inflight": { 778 | "version": "1.0.6", 779 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 780 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 781 | "dev": true, 782 | "dependencies": { 783 | "once": "^1.3.0", 784 | "wrappy": "1" 785 | } 786 | }, 787 | "node_modules/inherits": { 788 | "version": "2.0.4", 789 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 790 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 791 | "dev": true 792 | }, 793 | "node_modules/is-extglob": { 794 | "version": "2.1.1", 795 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 796 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 797 | "dev": true, 798 | "engines": { 799 | "node": ">=0.10.0" 800 | } 801 | }, 802 | "node_modules/is-glob": { 803 | "version": "4.0.3", 804 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 805 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 806 | "dev": true, 807 | "dependencies": { 808 | "is-extglob": "^2.1.1" 809 | }, 810 | "engines": { 811 | "node": ">=0.10.0" 812 | } 813 | }, 814 | "node_modules/is-path-inside": { 815 | "version": "3.0.3", 816 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", 817 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", 818 | "dev": true, 819 | "engines": { 820 | "node": ">=8" 821 | } 822 | }, 823 | "node_modules/isexe": { 824 | "version": "2.0.0", 825 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 826 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 827 | "dev": true 828 | }, 829 | "node_modules/js-yaml": { 830 | "version": "4.1.0", 831 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 832 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 833 | "dev": true, 834 | "dependencies": { 835 | "argparse": "^2.0.1" 836 | }, 837 | "bin": { 838 | "js-yaml": "bin/js-yaml.js" 839 | } 840 | }, 841 | "node_modules/json-schema-traverse": { 842 | "version": "0.4.1", 843 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 844 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 845 | "dev": true 846 | }, 847 | "node_modules/json-stable-stringify-without-jsonify": { 848 | "version": "1.0.1", 849 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 850 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 851 | "dev": true 852 | }, 853 | "node_modules/levn": { 854 | "version": "0.4.1", 855 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 856 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 857 | "dev": true, 858 | "dependencies": { 859 | "prelude-ls": "^1.2.1", 860 | "type-check": "~0.4.0" 861 | }, 862 | "engines": { 863 | "node": ">= 0.8.0" 864 | } 865 | }, 866 | "node_modules/locate-path": { 867 | "version": "6.0.0", 868 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 869 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 870 | "dev": true, 871 | "dependencies": { 872 | "p-locate": "^5.0.0" 873 | }, 874 | "engines": { 875 | "node": ">=10" 876 | }, 877 | "funding": { 878 | "url": "https://github.com/sponsors/sindresorhus" 879 | } 880 | }, 881 | "node_modules/lodash.merge": { 882 | "version": "4.6.2", 883 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 884 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 885 | "dev": true 886 | }, 887 | "node_modules/minimatch": { 888 | "version": "3.1.2", 889 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 890 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 891 | "dev": true, 892 | "dependencies": { 893 | "brace-expansion": "^1.1.7" 894 | }, 895 | "engines": { 896 | "node": "*" 897 | } 898 | }, 899 | "node_modules/ms": { 900 | "version": "2.1.2", 901 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 902 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 903 | "dev": true 904 | }, 905 | "node_modules/natural-compare": { 906 | "version": "1.4.0", 907 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 908 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 909 | "dev": true 910 | }, 911 | "node_modules/once": { 912 | "version": "1.4.0", 913 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 914 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 915 | "dev": true, 916 | "dependencies": { 917 | "wrappy": "1" 918 | } 919 | }, 920 | "node_modules/optionator": { 921 | "version": "0.9.1", 922 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 923 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 924 | "dev": true, 925 | "dependencies": { 926 | "deep-is": "^0.1.3", 927 | "fast-levenshtein": "^2.0.6", 928 | "levn": "^0.4.1", 929 | "prelude-ls": "^1.2.1", 930 | "type-check": "^0.4.0", 931 | "word-wrap": "^1.2.3" 932 | }, 933 | "engines": { 934 | "node": ">= 0.8.0" 935 | } 936 | }, 937 | "node_modules/p-limit": { 938 | "version": "3.1.0", 939 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 940 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 941 | "dev": true, 942 | "dependencies": { 943 | "yocto-queue": "^0.1.0" 944 | }, 945 | "engines": { 946 | "node": ">=10" 947 | }, 948 | "funding": { 949 | "url": "https://github.com/sponsors/sindresorhus" 950 | } 951 | }, 952 | "node_modules/p-locate": { 953 | "version": "5.0.0", 954 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 955 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 956 | "dev": true, 957 | "dependencies": { 958 | "p-limit": "^3.0.2" 959 | }, 960 | "engines": { 961 | "node": ">=10" 962 | }, 963 | "funding": { 964 | "url": "https://github.com/sponsors/sindresorhus" 965 | } 966 | }, 967 | "node_modules/parent-module": { 968 | "version": "1.0.1", 969 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 970 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 971 | "dev": true, 972 | "dependencies": { 973 | "callsites": "^3.0.0" 974 | }, 975 | "engines": { 976 | "node": ">=6" 977 | } 978 | }, 979 | "node_modules/path-exists": { 980 | "version": "4.0.0", 981 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 982 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 983 | "dev": true, 984 | "engines": { 985 | "node": ">=8" 986 | } 987 | }, 988 | "node_modules/path-is-absolute": { 989 | "version": "1.0.1", 990 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 991 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 992 | "dev": true, 993 | "engines": { 994 | "node": ">=0.10.0" 995 | } 996 | }, 997 | "node_modules/path-key": { 998 | "version": "3.1.1", 999 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1000 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1001 | "dev": true, 1002 | "engines": { 1003 | "node": ">=8" 1004 | } 1005 | }, 1006 | "node_modules/prelude-ls": { 1007 | "version": "1.2.1", 1008 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1009 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1010 | "dev": true, 1011 | "engines": { 1012 | "node": ">= 0.8.0" 1013 | } 1014 | }, 1015 | "node_modules/punycode": { 1016 | "version": "2.3.0", 1017 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 1018 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", 1019 | "dev": true, 1020 | "engines": { 1021 | "node": ">=6" 1022 | } 1023 | }, 1024 | "node_modules/queue-microtask": { 1025 | "version": "1.2.3", 1026 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1027 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1028 | "dev": true, 1029 | "funding": [ 1030 | { 1031 | "type": "github", 1032 | "url": "https://github.com/sponsors/feross" 1033 | }, 1034 | { 1035 | "type": "patreon", 1036 | "url": "https://www.patreon.com/feross" 1037 | }, 1038 | { 1039 | "type": "consulting", 1040 | "url": "https://feross.org/support" 1041 | } 1042 | ] 1043 | }, 1044 | "node_modules/resolve-from": { 1045 | "version": "4.0.0", 1046 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1047 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1048 | "dev": true, 1049 | "engines": { 1050 | "node": ">=4" 1051 | } 1052 | }, 1053 | "node_modules/reusify": { 1054 | "version": "1.0.4", 1055 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1056 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1057 | "dev": true, 1058 | "engines": { 1059 | "iojs": ">=1.0.0", 1060 | "node": ">=0.10.0" 1061 | } 1062 | }, 1063 | "node_modules/rimraf": { 1064 | "version": "3.0.2", 1065 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1066 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1067 | "dev": true, 1068 | "dependencies": { 1069 | "glob": "^7.1.3" 1070 | }, 1071 | "bin": { 1072 | "rimraf": "bin.js" 1073 | }, 1074 | "funding": { 1075 | "url": "https://github.com/sponsors/isaacs" 1076 | } 1077 | }, 1078 | "node_modules/run-parallel": { 1079 | "version": "1.2.0", 1080 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1081 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1082 | "dev": true, 1083 | "funding": [ 1084 | { 1085 | "type": "github", 1086 | "url": "https://github.com/sponsors/feross" 1087 | }, 1088 | { 1089 | "type": "patreon", 1090 | "url": "https://www.patreon.com/feross" 1091 | }, 1092 | { 1093 | "type": "consulting", 1094 | "url": "https://feross.org/support" 1095 | } 1096 | ], 1097 | "dependencies": { 1098 | "queue-microtask": "^1.2.2" 1099 | } 1100 | }, 1101 | "node_modules/shebang-command": { 1102 | "version": "2.0.0", 1103 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1104 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1105 | "dev": true, 1106 | "dependencies": { 1107 | "shebang-regex": "^3.0.0" 1108 | }, 1109 | "engines": { 1110 | "node": ">=8" 1111 | } 1112 | }, 1113 | "node_modules/shebang-regex": { 1114 | "version": "3.0.0", 1115 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1116 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1117 | "dev": true, 1118 | "engines": { 1119 | "node": ">=8" 1120 | } 1121 | }, 1122 | "node_modules/strip-ansi": { 1123 | "version": "6.0.1", 1124 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1125 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1126 | "dev": true, 1127 | "dependencies": { 1128 | "ansi-regex": "^5.0.1" 1129 | }, 1130 | "engines": { 1131 | "node": ">=8" 1132 | } 1133 | }, 1134 | "node_modules/strip-json-comments": { 1135 | "version": "3.1.1", 1136 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1137 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1138 | "dev": true, 1139 | "engines": { 1140 | "node": ">=8" 1141 | }, 1142 | "funding": { 1143 | "url": "https://github.com/sponsors/sindresorhus" 1144 | } 1145 | }, 1146 | "node_modules/supports-color": { 1147 | "version": "7.2.0", 1148 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1149 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1150 | "dev": true, 1151 | "dependencies": { 1152 | "has-flag": "^4.0.0" 1153 | }, 1154 | "engines": { 1155 | "node": ">=8" 1156 | } 1157 | }, 1158 | "node_modules/text-table": { 1159 | "version": "0.2.0", 1160 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1161 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 1162 | "dev": true 1163 | }, 1164 | "node_modules/type-check": { 1165 | "version": "0.4.0", 1166 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1167 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1168 | "dev": true, 1169 | "dependencies": { 1170 | "prelude-ls": "^1.2.1" 1171 | }, 1172 | "engines": { 1173 | "node": ">= 0.8.0" 1174 | } 1175 | }, 1176 | "node_modules/type-fest": { 1177 | "version": "0.20.2", 1178 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 1179 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 1180 | "dev": true, 1181 | "engines": { 1182 | "node": ">=10" 1183 | }, 1184 | "funding": { 1185 | "url": "https://github.com/sponsors/sindresorhus" 1186 | } 1187 | }, 1188 | "node_modules/uri-js": { 1189 | "version": "4.4.1", 1190 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1191 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1192 | "dev": true, 1193 | "dependencies": { 1194 | "punycode": "^2.1.0" 1195 | } 1196 | }, 1197 | "node_modules/which": { 1198 | "version": "2.0.2", 1199 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1200 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1201 | "dev": true, 1202 | "dependencies": { 1203 | "isexe": "^2.0.0" 1204 | }, 1205 | "bin": { 1206 | "node-which": "bin/node-which" 1207 | }, 1208 | "engines": { 1209 | "node": ">= 8" 1210 | } 1211 | }, 1212 | "node_modules/word-wrap": { 1213 | "version": "1.2.3", 1214 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1215 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1216 | "dev": true, 1217 | "engines": { 1218 | "node": ">=0.10.0" 1219 | } 1220 | }, 1221 | "node_modules/wrappy": { 1222 | "version": "1.0.2", 1223 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1224 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1225 | "dev": true 1226 | }, 1227 | "node_modules/yocto-queue": { 1228 | "version": "0.1.0", 1229 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1230 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1231 | "dev": true, 1232 | "engines": { 1233 | "node": ">=10" 1234 | }, 1235 | "funding": { 1236 | "url": "https://github.com/sponsors/sindresorhus" 1237 | } 1238 | } 1239 | }, 1240 | "dependencies": { 1241 | "@eslint-community/eslint-utils": { 1242 | "version": "4.4.0", 1243 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", 1244 | "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", 1245 | "dev": true, 1246 | "requires": { 1247 | "eslint-visitor-keys": "^3.3.0" 1248 | } 1249 | }, 1250 | "@eslint-community/regexpp": { 1251 | "version": "4.5.1", 1252 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", 1253 | "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", 1254 | "dev": true 1255 | }, 1256 | "@eslint/eslintrc": { 1257 | "version": "2.0.3", 1258 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", 1259 | "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", 1260 | "dev": true, 1261 | "requires": { 1262 | "ajv": "^6.12.4", 1263 | "debug": "^4.3.2", 1264 | "espree": "^9.5.2", 1265 | "globals": "^13.19.0", 1266 | "ignore": "^5.2.0", 1267 | "import-fresh": "^3.2.1", 1268 | "js-yaml": "^4.1.0", 1269 | "minimatch": "^3.1.2", 1270 | "strip-json-comments": "^3.1.1" 1271 | } 1272 | }, 1273 | "@eslint/js": { 1274 | "version": "8.42.0", 1275 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", 1276 | "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", 1277 | "dev": true 1278 | }, 1279 | "@humanwhocodes/config-array": { 1280 | "version": "0.11.10", 1281 | "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", 1282 | "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", 1283 | "dev": true, 1284 | "requires": { 1285 | "@humanwhocodes/object-schema": "^1.2.1", 1286 | "debug": "^4.1.1", 1287 | "minimatch": "^3.0.5" 1288 | } 1289 | }, 1290 | "@humanwhocodes/module-importer": { 1291 | "version": "1.0.1", 1292 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 1293 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 1294 | "dev": true 1295 | }, 1296 | "@humanwhocodes/object-schema": { 1297 | "version": "1.2.1", 1298 | "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", 1299 | "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", 1300 | "dev": true 1301 | }, 1302 | "@nodelib/fs.scandir": { 1303 | "version": "2.1.5", 1304 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 1305 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 1306 | "dev": true, 1307 | "requires": { 1308 | "@nodelib/fs.stat": "2.0.5", 1309 | "run-parallel": "^1.1.9" 1310 | } 1311 | }, 1312 | "@nodelib/fs.stat": { 1313 | "version": "2.0.5", 1314 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 1315 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 1316 | "dev": true 1317 | }, 1318 | "@nodelib/fs.walk": { 1319 | "version": "1.2.8", 1320 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 1321 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 1322 | "dev": true, 1323 | "requires": { 1324 | "@nodelib/fs.scandir": "2.1.5", 1325 | "fastq": "^1.6.0" 1326 | } 1327 | }, 1328 | "acorn": { 1329 | "version": "8.8.2", 1330 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", 1331 | "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", 1332 | "dev": true 1333 | }, 1334 | "acorn-jsx": { 1335 | "version": "5.3.2", 1336 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 1337 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 1338 | "dev": true, 1339 | "requires": {} 1340 | }, 1341 | "ajv": { 1342 | "version": "6.12.6", 1343 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 1344 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 1345 | "dev": true, 1346 | "requires": { 1347 | "fast-deep-equal": "^3.1.1", 1348 | "fast-json-stable-stringify": "^2.0.0", 1349 | "json-schema-traverse": "^0.4.1", 1350 | "uri-js": "^4.2.2" 1351 | } 1352 | }, 1353 | "ansi-regex": { 1354 | "version": "5.0.1", 1355 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1356 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1357 | "dev": true 1358 | }, 1359 | "ansi-styles": { 1360 | "version": "4.3.0", 1361 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1362 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1363 | "dev": true, 1364 | "requires": { 1365 | "color-convert": "^2.0.1" 1366 | } 1367 | }, 1368 | "argparse": { 1369 | "version": "2.0.1", 1370 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 1371 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 1372 | "dev": true 1373 | }, 1374 | "balanced-match": { 1375 | "version": "1.0.2", 1376 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1377 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1378 | "dev": true 1379 | }, 1380 | "brace-expansion": { 1381 | "version": "1.1.11", 1382 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1383 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1384 | "dev": true, 1385 | "requires": { 1386 | "balanced-match": "^1.0.0", 1387 | "concat-map": "0.0.1" 1388 | } 1389 | }, 1390 | "callsites": { 1391 | "version": "3.1.0", 1392 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 1393 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 1394 | "dev": true 1395 | }, 1396 | "chalk": { 1397 | "version": "4.1.2", 1398 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1399 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1400 | "dev": true, 1401 | "requires": { 1402 | "ansi-styles": "^4.1.0", 1403 | "supports-color": "^7.1.0" 1404 | } 1405 | }, 1406 | "color-convert": { 1407 | "version": "2.0.1", 1408 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1409 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1410 | "dev": true, 1411 | "requires": { 1412 | "color-name": "~1.1.4" 1413 | } 1414 | }, 1415 | "color-name": { 1416 | "version": "1.1.4", 1417 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1418 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1419 | "dev": true 1420 | }, 1421 | "concat-map": { 1422 | "version": "0.0.1", 1423 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1424 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1425 | "dev": true 1426 | }, 1427 | "cross-spawn": { 1428 | "version": "7.0.3", 1429 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 1430 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 1431 | "dev": true, 1432 | "requires": { 1433 | "path-key": "^3.1.0", 1434 | "shebang-command": "^2.0.0", 1435 | "which": "^2.0.1" 1436 | } 1437 | }, 1438 | "debug": { 1439 | "version": "4.3.4", 1440 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1441 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1442 | "dev": true, 1443 | "requires": { 1444 | "ms": "2.1.2" 1445 | } 1446 | }, 1447 | "deep-is": { 1448 | "version": "0.1.4", 1449 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 1450 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 1451 | "dev": true 1452 | }, 1453 | "doctrine": { 1454 | "version": "3.0.0", 1455 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 1456 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 1457 | "dev": true, 1458 | "requires": { 1459 | "esutils": "^2.0.2" 1460 | } 1461 | }, 1462 | "dom-serializer": { 1463 | "version": "2.0.0", 1464 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 1465 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 1466 | "dev": true, 1467 | "requires": { 1468 | "domelementtype": "^2.3.0", 1469 | "domhandler": "^5.0.2", 1470 | "entities": "^4.2.0" 1471 | } 1472 | }, 1473 | "domelementtype": { 1474 | "version": "2.3.0", 1475 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 1476 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 1477 | "dev": true 1478 | }, 1479 | "domhandler": { 1480 | "version": "5.0.3", 1481 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 1482 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 1483 | "dev": true, 1484 | "requires": { 1485 | "domelementtype": "^2.3.0" 1486 | } 1487 | }, 1488 | "domutils": { 1489 | "version": "3.1.0", 1490 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 1491 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 1492 | "dev": true, 1493 | "requires": { 1494 | "dom-serializer": "^2.0.0", 1495 | "domelementtype": "^2.3.0", 1496 | "domhandler": "^5.0.3" 1497 | } 1498 | }, 1499 | "entities": { 1500 | "version": "4.5.0", 1501 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1502 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1503 | "dev": true 1504 | }, 1505 | "escape-string-regexp": { 1506 | "version": "4.0.0", 1507 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 1508 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 1509 | "dev": true 1510 | }, 1511 | "eslint": { 1512 | "version": "8.42.0", 1513 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", 1514 | "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", 1515 | "dev": true, 1516 | "requires": { 1517 | "@eslint-community/eslint-utils": "^4.2.0", 1518 | "@eslint-community/regexpp": "^4.4.0", 1519 | "@eslint/eslintrc": "^2.0.3", 1520 | "@eslint/js": "8.42.0", 1521 | "@humanwhocodes/config-array": "^0.11.10", 1522 | "@humanwhocodes/module-importer": "^1.0.1", 1523 | "@nodelib/fs.walk": "^1.2.8", 1524 | "ajv": "^6.10.0", 1525 | "chalk": "^4.0.0", 1526 | "cross-spawn": "^7.0.2", 1527 | "debug": "^4.3.2", 1528 | "doctrine": "^3.0.0", 1529 | "escape-string-regexp": "^4.0.0", 1530 | "eslint-scope": "^7.2.0", 1531 | "eslint-visitor-keys": "^3.4.1", 1532 | "espree": "^9.5.2", 1533 | "esquery": "^1.4.2", 1534 | "esutils": "^2.0.2", 1535 | "fast-deep-equal": "^3.1.3", 1536 | "file-entry-cache": "^6.0.1", 1537 | "find-up": "^5.0.0", 1538 | "glob-parent": "^6.0.2", 1539 | "globals": "^13.19.0", 1540 | "graphemer": "^1.4.0", 1541 | "ignore": "^5.2.0", 1542 | "import-fresh": "^3.0.0", 1543 | "imurmurhash": "^0.1.4", 1544 | "is-glob": "^4.0.0", 1545 | "is-path-inside": "^3.0.3", 1546 | "js-yaml": "^4.1.0", 1547 | "json-stable-stringify-without-jsonify": "^1.0.1", 1548 | "levn": "^0.4.1", 1549 | "lodash.merge": "^4.6.2", 1550 | "minimatch": "^3.1.2", 1551 | "natural-compare": "^1.4.0", 1552 | "optionator": "^0.9.1", 1553 | "strip-ansi": "^6.0.1", 1554 | "strip-json-comments": "^3.1.0", 1555 | "text-table": "^0.2.0" 1556 | } 1557 | }, 1558 | "eslint-plugin-html": { 1559 | "version": "7.1.0", 1560 | "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz", 1561 | "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==", 1562 | "dev": true, 1563 | "requires": { 1564 | "htmlparser2": "^8.0.1" 1565 | } 1566 | }, 1567 | "eslint-plugin-one-variable-per-var": { 1568 | "version": "0.0.3", 1569 | "resolved": "https://registry.npmjs.org/eslint-plugin-one-variable-per-var/-/eslint-plugin-one-variable-per-var-0.0.3.tgz", 1570 | "integrity": "sha512-gdqNqxyuGNjiJFIbOFMV5rs+cAR8TwQmldwsHINFNtgsm7/D5ogFmplibJL4UzQpINb5RwiXdOY0Ixed71PtIQ==", 1571 | "dev": true, 1572 | "requires": {} 1573 | }, 1574 | "eslint-plugin-optional-comma-spacing": { 1575 | "version": "0.0.4", 1576 | "resolved": "https://registry.npmjs.org/eslint-plugin-optional-comma-spacing/-/eslint-plugin-optional-comma-spacing-0.0.4.tgz", 1577 | "integrity": "sha512-VXTJW7LgMxjJngnCrPl/oLxw74aJvDoNLYYXjOedqq4qIuEb9JBqFJolAIHQFSqWPY0teQEkl8HKxfphu3eczg==", 1578 | "dev": true, 1579 | "requires": {} 1580 | }, 1581 | "eslint-plugin-require-trailing-comma": { 1582 | "version": "0.0.1", 1583 | "resolved": "https://registry.npmjs.org/eslint-plugin-require-trailing-comma/-/eslint-plugin-require-trailing-comma-0.0.1.tgz", 1584 | "integrity": "sha512-PFNp10AQZsFUbbt70esqj2WW6wd0mJbZaKZMSdWsvTqRPjOdzQd9Cn1xx7i8YCrAGYZCZpJIO8O7/22sQvuCTA==", 1585 | "dev": true, 1586 | "requires": {} 1587 | }, 1588 | "eslint-scope": { 1589 | "version": "7.2.0", 1590 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", 1591 | "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", 1592 | "dev": true, 1593 | "requires": { 1594 | "esrecurse": "^4.3.0", 1595 | "estraverse": "^5.2.0" 1596 | } 1597 | }, 1598 | "eslint-visitor-keys": { 1599 | "version": "3.4.1", 1600 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", 1601 | "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", 1602 | "dev": true 1603 | }, 1604 | "espree": { 1605 | "version": "9.5.2", 1606 | "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", 1607 | "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", 1608 | "dev": true, 1609 | "requires": { 1610 | "acorn": "^8.8.0", 1611 | "acorn-jsx": "^5.3.2", 1612 | "eslint-visitor-keys": "^3.4.1" 1613 | } 1614 | }, 1615 | "esquery": { 1616 | "version": "1.5.0", 1617 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", 1618 | "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", 1619 | "dev": true, 1620 | "requires": { 1621 | "estraverse": "^5.1.0" 1622 | } 1623 | }, 1624 | "esrecurse": { 1625 | "version": "4.3.0", 1626 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 1627 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 1628 | "dev": true, 1629 | "requires": { 1630 | "estraverse": "^5.2.0" 1631 | } 1632 | }, 1633 | "estraverse": { 1634 | "version": "5.3.0", 1635 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 1636 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 1637 | "dev": true 1638 | }, 1639 | "esutils": { 1640 | "version": "2.0.3", 1641 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1642 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1643 | "dev": true 1644 | }, 1645 | "fast-deep-equal": { 1646 | "version": "3.1.3", 1647 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 1648 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 1649 | "dev": true 1650 | }, 1651 | "fast-json-stable-stringify": { 1652 | "version": "2.1.0", 1653 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1654 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1655 | "dev": true 1656 | }, 1657 | "fast-levenshtein": { 1658 | "version": "2.0.6", 1659 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1660 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 1661 | "dev": true 1662 | }, 1663 | "fastq": { 1664 | "version": "1.15.0", 1665 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 1666 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 1667 | "dev": true, 1668 | "requires": { 1669 | "reusify": "^1.0.4" 1670 | } 1671 | }, 1672 | "file-entry-cache": { 1673 | "version": "6.0.1", 1674 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", 1675 | "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", 1676 | "dev": true, 1677 | "requires": { 1678 | "flat-cache": "^3.0.4" 1679 | } 1680 | }, 1681 | "find-up": { 1682 | "version": "5.0.0", 1683 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 1684 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 1685 | "dev": true, 1686 | "requires": { 1687 | "locate-path": "^6.0.0", 1688 | "path-exists": "^4.0.0" 1689 | } 1690 | }, 1691 | "flat-cache": { 1692 | "version": "3.0.4", 1693 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", 1694 | "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", 1695 | "dev": true, 1696 | "requires": { 1697 | "flatted": "^3.1.0", 1698 | "rimraf": "^3.0.2" 1699 | } 1700 | }, 1701 | "flatted": { 1702 | "version": "3.2.7", 1703 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", 1704 | "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", 1705 | "dev": true 1706 | }, 1707 | "fs.realpath": { 1708 | "version": "1.0.0", 1709 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1710 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1711 | "dev": true 1712 | }, 1713 | "glob": { 1714 | "version": "7.2.3", 1715 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1716 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1717 | "dev": true, 1718 | "requires": { 1719 | "fs.realpath": "^1.0.0", 1720 | "inflight": "^1.0.4", 1721 | "inherits": "2", 1722 | "minimatch": "^3.1.1", 1723 | "once": "^1.3.0", 1724 | "path-is-absolute": "^1.0.0" 1725 | } 1726 | }, 1727 | "glob-parent": { 1728 | "version": "6.0.2", 1729 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1730 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1731 | "dev": true, 1732 | "requires": { 1733 | "is-glob": "^4.0.3" 1734 | } 1735 | }, 1736 | "globals": { 1737 | "version": "13.20.0", 1738 | "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", 1739 | "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", 1740 | "dev": true, 1741 | "requires": { 1742 | "type-fest": "^0.20.2" 1743 | } 1744 | }, 1745 | "graphemer": { 1746 | "version": "1.4.0", 1747 | "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 1748 | "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 1749 | "dev": true 1750 | }, 1751 | "has-flag": { 1752 | "version": "4.0.0", 1753 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1754 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1755 | "dev": true 1756 | }, 1757 | "htmlparser2": { 1758 | "version": "8.0.2", 1759 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", 1760 | "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", 1761 | "dev": true, 1762 | "requires": { 1763 | "domelementtype": "^2.3.0", 1764 | "domhandler": "^5.0.3", 1765 | "domutils": "^3.0.1", 1766 | "entities": "^4.4.0" 1767 | } 1768 | }, 1769 | "ignore": { 1770 | "version": "5.2.4", 1771 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", 1772 | "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", 1773 | "dev": true 1774 | }, 1775 | "import-fresh": { 1776 | "version": "3.3.0", 1777 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 1778 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 1779 | "dev": true, 1780 | "requires": { 1781 | "parent-module": "^1.0.0", 1782 | "resolve-from": "^4.0.0" 1783 | } 1784 | }, 1785 | "imurmurhash": { 1786 | "version": "0.1.4", 1787 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1788 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1789 | "dev": true 1790 | }, 1791 | "inflight": { 1792 | "version": "1.0.6", 1793 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1794 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1795 | "dev": true, 1796 | "requires": { 1797 | "once": "^1.3.0", 1798 | "wrappy": "1" 1799 | } 1800 | }, 1801 | "inherits": { 1802 | "version": "2.0.4", 1803 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1804 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1805 | "dev": true 1806 | }, 1807 | "is-extglob": { 1808 | "version": "2.1.1", 1809 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1810 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1811 | "dev": true 1812 | }, 1813 | "is-glob": { 1814 | "version": "4.0.3", 1815 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1816 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1817 | "dev": true, 1818 | "requires": { 1819 | "is-extglob": "^2.1.1" 1820 | } 1821 | }, 1822 | "is-path-inside": { 1823 | "version": "3.0.3", 1824 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", 1825 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", 1826 | "dev": true 1827 | }, 1828 | "isexe": { 1829 | "version": "2.0.0", 1830 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1831 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1832 | "dev": true 1833 | }, 1834 | "js-yaml": { 1835 | "version": "4.1.0", 1836 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1837 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1838 | "dev": true, 1839 | "requires": { 1840 | "argparse": "^2.0.1" 1841 | } 1842 | }, 1843 | "json-schema-traverse": { 1844 | "version": "0.4.1", 1845 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1846 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1847 | "dev": true 1848 | }, 1849 | "json-stable-stringify-without-jsonify": { 1850 | "version": "1.0.1", 1851 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1852 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1853 | "dev": true 1854 | }, 1855 | "levn": { 1856 | "version": "0.4.1", 1857 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1858 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1859 | "dev": true, 1860 | "requires": { 1861 | "prelude-ls": "^1.2.1", 1862 | "type-check": "~0.4.0" 1863 | } 1864 | }, 1865 | "locate-path": { 1866 | "version": "6.0.0", 1867 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1868 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1869 | "dev": true, 1870 | "requires": { 1871 | "p-locate": "^5.0.0" 1872 | } 1873 | }, 1874 | "lodash.merge": { 1875 | "version": "4.6.2", 1876 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1877 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1878 | "dev": true 1879 | }, 1880 | "minimatch": { 1881 | "version": "3.1.2", 1882 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1883 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1884 | "dev": true, 1885 | "requires": { 1886 | "brace-expansion": "^1.1.7" 1887 | } 1888 | }, 1889 | "ms": { 1890 | "version": "2.1.2", 1891 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1892 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1893 | "dev": true 1894 | }, 1895 | "natural-compare": { 1896 | "version": "1.4.0", 1897 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1898 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 1899 | "dev": true 1900 | }, 1901 | "once": { 1902 | "version": "1.4.0", 1903 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1904 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1905 | "dev": true, 1906 | "requires": { 1907 | "wrappy": "1" 1908 | } 1909 | }, 1910 | "optionator": { 1911 | "version": "0.9.1", 1912 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", 1913 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", 1914 | "dev": true, 1915 | "requires": { 1916 | "deep-is": "^0.1.3", 1917 | "fast-levenshtein": "^2.0.6", 1918 | "levn": "^0.4.1", 1919 | "prelude-ls": "^1.2.1", 1920 | "type-check": "^0.4.0", 1921 | "word-wrap": "^1.2.3" 1922 | } 1923 | }, 1924 | "p-limit": { 1925 | "version": "3.1.0", 1926 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1927 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1928 | "dev": true, 1929 | "requires": { 1930 | "yocto-queue": "^0.1.0" 1931 | } 1932 | }, 1933 | "p-locate": { 1934 | "version": "5.0.0", 1935 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1936 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1937 | "dev": true, 1938 | "requires": { 1939 | "p-limit": "^3.0.2" 1940 | } 1941 | }, 1942 | "parent-module": { 1943 | "version": "1.0.1", 1944 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1945 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1946 | "dev": true, 1947 | "requires": { 1948 | "callsites": "^3.0.0" 1949 | } 1950 | }, 1951 | "path-exists": { 1952 | "version": "4.0.0", 1953 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1954 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1955 | "dev": true 1956 | }, 1957 | "path-is-absolute": { 1958 | "version": "1.0.1", 1959 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1960 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1961 | "dev": true 1962 | }, 1963 | "path-key": { 1964 | "version": "3.1.1", 1965 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1966 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1967 | "dev": true 1968 | }, 1969 | "prelude-ls": { 1970 | "version": "1.2.1", 1971 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1972 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1973 | "dev": true 1974 | }, 1975 | "punycode": { 1976 | "version": "2.3.0", 1977 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", 1978 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", 1979 | "dev": true 1980 | }, 1981 | "queue-microtask": { 1982 | "version": "1.2.3", 1983 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1984 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1985 | "dev": true 1986 | }, 1987 | "resolve-from": { 1988 | "version": "4.0.0", 1989 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1990 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1991 | "dev": true 1992 | }, 1993 | "reusify": { 1994 | "version": "1.0.4", 1995 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1996 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1997 | "dev": true 1998 | }, 1999 | "rimraf": { 2000 | "version": "3.0.2", 2001 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 2002 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 2003 | "dev": true, 2004 | "requires": { 2005 | "glob": "^7.1.3" 2006 | } 2007 | }, 2008 | "run-parallel": { 2009 | "version": "1.2.0", 2010 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 2011 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 2012 | "dev": true, 2013 | "requires": { 2014 | "queue-microtask": "^1.2.2" 2015 | } 2016 | }, 2017 | "shebang-command": { 2018 | "version": "2.0.0", 2019 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2020 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2021 | "dev": true, 2022 | "requires": { 2023 | "shebang-regex": "^3.0.0" 2024 | } 2025 | }, 2026 | "shebang-regex": { 2027 | "version": "3.0.0", 2028 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2029 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2030 | "dev": true 2031 | }, 2032 | "strip-ansi": { 2033 | "version": "6.0.1", 2034 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2035 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2036 | "dev": true, 2037 | "requires": { 2038 | "ansi-regex": "^5.0.1" 2039 | } 2040 | }, 2041 | "strip-json-comments": { 2042 | "version": "3.1.1", 2043 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 2044 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 2045 | "dev": true 2046 | }, 2047 | "supports-color": { 2048 | "version": "7.2.0", 2049 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2050 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2051 | "dev": true, 2052 | "requires": { 2053 | "has-flag": "^4.0.0" 2054 | } 2055 | }, 2056 | "text-table": { 2057 | "version": "0.2.0", 2058 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 2059 | "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", 2060 | "dev": true 2061 | }, 2062 | "type-check": { 2063 | "version": "0.4.0", 2064 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 2065 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 2066 | "dev": true, 2067 | "requires": { 2068 | "prelude-ls": "^1.2.1" 2069 | } 2070 | }, 2071 | "type-fest": { 2072 | "version": "0.20.2", 2073 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 2074 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 2075 | "dev": true 2076 | }, 2077 | "uri-js": { 2078 | "version": "4.4.1", 2079 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 2080 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 2081 | "dev": true, 2082 | "requires": { 2083 | "punycode": "^2.1.0" 2084 | } 2085 | }, 2086 | "which": { 2087 | "version": "2.0.2", 2088 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2089 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2090 | "dev": true, 2091 | "requires": { 2092 | "isexe": "^2.0.0" 2093 | } 2094 | }, 2095 | "word-wrap": { 2096 | "version": "1.2.3", 2097 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 2098 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 2099 | "dev": true 2100 | }, 2101 | "wrappy": { 2102 | "version": "1.0.2", 2103 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2104 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2105 | "dev": true 2106 | }, 2107 | "yocto-queue": { 2108 | "version": "0.1.0", 2109 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 2110 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 2111 | "dev": true 2112 | } 2113 | } 2114 | } 2115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgpu-avoid-redundant-state-setting", 3 | "version": "0.0.1", 4 | "description": "check for and/or avoid redundant state setting in WebGPU", 5 | "main": "webgpu-avoid-redundant-state-setting.js", 6 | "module": "webgpu-avoid-redundant-state-setting.js", 7 | "types": "types.d.ts", 8 | "type": "module", 9 | "directories": { 10 | "test": "test" 11 | }, 12 | "scripts": { 13 | "check": "npm run lint", 14 | "check-ci": "npm run pre-push", 15 | "lint": "eslint \"src/**/*.{js}\"", 16 | "pre-push": "npm run lint && npm run test", 17 | "test": "node puppeteer.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/greggman/webgpu-avoid-redundant-state-setting.git" 22 | }, 23 | "keywords": [ 24 | "webgpu" 25 | ], 26 | "author": "Gregg Tavares", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/greggman/webgpu-avoid-redundant-state-setting/issues" 30 | }, 31 | "homepage": "https://github.com/greggman/webgpu-avoid-redundant-state-setting#readme", 32 | "devDependencies": { 33 | "eslint": "^8.40.0", 34 | "eslint-plugin-html": "^7.1.0", 35 | "eslint-plugin-one-variable-per-var": "^0.0.3", 36 | "eslint-plugin-optional-comma-spacing": "^0.0.4", 37 | "eslint-plugin-require-trailing-comma": "^0.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/assert.js: -------------------------------------------------------------------------------- 1 | export const config = {}; 2 | 3 | export function setConfig(options) { 4 | Object.assign(config, options); 5 | } 6 | 7 | function formatMsg(msg) { 8 | return `${msg}${msg ? ': ' : ''}`; 9 | } 10 | 11 | export function assertTruthy(actual, msg = '') { 12 | if (!actual) { 13 | throw new Error(`${formatMsg(msg)}expected: truthy, actual: ${actual}`); 14 | } 15 | } 16 | 17 | export function assertFalsy(actual, msg = '') { 18 | if (actual) { 19 | throw new Error(`${formatMsg(msg)}expected: falsy, actual: ${actual}`); 20 | } 21 | } 22 | 23 | export function assertStringMatchesRegEx(actual, regex, msg = '') { 24 | if (!regex.test(actual)) { 25 | throw new Error(`${formatMsg(msg)}expected: ${regex}, actual: ${actual}`); 26 | } 27 | } 28 | 29 | export function assertLessThan(actual, expected, msg = '') { 30 | if (actual >= expected) { 31 | throw new Error(`${formatMsg(msg)}expected: ${actual} to be less than: ${expected}`); 32 | } 33 | } 34 | 35 | export function assertEqualApproximately(actual, expected, range = 0.0000001, msg = '') { 36 | if (actual.length) { 37 | assertArrayEqualApproximately(actual, expected, range, msg); 38 | } 39 | const diff = Math.abs(actual - expected); 40 | if (diff > range) { 41 | throw new Error(`${formatMsg(msg)}expected: ${actual} to be less ${range} different than: ${expected}`); 42 | } 43 | } 44 | 45 | export function assertInstanceOf(actual, expectedType, msg = '') { 46 | if (!(actual instanceof expectedType)) { 47 | throw new Error(`${formatMsg(msg)}expected: ${actual} to be of type: ${expectedType.constructor.name}`); 48 | } 49 | } 50 | 51 | export function assertIsArray(actual, msg = '') { 52 | if (!Array.isArray(actual)) { 53 | throw new Error(`${formatMsg(msg)}expected: ${actual} to be an Array`); 54 | } 55 | } 56 | 57 | export function assertDeepEqual(actual, expected, msg = '') { 58 | if (typeof actual === 'number' || 59 | typeof actual === 'string' || 60 | typeof actual === 'boolean' || 61 | Array.isArray(actual)) { 62 | assertEqual(actual, expected, msg); 63 | } else { 64 | const actualKeys = [...Object.keys(actual)]; 65 | const expectedKeys = [...Object.keys(expected)]; 66 | const onActual = actualKeys.filter(k => !(k in expected)); 67 | const onExpected = expectedKeys.filter(k => !(k in actual)); 68 | if (actualKeys.length !== expectedKeys.length) { 69 | throw new Error(`missing keys:\on actual but not expected: ${onActual}\n on expected but not actual ${onExpected}`); 70 | } 71 | for (const key of actualKeys) { 72 | assertDeepEqual(actual[key], expected[key], `${msg}: for ${key}`); 73 | } 74 | } 75 | } 76 | 77 | export function assertDeepEqualApproximately(actual, expected, range = 1e7, msg = '') { 78 | if (typeof actual === 'number') { 79 | assertEqualApproximately(actual, expected, range, msg); 80 | } else if (Array.isArray(actual) || actual.length) { 81 | assertArrayEqualApproximately(actual, expected, range, msg); 82 | } else if (typeof actual === 'string' || 83 | typeof actual === 'boolean') { 84 | assertEqual(actual, expected, msg); 85 | } else { 86 | const actualKeys = [...Object.keys(actual)]; 87 | const expectedKeys = [...Object.keys(expected)]; 88 | const onActual = actualKeys.filter(k => !(k in expected)); 89 | const onExpected = expectedKeys.filter(k => !(k in actual)); 90 | if (actualKeys.length !== expectedKeys.length) { 91 | throw new Error(`missing keys:\on actual but not expected: ${onActual}\n on expected but not actual ${onExpected}`); 92 | } 93 | for (const key of actualKeys) { 94 | assertDeepEqualApproximately(actual[key], expected[key], range, `${msg}: for ${key}`); 95 | } 96 | } 97 | } 98 | export function assertEqual(actual, expected, msg = '') { 99 | // I'm sure this is not sufficient 100 | if (actual.length && expected.length) { 101 | assertArrayEqual(actual, expected); 102 | } else if (actual !== expected) { 103 | throw new Error(`${formatMsg(msg)}expected: ${expected} to equal actual: ${actual}`); 104 | } 105 | } 106 | 107 | export function assertStrictEqual(actual, expected, msg = '') { 108 | if (actual !== expected) { 109 | throw new Error(`${formatMsg(msg)}expected: ${expected} to equal actual: ${actual}`); 110 | } 111 | } 112 | 113 | export function assertNotEqual(actual, expected, msg = '') { 114 | if (actual === expected) { 115 | throw new Error(`${formatMsg(msg)}expected: ${expected} to not equal actual: ${actual}`); 116 | } 117 | } 118 | 119 | export function assertStrictNotEqual(actual, expected, msg = '') { 120 | if (actual === expected) { 121 | throw new Error(`${formatMsg(msg)}expected: ${expected} to not equal actual: ${actual}`); 122 | } 123 | } 124 | 125 | export function assertArrayEqual(actual, expected, msg = '') { 126 | if (actual.length !== expected.length) { 127 | throw new Error(`${formatMsg(msg)}expected: array.length ${expected.length} to equal actual.length: ${actual.length}`); 128 | } 129 | const errors = []; 130 | for (let i = 0; i < actual.length; ++i) { 131 | if (actual[i] !== expected[i]) { 132 | errors.push(`${formatMsg(msg)}expected: expected[${i}] ${expected[i]} to equal actual[${i}]: ${actual[i]}`); 133 | if (errors.length === 10) { 134 | break; 135 | } 136 | } 137 | } 138 | if (errors.length > 0) { 139 | throw new Error(errors.join('\n')); 140 | } 141 | } 142 | 143 | export function assertArrayEqualApproximately(actual, expected, range = 0.0000001, msg = '') { 144 | if (actual.length !== expected.length) { 145 | throw new Error(`${formatMsg(msg)}expected: array.length ${expected.length} to equal actual.length: ${actual.length}`); 146 | } 147 | const errors = []; 148 | for (let i = 0; i < actual.length; ++i) { 149 | const diff = Math.abs(actual[i] - expected[i]); 150 | if (diff > range) { 151 | errors.push(`${formatMsg(msg)}expected: expected[${i}] ${expected[i]} to equal actual[${i}]: ${actual[i]} within ${range} but was different by ${diff}`); 152 | if (errors.length === 10) { 153 | break; 154 | } 155 | } 156 | } 157 | if (errors.length > 0) { 158 | throw new Error(errors.join('\n')); 159 | } 160 | } 161 | 162 | export function assertThrowsWith(func, expectations, msg = '') { 163 | let error = ''; 164 | if (config.throwOnError === false) { 165 | const origFn = console.error; 166 | const errors = []; 167 | console.error = function (...args) { 168 | errors.push(args.join(' ')); 169 | }; 170 | func(); 171 | console.error = origFn; 172 | if (errors.length) { 173 | error = errors.join('\n'); 174 | console.error(error); 175 | } 176 | } else { 177 | try { 178 | func(); 179 | } catch (e) { 180 | console.error(e); // eslint-disable-line 181 | error = e; 182 | } 183 | 184 | } 185 | 186 | if (config.noLint) { 187 | return; 188 | } 189 | 190 | assertStringMatchesREs(error.toString().replace(/\n/g, ' '), expectations, msg); 191 | } 192 | 193 | // check if it throws it throws with x 194 | export function assertIfThrowsItThrowsWith(func, expectations, msg = '') { 195 | let error = ''; 196 | let threw = false; 197 | if (config.throwOnError === false) { 198 | const origFn = console.error; 199 | const errors = []; 200 | console.error = function (...args) { 201 | errors.push(args.join(' ')); 202 | }; 203 | func(); 204 | console.error = origFn; 205 | if (errors.length) { 206 | error = errors.join('\n'); 207 | console.error(error); 208 | } 209 | } else { 210 | try { 211 | func(); 212 | } catch (e) { 213 | console.error(e); // eslint-disable-line 214 | error = e; 215 | threw = true; 216 | } 217 | 218 | } 219 | 220 | if (config.noLint) { 221 | return; 222 | } 223 | 224 | if (!threw) { 225 | return; 226 | } 227 | 228 | assertStringMatchesREs(error.toString().replace(/\n/g, ' '), expectations, msg); 229 | } 230 | 231 | function assertStringMatchesREs(actual, expectations, msg) { 232 | for (const expectation of expectations) { 233 | if (expectation instanceof RegExp) { 234 | if (!expectation.test(actual)) { 235 | throw new Error(`${formatMsg(msg)}expected: ${expectation}, actual: ${actual}`); 236 | } 237 | } 238 | } 239 | } 240 | 241 | export function assertWarnsWith(func, expectations, msg = '') { 242 | const warnings = []; 243 | const origWarnFn = console.warn; 244 | console.warn = function (...args) { 245 | origWarnFn.call(this, ...args); 246 | warnings.push(args.join(' ')); 247 | }; 248 | 249 | let error; 250 | try { 251 | func(); 252 | } catch (e) { 253 | error = e; 254 | } 255 | 256 | console.warn = origWarnFn; 257 | 258 | if (error) { 259 | throw error; 260 | } 261 | 262 | if (config.noLint) { 263 | return; 264 | } 265 | 266 | assertStringMatchesREs(warnings.join(' '), expectations, msg); 267 | } 268 | 269 | export default { 270 | false: assertFalsy, 271 | equal: assertEqual, 272 | matchesRegEx: assertStringMatchesRegEx, 273 | notEqual: assertNotEqual, 274 | throwsWith: assertThrowsWith, 275 | true: assertTruthy, 276 | }; -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | webgpu-avoid-redundant-state-setting tests 6 | 7 | 8 | 9 | 10 |
11 | 23 | 24 | 25 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global mocha */ 2 | import './tests/webgpu-avoid-redundant-state-setting-tests.js'; 3 | 4 | const settings = Object.fromEntries(new URLSearchParams(window.location.search).entries()); 5 | if (settings.reporter) { 6 | mocha.reporter(settings.reporter); 7 | } 8 | if (settings.grep) { 9 | mocha.grep(new RegExp(settings.grep, 'i'), false); 10 | } 11 | 12 | mocha.run((failures) => { 13 | window.testsPromiseInfo.resolve(failures); 14 | }); 15 | -------------------------------------------------------------------------------- /test/mocha-support.js: -------------------------------------------------------------------------------- 1 | /* global globalThis */ 2 | export const describe = globalThis.describe; 3 | export const it = globalThis.it; 4 | export const before = globalThis.before; 5 | export const after = globalThis.after; 6 | export const beforeEach = globalThis.beforeEach; 7 | export const afterEach = globalThis.afterEach; 8 | 9 | -------------------------------------------------------------------------------- /test/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | :root { 4 | --mocha-color: #000; 5 | --mocha-bg-color: #fff; 6 | --mocha-pass-icon-color: #00d6b2; 7 | --mocha-pass-color: #fff; 8 | --mocha-pass-shadow-color: rgba(0,0,0,.2); 9 | --mocha-pass-mediump-color: #c09853; 10 | --mocha-pass-slow-color: #b94a48; 11 | --mocha-test-pending-color: #0b97c4; 12 | --mocha-test-pending-icon-color: #0b97c4; 13 | --mocha-test-fail-color: #c00; 14 | --mocha-test-fail-icon-color: #c00; 15 | --mocha-test-fail-pre-color: #000; 16 | --mocha-test-fail-pre-error-color: #c00; 17 | --mocha-test-html-error-color: #000; 18 | --mocha-box-shadow-color: #eee; 19 | --mocha-box-bottom-color: #ddd; 20 | --mocha-test-replay-color: #888; 21 | --mocha-test-replay-bg-color: #eee; 22 | --mocha-stats-color: #888; 23 | --mocha-stats-em-color: #000; 24 | --mocha-stats-hover-color: #eee; 25 | --mocha-error-color: #c00; 26 | 27 | --mocha-code-comment: #ddd; 28 | --mocha-code-init: #2f6fad; 29 | --mocha-code-string: #5890ad; 30 | --mocha-code-keyword: #8a6343; 31 | --mocha-code-number: #2f6fad; 32 | } 33 | 34 | @media (prefers-color-scheme: dark) { 35 | :root { 36 | --mocha-color: #fff; 37 | --mocha-bg-color: #222; 38 | --mocha-pass-icon-color: #00d6b2; 39 | --mocha-pass-color: #222; 40 | --mocha-pass-shadow-color: rgba(255,255,255,.2); 41 | --mocha-pass-mediump-color: #f1be67; 42 | --mocha-pass-slow-color: #f49896; 43 | --mocha-test-pending-color: #0b97c4; 44 | --mocha-test-pending-icon-color: #0b97c4; 45 | --mocha-test-fail-color: #f44; 46 | --mocha-test-fail-icon-color: #f44; 47 | --mocha-test-fail-pre-color: #fff; 48 | --mocha-test-fail-pre-error-color: #f44; 49 | --mocha-test-html-error-color: #fff; 50 | --mocha-box-shadow-color: #444; 51 | --mocha-box-bottom-color: #555; 52 | --mocha-test-replay-color: #888; 53 | --mocha-test-replay-bg-color: #444; 54 | --mocha-stats-color: #aaa; 55 | --mocha-stats-em-color: #fff; 56 | --mocha-stats-hover-color: #444; 57 | --mocha-error-color: #f44; 58 | 59 | --mocha-code-comment: #ddd; 60 | --mocha-code-init: #9cc7f1; 61 | --mocha-code-string: #80d4ff; 62 | --mocha-code-keyword: #e3a470; 63 | --mocha-code-number: #4ca7ff; 64 | } 65 | } 66 | 67 | body { 68 | margin:0; 69 | background-color: var(--mocha-bg-color); 70 | color: var(--mocha-color); 71 | } 72 | 73 | #mocha { 74 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 75 | margin: 60px 50px; 76 | } 77 | 78 | #mocha ul, 79 | #mocha li { 80 | margin: 0; 81 | padding: 0; 82 | } 83 | 84 | #mocha ul { 85 | list-style: none; 86 | } 87 | 88 | #mocha h1, 89 | #mocha h2 { 90 | margin: 0; 91 | } 92 | 93 | #mocha h1 { 94 | margin-top: 15px; 95 | font-size: 1em; 96 | font-weight: 200; 97 | } 98 | 99 | #mocha h1 a { 100 | text-decoration: none; 101 | color: inherit; 102 | } 103 | 104 | #mocha h1 a:hover { 105 | text-decoration: underline; 106 | } 107 | 108 | #mocha .suite .suite h1 { 109 | margin-top: 0; 110 | font-size: .8em; 111 | } 112 | 113 | #mocha .hidden { 114 | display: none; 115 | } 116 | 117 | #mocha h2 { 118 | font-size: 12px; 119 | font-weight: normal; 120 | cursor: pointer; 121 | } 122 | 123 | #mocha .suite { 124 | margin-left: 15px; 125 | } 126 | 127 | #mocha .test { 128 | margin-left: 15px; 129 | overflow: hidden; 130 | } 131 | 132 | #mocha .test.pending:hover h2::after { 133 | content: '(pending)'; 134 | font-family: arial, sans-serif; 135 | } 136 | 137 | #mocha .test.pass.medium .duration { 138 | background: var(--mocha-pass-mediump-color); 139 | } 140 | 141 | #mocha .test.pass.slow .duration { 142 | background: var(--mocha-pass-slow-color); 143 | } 144 | 145 | #mocha .test.pass::before { 146 | content: '✓'; 147 | font-size: 12px; 148 | display: block; 149 | float: left; 150 | margin-right: 5px; 151 | color: var(--mocha-pass-icon-color); 152 | } 153 | 154 | #mocha .test.pass .duration { 155 | font-size: 9px; 156 | margin-left: 5px; 157 | padding: 2px 5px; 158 | color: var(--mocha-pass-color); 159 | -webkit-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color); 160 | -moz-box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color); 161 | box-shadow: inset 0 1px 1px var(--mocha-pass-shadow-color); 162 | -webkit-border-radius: 5px; 163 | -moz-border-radius: 5px; 164 | -ms-border-radius: 5px; 165 | -o-border-radius: 5px; 166 | border-radius: 5px; 167 | } 168 | 169 | #mocha .test.pass.fast .duration { 170 | display: none; 171 | } 172 | 173 | #mocha .test.pending { 174 | color: var(--mocha-test-pending-color); 175 | } 176 | 177 | #mocha .test.pending::before { 178 | content: '◦'; 179 | color: var(--mocha-test-pending-icon-color); 180 | } 181 | 182 | #mocha .test.fail { 183 | color: var(--mocha-test-fail-color); 184 | } 185 | 186 | #mocha .test.fail pre { 187 | color: var(--mocha-test-fail-pre-color); 188 | } 189 | 190 | #mocha .test.fail::before { 191 | content: '✖'; 192 | font-size: 12px; 193 | display: block; 194 | float: left; 195 | margin-right: 5px; 196 | color: var(--mocha-pass-icon-color); 197 | } 198 | 199 | #mocha .test pre.error { 200 | color: var(--mocha-test-fail-pre-error-color); 201 | max-height: 300px; 202 | overflow: auto; 203 | } 204 | 205 | #mocha .test .html-error { 206 | overflow: auto; 207 | color: var(--mocha-test-html-error-color); 208 | display: block; 209 | float: left; 210 | clear: left; 211 | font: 12px/1.5 monaco, monospace; 212 | margin: 5px; 213 | padding: 15px; 214 | border: 1px solid var(--mocha-box-shadow-color); 215 | max-width: 85%; /*(1)*/ 216 | max-width: -webkit-calc(100% - 42px); 217 | max-width: -moz-calc(100% - 42px); 218 | max-width: calc(100% - 42px); /*(2)*/ 219 | max-height: 300px; 220 | word-wrap: break-word; 221 | border-bottom-color: var(--mocha-box-bottom-color); 222 | -webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color); 223 | -moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color); 224 | box-shadow: 0 1px 3px var(--mocha-box-shadow-color); 225 | -webkit-border-radius: 3px; 226 | -moz-border-radius: 3px; 227 | border-radius: 3px; 228 | } 229 | 230 | #mocha .test .html-error pre.error { 231 | border: none; 232 | -webkit-border-radius: 0; 233 | -moz-border-radius: 0; 234 | border-radius: 0; 235 | -webkit-box-shadow: 0; 236 | -moz-box-shadow: 0; 237 | box-shadow: 0; 238 | padding: 0; 239 | margin: 0; 240 | margin-top: 18px; 241 | max-height: none; 242 | } 243 | 244 | /** 245 | * (1): approximate for browsers not supporting calc 246 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 247 | * ^^ seriously 248 | */ 249 | #mocha .test pre { 250 | display: block; 251 | float: left; 252 | clear: left; 253 | font: 12px/1.5 monaco, monospace; 254 | margin: 5px; 255 | padding: 15px; 256 | border: 1px solid var(--mocha-box-shadow-color); 257 | max-width: 85%; /*(1)*/ 258 | max-width: -webkit-calc(100% - 42px); 259 | max-width: -moz-calc(100% - 42px); 260 | max-width: calc(100% - 42px); /*(2)*/ 261 | word-wrap: break-word; 262 | border-bottom-color: var(--mocha-box-bottom-color); 263 | -webkit-box-shadow: 0 1px 3px var(--mocha-box-shadow-color); 264 | -moz-box-shadow: 0 1px 3px var(--mocha-box-shadow-color); 265 | box-shadow: 0 1px 3px var(--mocha-box-shadow-color); 266 | -webkit-border-radius: 3px; 267 | -moz-border-radius: 3px; 268 | border-radius: 3px; 269 | } 270 | 271 | #mocha .test h2 { 272 | position: relative; 273 | } 274 | 275 | #mocha .test a.replay { 276 | position: absolute; 277 | top: 3px; 278 | right: 0; 279 | text-decoration: none; 280 | vertical-align: middle; 281 | display: block; 282 | width: 15px; 283 | height: 15px; 284 | line-height: 15px; 285 | text-align: center; 286 | background: var(--mocha-test-replay-bg-color); 287 | font-size: 15px; 288 | -webkit-border-radius: 15px; 289 | -moz-border-radius: 15px; 290 | border-radius: 15px; 291 | -webkit-transition:opacity 200ms; 292 | -moz-transition:opacity 200ms; 293 | -o-transition:opacity 200ms; 294 | transition: opacity 200ms; 295 | opacity: 0.3; 296 | color: var(--mocha-test-replay-color); 297 | } 298 | 299 | #mocha .test:hover a.replay { 300 | opacity: 1; 301 | } 302 | 303 | #mocha-report.pass .test.fail { 304 | display: none; 305 | } 306 | 307 | #mocha-report.fail .test.pass { 308 | display: none; 309 | } 310 | 311 | #mocha-report.pending .test.pass, 312 | #mocha-report.pending .test.fail { 313 | display: none; 314 | } 315 | #mocha-report.pending .test.pass.pending { 316 | display: block; 317 | } 318 | 319 | #mocha-error { 320 | color: var(--mocha-error-color); 321 | font-size: 1.5em; 322 | font-weight: 100; 323 | letter-spacing: 1px; 324 | } 325 | 326 | #mocha-stats { 327 | position: fixed; 328 | top: 15px; 329 | right: 10px; 330 | font-size: 12px; 331 | margin: 0; 332 | color: var(--mocha-stats-color); 333 | z-index: 1; 334 | } 335 | 336 | #mocha-stats .progress { 337 | float: right; 338 | padding-top: 0; 339 | 340 | /** 341 | * Set safe initial values, so mochas .progress does not inherit these 342 | * properties from Bootstrap .progress (which causes .progress height to 343 | * equal line height set in Bootstrap). 344 | */ 345 | height: auto; 346 | -webkit-box-shadow: none; 347 | -moz-box-shadow: none; 348 | box-shadow: none; 349 | background-color: initial; 350 | } 351 | 352 | #mocha-stats em { 353 | color: var(--mocha-stats-em-color); 354 | } 355 | 356 | #mocha-stats a { 357 | text-decoration: none; 358 | color: inherit; 359 | } 360 | 361 | #mocha-stats a:hover { 362 | border-bottom: 1px solid var(--mocha-stats-hover-color); 363 | } 364 | 365 | #mocha-stats li { 366 | display: inline-block; 367 | margin: 0 5px; 368 | list-style: none; 369 | padding-top: 11px; 370 | } 371 | 372 | #mocha-stats canvas { 373 | width: 40px; 374 | height: 40px; 375 | } 376 | 377 | #mocha code .comment { color: var(--mocha-code-comment); } 378 | #mocha code .init { color: var(--mocha-code-init); } 379 | #mocha code .string { color: var(--mocha-code-string); } 380 | #mocha code .keyword { color: var(--mocha-code-keyword); } 381 | #mocha code .number { color: var(--mocha-code-number); } 382 | 383 | @media screen and (max-device-width: 480px) { 384 | #mocha { 385 | margin: 60px 0px; 386 | } 387 | 388 | #mocha #stats { 389 | position: absolute; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /test/puppeteer.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import puppeteer from 'puppeteer'; 4 | import path from 'path'; 5 | import express from 'express'; 6 | import url from 'url'; 7 | const app = express(); 8 | const port = 3000; 9 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); // eslint-disable-line 10 | 11 | app.use(express.static(path.dirname(__dirname))); 12 | const server = app.listen(port, () => { 13 | console.log(`Example app listening on port ${port}!`); 14 | test(port); 15 | }); 16 | 17 | function makePromiseInfo() { 18 | const info = {}; 19 | const promise = new Promise((resolve, reject) => { 20 | Object.assign(info, {resolve, reject}); 21 | }); 22 | info.promise = promise; 23 | return info; 24 | } 25 | 26 | 27 | async function test(port) { 28 | const browser = await puppeteer.launch(); 29 | const page = await browser.newPage(); 30 | 31 | page.on('console', async e => { 32 | const args = await Promise.all(e.args().map(a => a.jsonValue())); 33 | console.log(...args); 34 | }); 35 | 36 | let totalFailures = 0; 37 | let waitingPromiseInfo; 38 | 39 | // Get the "viewport" of the page, as reported by the page. 40 | page.on('domcontentloaded', async() => { 41 | const failures = await page.evaluate(() => { 42 | return window.testsPromiseInfo.promise; 43 | }); 44 | 45 | totalFailures += failures; 46 | 47 | waitingPromiseInfo.resolve(); 48 | }); 49 | 50 | const urls = [ 51 | `http://localhost:${port}/test/index.html?reporter=spec`, 52 | ]; 53 | 54 | for (const url of urls) { 55 | waitingPromiseInfo = makePromiseInfo(); 56 | await page.goto(url); 57 | await waitingPromiseInfo.promise; 58 | } 59 | 60 | await browser.close(); 61 | server.close(); 62 | 63 | process.exit(totalFailures ? 1 : 0); // eslint-disable-line 64 | } 65 | -------------------------------------------------------------------------------- /test/tests/webgpu-avoid-redundant-state-setting-tests.js: -------------------------------------------------------------------------------- 1 | import {getAndResetRedundantCallInfo} from '../../webgpu-avoid-redundant-state-setting.js'; 2 | 3 | import { 4 | assertEqual, 5 | assertTruthy, 6 | } from '../assert.js'; 7 | 8 | import {describe, it} from '../mocha-support.js'; 9 | 10 | describe('webgpu-avoid-redundant-state-setting', () => { 11 | let device; 12 | let encoder; 13 | 14 | before(async() => { 15 | const adapter = await navigator.gpu?.requestAdapter(); 16 | device = await adapter?.requestDevice(); 17 | device?.addEventListener('uncapturederror', (event) => { 18 | assertTruthy(false, event.error.message); 19 | }); 20 | encoder = device?.createCommandEncoder(); 21 | }); 22 | 23 | after(() => { 24 | encoder?.finish(); 25 | device?.destroy(); 26 | }); 27 | 28 | function testSetPipeline(pass, pipeline0, pipeline1) { 29 | const tests = [ 30 | { args: [pipeline0], same: false }, 31 | { args: [pipeline0], same: true }, 32 | { args: [pipeline1], same: false }, 33 | ]; 34 | for (const {args, same} of tests) { 35 | pass.setPipeline(...args); 36 | const info = getAndResetRedundantCallInfo(); 37 | assertEqual(info.setPipeline, same ? 1 : 0, `setPipeline: ${args.join(', ')}`); 38 | } 39 | } 40 | 41 | function testSetBindGroup(pass, bindGroup0, bindGroup1, dynamicBindGroup0, dynamicBindGroup1) { 42 | const tests = [ 43 | { args: [0, bindGroup0], same: false }, 44 | { args: [0, bindGroup0], same: true }, 45 | { args: [0, bindGroup1], same: false }, 46 | { args: [0, dynamicBindGroup0, [0, 0]], same: false, label: '0' }, 47 | { args: [0, dynamicBindGroup0, [0, 0]], same: true, label: '1' }, 48 | { args: [0, dynamicBindGroup0, [256, 0]], same: false}, 49 | { args: [0, dynamicBindGroup0, [0, 256]], same: false}, 50 | { args: [0, dynamicBindGroup0, new Uint32Array([0, 0]), 0, 2], same: false}, 51 | { args: [0, dynamicBindGroup0, new Uint32Array([256, 0]), 0, 2], same: false}, 52 | { args: [0, dynamicBindGroup0, new Uint32Array([256, 0]), 0, 2], same: true}, 53 | { args: [0, dynamicBindGroup0, new Uint32Array([128, 256, 0]), 1, 2], same: true}, 54 | { args: [0, dynamicBindGroup0, new Uint32Array([256, 0]), 0, 2], same: true}, 55 | { args: [0, dynamicBindGroup0, [256, 0]], same: true, label: 'from typedarray to native array' }, 56 | { args: [0, dynamicBindGroup0, new Uint32Array([123, 345, 256, 0]), 2, 2], same: true, label: 'from native array to typedarray array' }, 57 | ]; 58 | for (const {args, same, label} of tests) { 59 | pass.setBindGroup(...args); 60 | const info = getAndResetRedundantCallInfo(); 61 | assertEqual(info.setBindGroup, same ? 1 : 0, `setBindGroup: ${args.join(', ')} label: ${label}`); 62 | } 63 | } 64 | 65 | describe('avoids redundant state setting', () => { 66 | 67 | describe('in compute pass', () => { 68 | 69 | let pass; 70 | let pipeline0; 71 | let pipeline1; 72 | let bindGroup0; 73 | let bindGroup1; 74 | let dynamicBindGroup0; 75 | let dynamicBindGroup1; 76 | 77 | before(() => { 78 | const shaderSrc = ` 79 | @group(0) @binding(0) var data0: array; 80 | @group(0) @binding(2) var data1: array; 81 | 82 | @compute @workgroup_size(1) fn computeSomething( 83 | @builtin(global_invocation_id) id: vec3 84 | ) { 85 | _ = &data0; 86 | _ = &data1; 87 | } 88 | `; 89 | 90 | const module = device.createShaderModule({code: shaderSrc}); 91 | const pipelineDesc = { 92 | layout: 'auto', 93 | compute: { 94 | module, 95 | entryPoint: 'computeSomething', 96 | }, 97 | }; 98 | pipeline0 = device.createComputePipeline(pipelineDesc); 99 | pipeline1 = device.createComputePipeline(pipelineDesc); 100 | const storageBuffer0 = device.createBuffer({ 101 | size: 1024, 102 | usage: GPUBufferUsage.STORAGE, 103 | }); 104 | const storageBuffer1 = device.createBuffer({ 105 | size: 1024, 106 | usage: GPUBufferUsage.STORAGE, 107 | }); 108 | bindGroup0 = device.createBindGroup({ 109 | label: 'group0', 110 | layout: pipeline0.getBindGroupLayout(0), 111 | entries: [ 112 | { binding: 0, resource: { buffer: storageBuffer0 } }, 113 | { binding: 2, resource: { buffer: storageBuffer1 } }, 114 | ], 115 | }); 116 | bindGroup1 = device.createBindGroup({ 117 | label: 'group1', 118 | layout: pipeline0.getBindGroupLayout(0), 119 | entries: [ 120 | { binding: 0, resource: { buffer: storageBuffer0 } }, 121 | { binding: 2, resource: { buffer: storageBuffer1 } }, 122 | ], 123 | }); 124 | 125 | const bindGroupLayout = device.createBindGroupLayout({ 126 | entries: [ 127 | { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage", hasDynamicOffset: true } }, 128 | { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage", hasDynamicOffset: true } }, 129 | ], 130 | }); 131 | dynamicBindGroup0 = device.createBindGroup({ 132 | label: 'dynamicGroup0', 133 | layout: bindGroupLayout, 134 | entries: [ 135 | { binding: 0, resource: { buffer: storageBuffer0, size: 512 } }, 136 | { binding: 2, resource: { buffer: storageBuffer1, size: 512 } }, 137 | ], 138 | }); 139 | dynamicBindGroup0 = device.createBindGroup({ 140 | label: 'dynamicGroup1', 141 | layout: bindGroupLayout, 142 | entries: [ 143 | { binding: 0, resource: { buffer: storageBuffer0, size: 512 } }, 144 | { binding: 2, resource: { buffer: storageBuffer1, size: 512 } }, 145 | ], 146 | }); 147 | 148 | pass = encoder.beginComputePass(); 149 | }); 150 | 151 | after(() => { 152 | pass.end(); 153 | }); 154 | 155 | beforeEach(() => { 156 | getAndResetRedundantCallInfo(); 157 | }); 158 | 159 | it('setPipeline', () => { 160 | testSetPipeline(pass, pipeline0, pipeline1); 161 | }); 162 | 163 | it('setBindGroup', () => { 164 | testSetBindGroup(pass, bindGroup0, bindGroup1, dynamicBindGroup0, dynamicBindGroup1); 165 | }); 166 | 167 | }); 168 | 169 | describe('in render pass', () => { 170 | 171 | let pass; 172 | let renderPassDescriptor; 173 | let pipeline0; 174 | let pipeline1; 175 | let bindGroup0; 176 | let bindGroup1; 177 | let dynamicBindGroup0; 178 | let dynamicBindGroup1; 179 | let vertexBuffer0; 180 | let vertexBuffer1; 181 | let indexBuffer0; 182 | let indexBuffer1; 183 | 184 | before(() => { 185 | const shaderSrc = ` 186 | struct VSUniforms0 { 187 | worldViewProjection: mat4x4, 188 | }; 189 | struct VSUniforms1 { 190 | worldInverseTranspose: mat4x4, 191 | }; 192 | @group(0) @binding(0) var vsUniforms0: VSUniforms0; 193 | @group(0) @binding(2) var vsUniforms1: VSUniforms1; 194 | 195 | struct MyVSInput { 196 | @location(0) position: vec4, 197 | @location(1) normal: vec3, 198 | @location(2) texcoord: vec2, 199 | }; 200 | 201 | struct MyVSOutput { 202 | @builtin(position) position: vec4, 203 | @location(0) normal: vec3, 204 | @location(1) texcoord: vec2, 205 | }; 206 | 207 | @vertex 208 | fn myVSMain(v: MyVSInput) -> MyVSOutput { 209 | var vsOut: MyVSOutput; 210 | vsOut.position = vsUniforms0.worldViewProjection * v.position; 211 | vsOut.normal = (vsUniforms1.worldInverseTranspose * vec4(v.normal, 0.0)).xyz; 212 | vsOut.texcoord = v.texcoord; 213 | return vsOut; 214 | } 215 | 216 | @fragment 217 | fn myFSMain(v: MyVSOutput) -> @location(0) vec4 { 218 | return vec4f(0); 219 | } 220 | `; 221 | 222 | const shaderModule = device.createShaderModule({code: shaderSrc}); 223 | const pipelineDesc = { 224 | layout: 'auto', 225 | vertex: { 226 | module: shaderModule, 227 | entryPoint: 'myVSMain', 228 | buffers: [ 229 | { arrayStride: 3 * 4, attributes: [ {shaderLocation: 0, offset: 0, format: 'float32x3'}, ], }, 230 | { arrayStride: 3 * 4, attributes: [ {shaderLocation: 1, offset: 0, format: 'float32x3'}, ], }, 231 | { arrayStride: 2 * 4, attributes: [ {shaderLocation: 2, offset: 0, format: 'float32x2',}, ], }, 232 | ], 233 | }, 234 | fragment: { 235 | module: shaderModule, 236 | entryPoint: 'myFSMain', 237 | targets: [ {format: 'rgba8unorm'}, ], 238 | }, 239 | }; 240 | pipeline0 = device.createRenderPipeline(pipelineDesc); 241 | pipeline1 = device.createRenderPipeline(pipelineDesc); 242 | 243 | const vsUniformBuffer0 = device.createBuffer({ 244 | size: 1024, 245 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, 246 | }); 247 | const vsUniformBuffer1 = device.createBuffer({ 248 | size: 1024, 249 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, 250 | }); 251 | 252 | bindGroup0 = device.createBindGroup({ 253 | label: 'group0', 254 | layout: pipeline0.getBindGroupLayout(0), 255 | entries: [ 256 | { binding: 0, resource: { buffer: vsUniformBuffer0 } }, 257 | { binding: 2, resource: { buffer: vsUniformBuffer1 } }, 258 | ], 259 | }); 260 | bindGroup1 = device.createBindGroup({ 261 | label: 'group1', 262 | layout: pipeline0.getBindGroupLayout(0), 263 | entries: [ 264 | { binding: 0, resource: { buffer: vsUniformBuffer0 } }, 265 | { binding: 2, resource: { buffer: vsUniformBuffer1 } }, 266 | ], 267 | }); 268 | 269 | const bindGroupLayout = device.createBindGroupLayout({ 270 | entries: [ 271 | { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform", hasDynamicOffset: true } }, 272 | { binding: 2, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform", hasDynamicOffset: true } }, 273 | ], 274 | }); 275 | dynamicBindGroup0 = device.createBindGroup({ 276 | label: 'dynamicGroup0', 277 | layout: bindGroupLayout, 278 | entries: [ 279 | { binding: 0, resource: { buffer: vsUniformBuffer0, size: 512 } }, 280 | { binding: 2, resource: { buffer: vsUniformBuffer1, size: 512 } }, 281 | ], 282 | }); 283 | dynamicBindGroup0 = device.createBindGroup({ 284 | label: 'dynamicGroup1', 285 | layout: bindGroupLayout, 286 | entries: [ 287 | { binding: 0, resource: { buffer: vsUniformBuffer0, size: 512 } }, 288 | { binding: 2, resource: { buffer: vsUniformBuffer1, size: 512 } }, 289 | ], 290 | }); 291 | 292 | vertexBuffer0 = device.createBuffer({ size: 128, usage: GPUBufferUsage.VERTEX }); 293 | vertexBuffer1 = device.createBuffer({ size: 128, usage: GPUBufferUsage.VERTEX }); 294 | indexBuffer0 = device.createBuffer({ size: 128, usage: GPUBufferUsage.INDEX }); 295 | indexBuffer1 = device.createBuffer({ size: 128, usage: GPUBufferUsage.INDEX }); 296 | 297 | const tex = device.createTexture({ 298 | size: [2, 2, 1], 299 | format: 'rgba8unorm', 300 | usage: GPUTextureUsage.RENDER_ATTACHMENT 301 | }); 302 | renderPassDescriptor = { 303 | colorAttachments: [ 304 | { 305 | view: tex.createView(), 306 | clearValue: [0, 0, 0, 0], 307 | loadOp: 'clear', 308 | storeOp: 'store', 309 | }, 310 | ], 311 | }; 312 | 313 | pass = encoder.beginRenderPass(renderPassDescriptor); 314 | }); 315 | 316 | after(() => { 317 | pass.end(); 318 | }); 319 | 320 | beforeEach(() => { 321 | getAndResetRedundantCallInfo(); 322 | }); 323 | 324 | it('setPipeline', () => { 325 | testSetPipeline(pass, pipeline0, pipeline1); 326 | }); 327 | 328 | it('setBindGroup', () => { 329 | testSetBindGroup(pass, bindGroup0, bindGroup1, dynamicBindGroup0, dynamicBindGroup1); 330 | }); 331 | 332 | it('setVertexBuffer', () => { 333 | const tests = [ 334 | { args: [0, vertexBuffer0], same: false }, 335 | { args: [0, vertexBuffer0], same: true }, 336 | { args: [0, vertexBuffer0, 0], same: true }, 337 | { args: [0, vertexBuffer0, 0, vertexBuffer0.size], same: true }, 338 | { args: [0, vertexBuffer0, 0, vertexBuffer0.size - 4], same: false }, 339 | { args: [0, vertexBuffer0, 4, vertexBuffer0.size - 4], same: false }, 340 | { args: [0, vertexBuffer1, 4, vertexBuffer0.size - 4], same: false }, 341 | ]; 342 | for (const {args, same} of tests) { 343 | pass.setVertexBuffer(...args); 344 | const info = getAndResetRedundantCallInfo(); 345 | assertEqual(info.setVertexBuffer, same ? 1 : 0, `setVertexBuffer: ${args.join(', ')}`); 346 | } 347 | }); 348 | 349 | it('setIndexBuffer', () => { 350 | const tests = [ 351 | { args: [indexBuffer0, 'uint16'], same: false }, 352 | { args: [indexBuffer0, 'uint16'], same: true }, 353 | { args: [indexBuffer0, 'uint16', 0], same: true }, 354 | { args: [indexBuffer0, 'uint16', 0, indexBuffer0.size], same: true }, 355 | { args: [indexBuffer0, 'uint16', 0, indexBuffer0.size - 4], same: false }, 356 | { args: [indexBuffer0, 'uint16', 4, indexBuffer0.size - 4], same: false }, 357 | { args: [indexBuffer1, 'uint16', 4, indexBuffer0.size - 4], same: false }, 358 | { args: [indexBuffer1, 'uint32', 4, indexBuffer0.size - 4], same: false }, 359 | ]; 360 | for (const {args, same} of tests) { 361 | pass.setIndexBuffer(...args); 362 | const info = getAndResetRedundantCallInfo(); 363 | assertEqual(info.setIndexBuffer, same ? 1 : 0, `setIndexBuffer: ${args.join(', ')}`); 364 | } 365 | }); 366 | 367 | it('setViewport', () => { 368 | const viewports = [ 369 | [0, 0, 1, 1, 0, 1], 370 | [1, 0, 1, 1, 0, 1], 371 | [1, 1, 1, 1, 0, 1], 372 | [1, 1, 0, 1, 0, 1], 373 | [1, 1, 0, 0, 0, 1], 374 | [1, 1, 0, 0, 0.5, 1], 375 | [1, 1, 0, 0, 0.5, 0.6], 376 | [1, 1, 0, 0, 0.5, 0.6], 377 | ]; 378 | for (let i = 0; i < viewports.length; ++i) { 379 | const viewport = viewports[i]; 380 | pass.setViewport(...viewport); 381 | const info = getAndResetRedundantCallInfo(); 382 | assertEqual(info.setViewport, i === viewports.length - 1 ? 1 : 0, `viewport: ${viewport.join(', ')}`); 383 | } 384 | }); 385 | 386 | it('setScissor', () => { 387 | const scissors = [ 388 | [0, 0, 1, 1], 389 | [1, 0, 1, 1], 390 | [1, 1, 1, 1], 391 | [1, 1, 0, 1], 392 | [1, 1, 0, 1], 393 | ]; 394 | for (let i = 0; i < scissors.length; ++i) { 395 | const scissor = scissors[i]; 396 | pass.setScissorRect(...scissor); 397 | const info = getAndResetRedundantCallInfo(); 398 | assertEqual(info.setScissorRect, i === scissors.length - 1 ? 1 : 0, `scissor: ${scissor.join(', ')}`); 399 | } 400 | }); 401 | 402 | it('setBlendConstant', () => { 403 | const constants = [ 404 | { constant: [0, 0, 1, 1], same: false }, 405 | { constant: [1, 0, 1, 1], same: false }, 406 | { constant: [1, 1, 1, 1], same: false }, 407 | { constant: [1, 1, 0, 1], same: false }, 408 | { constant: [1, 1, 0, 1], same: true }, 409 | { constant: { r: 0, g: 0, b: 1, a: 1 }, same: false }, 410 | { constant: { r: 1, g: 0, b: 1, a: 1 }, same: false }, 411 | { constant: { r: 1, g: 1, b: 1, a: 1 }, same: false }, 412 | { constant: { r: 1, g: 1, b: 0, a: 1 }, same: false }, 413 | { constant: { r: 1, g: 1, b: 0, a: 1 }, same: true }, 414 | { constant: [1, 1, 0, 1], same: true }, 415 | { constant: { r: 1, g: 1, b: 0, a: 1 }, same: true }, 416 | ]; 417 | for (const {constant, same} of constants) { 418 | pass.setBlendConstant(constant); 419 | const info = getAndResetRedundantCallInfo(); 420 | assertEqual(info.setBlendConstant, same ? 1 : 0, `blendConstant: ${JSON.stringify(constant)}`); 421 | } 422 | }); 423 | 424 | it('setStencilReference', () => { 425 | const references = [ 426 | 128, 427 | 128, 428 | ]; 429 | for (let i = 0; i < references.length; ++i) { 430 | const reference = references[i]; 431 | pass.setStencilReference(reference); 432 | const info = getAndResetRedundantCallInfo(); 433 | assertEqual(info.setStencilReference, i === references.length - 1 ? 1 : 0, `stencilReference: ${reference} (${i})`); 434 | } 435 | }); 436 | }); 437 | 438 | }); 439 | 440 | }); 441 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | type RedundantCalls = { 2 | setPipeline: number, 3 | setVertexBuffer: number, 4 | setIndexBuffer: number, 5 | setBindGroup: number, 6 | setViewport: number, 7 | setScissorRect: number, 8 | setBlendConstant: number, 9 | }; 10 | 11 | /** 12 | * Gets WebGPU redundant call counts and resets to 0. 13 | */ 14 | export declare function getAndResetRedundantCallInfo(): RedundantCalls; 15 | -------------------------------------------------------------------------------- /webgpu-avoid-redundant-state-setting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} RedundantCalls 3 | * @property {number} setPipeline 4 | * @property {number} setVertexBuffer 5 | * @property {number} setIndexBuffer 6 | * @property {number} setBindGroup 7 | * @property {number} setViewport 8 | * @property {number} setScissorRect 9 | * @property {number} setBlendConstant 10 | * @property {number} setStencilReference 11 | */ 12 | const redundantCalls = { 13 | setPipeline: 0, 14 | setVertexBuffer: 0, 15 | setIndexBuffer: 0, 16 | setBindGroup: 0, 17 | setViewport: 0, 18 | setScissorRect: 0, 19 | setBlendConstant: 0, 20 | setStencilReference: 0, 21 | }; 22 | 23 | /** 24 | * 25 | * @returns RedundantCalls 26 | */ 27 | export function getAndResetRedundantCallInfo() { 28 | const info = {...redundantCalls}; 29 | 30 | redundantCalls.setPipeline = 0; 31 | redundantCalls.setVertexBuffer = 0; 32 | redundantCalls.setIndexBuffer = 0; 33 | redundantCalls.setBindGroup = 0; 34 | redundantCalls.setViewport = 0; 35 | redundantCalls.setScissorRect = 0; 36 | redundantCalls.setBlendConstant = 0; 37 | redundantCalls.setStencilReference = 0; 38 | 39 | return info; 40 | } 41 | 42 | if (typeof GPUDevice !== 'undefined') { 43 | 44 | // Is this premature optimization? 45 | const freePassEncoderState = []; 46 | const passEncoderToStateMap = new Map(); 47 | const bindGroupLayoutToNumDynamicOffsetsMap = new WeakMap(); 48 | const numDynamicOffsetsSym = Symbol('numDynamicOffsets'); 49 | 50 | class RenderPassState { 51 | vertexState = []; 52 | bindGroupState = []; 53 | indexState = {}; 54 | currentPipeline = undefined; 55 | viewport = [-1, -1, -1, -1, -1, -1]; 56 | scissor = [-1, -1, -1, -1]; 57 | blendConstant = [0, 0, 0, 0]; 58 | stencilReference = 0; 59 | 60 | resetForExecuteBundles() { 61 | this.vertexState.length = 0; 62 | this.bindGroupState.length = 0; 63 | this.indexState.buffer = null; 64 | this.currentPipeline = null; 65 | return this; 66 | } 67 | reset() { 68 | this.resetForExecuteBundles(); 69 | this.viewport = [-1, -1, -1, -1, -1, -1]; 70 | this.scissor = [-1, -1, -1, -1]; 71 | this.blendConstant = [0, 0, 0, 0]; 72 | this.stencilReference = 0; 73 | return this; 74 | } 75 | } 76 | 77 | function normalizeColor(c) { 78 | return c.r === undefined 79 | ? c 80 | : [c.r, c.g, c.b, c.a]; 81 | } 82 | 83 | function getPassEncoderState() { 84 | if (freePassEncoderState.length === 0) { 85 | freePassEncoderState.push(new RenderPassState()); 86 | } 87 | return freePassEncoderState.pop().reset(); 88 | } 89 | 90 | function arrayEquals(a, b) { 91 | if (a.length !== b.length) { 92 | return false; 93 | } 94 | for (let i = 0; i < a.length; ++i) { 95 | if (a[i] !== b[i]) { 96 | return false; 97 | } 98 | } 99 | return true; 100 | } 101 | 102 | function wrapFn(ctor, name, fn) { 103 | ctor.prototype[name] = fn(ctor.prototype[name]); 104 | } 105 | 106 | wrapFn(GPUCommandEncoder, 'beginComputePass', function(origFn) { 107 | return function(...args) { 108 | const pass = origFn.call(this, ...args); 109 | passEncoderToStateMap.set(pass, getPassEncoderState()); 110 | return pass; 111 | }; 112 | }) 113 | 114 | wrapFn(GPUCommandEncoder, 'beginRenderPass', function(origFn) { 115 | return function(...args) { 116 | const pass = origFn.call(this, ...args); 117 | // TODO: We should try to set viewport and scissor from colorAttachments/depthStencilAttachment 118 | // but those only have textureViews and so we'd need to keep a map of views to textures. 119 | // I expect viewport and scissor are not set often so this seems overkill. 120 | passEncoderToStateMap.set(pass, getPassEncoderState()); 121 | return pass; 122 | }; 123 | }) 124 | 125 | wrapFn(GPUCommandEncoder, 'executeBundles', function(origFn) { 126 | return function(...args) { 127 | passEncoderToStateMap.get(this).resetForExecuteBundles(); 128 | origFn.call(this, ...args); 129 | }; 130 | }); 131 | 132 | wrapFn(GPUDevice, 'createBindGroupLayout', function(origFn) { 133 | return function(desc) { 134 | const layout = origFn.call(this, desc); 135 | let numDynamicOffsets = 0; 136 | for (const entry of desc.entries) { 137 | numDynamicOffsets += entry.buffer?.hasDynamicOffset ? 1 : 0; 138 | } 139 | bindGroupLayoutToNumDynamicOffsetsMap.set(layout, numDynamicOffsets) 140 | return layout; 141 | }; 142 | }); 143 | 144 | wrapFn(GPUDevice, 'createBindGroup', function(origFn) { 145 | return function(desc) { 146 | const bg = origFn.call(this, desc); 147 | bg[numDynamicOffsetsSym] = bindGroupLayoutToNumDynamicOffsetsMap.get(desc.layout); 148 | return bg; 149 | }; 150 | }); 151 | 152 | function getDynamicOffsetsLength(bindGroup, length) { 153 | // the bindGroup was layout 'auto'; 154 | const numDynamicOffsets = bindGroup[numDynamicOffsetsSym]; 155 | if (numDynamicOffsetsSym === undefined) { 156 | return 0; 157 | } 158 | return length === undefined 159 | ? numDynamicOffsets 160 | : Math.min(length, numDynamicOffsets); 161 | } 162 | 163 | function ASSERT(cond) { 164 | if (!cond) { 165 | debugger; 166 | throw new Error('assert'); 167 | } 168 | } 169 | 170 | function bindGroupSame(cachedBindGroup, bindGroup, dynamicOffsets, start, length) { 171 | if (!cachedBindGroup || cachedBindGroup.bindGroup !== bindGroup) { 172 | return false; 173 | }; 174 | if (!dynamicOffsets && !cachedBindGroup.dynamicOffsets) { 175 | return true; 176 | } 177 | const cachedDynamicOffsets = cachedBindGroup.dynamicOffsets; 178 | ASSERT(bindGroup[numDynamicOffsetsSym] !== undefined); 179 | length = getDynamicOffsetsLength(bindGroup, length) 180 | if (length !== cachedDynamicOffsets.length) { 181 | return false; 182 | } 183 | start = start || 0; 184 | for (let i = 0; i < length; ++i) { 185 | if (dynamicOffsets[start + i] !== cachedDynamicOffsets[i]) { 186 | return false; 187 | } 188 | } 189 | return true; 190 | } 191 | 192 | function dupOffsets(offsets, start, length, bindGroup) { 193 | length = getDynamicOffsetsLength(bindGroup, length) 194 | if (Array.isArray(offsets)) { 195 | return offsets.slice(0, length); 196 | } 197 | if (offsets instanceof Uint32Array) { 198 | return offsets.slice(start || 0, length); 199 | } 200 | return offsets; 201 | } 202 | 203 | const setPipelineWrapper = function(origFn) { 204 | return function(pipeline) { 205 | const state = passEncoderToStateMap.get(this); 206 | if (state.currentPipeline !== pipeline) { 207 | state.currentPipeline = pipeline; 208 | origFn.call(this, pipeline); 209 | } else { 210 | ++redundantCalls.setPipeline; 211 | } 212 | }; 213 | }; 214 | wrapFn(GPURenderPassEncoder, 'setPipeline', setPipelineWrapper); 215 | wrapFn(GPUComputePassEncoder, 'setPipeline', setPipelineWrapper); 216 | 217 | const setBindGroupWrapper = function(origFn) { 218 | return function(ndx, ...args) { 219 | const [bindGroup, dynamicOffsets, start, length] = args; 220 | const {bindGroupState} = passEncoderToStateMap.get(this); 221 | if (!bindGroupSame(bindGroupState[ndx], bindGroup, dynamicOffsets, start, length)) { 222 | bindGroupState[ndx] = {bindGroup, dynamicOffsets: dupOffsets(dynamicOffsets, start, length, bindGroup), start, length}; 223 | origFn.call(this, ndx, ...args); 224 | } else { 225 | ++redundantCalls.setBindGroup; 226 | } 227 | }; 228 | } 229 | wrapFn(GPURenderPassEncoder, 'setBindGroup', setBindGroupWrapper); 230 | wrapFn(GPUComputePassEncoder, 'setBindGroup', setBindGroupWrapper); 231 | 232 | wrapFn(GPURenderPassEncoder, 'setViewport', function(origFn) { 233 | return function(...args) { 234 | const state = passEncoderToStateMap.get(this); 235 | if (!arrayEquals(state.viewport, args)) { 236 | state.viewport = args.slice(); 237 | origFn.call(this, ...args); 238 | } else { 239 | ++redundantCalls.setViewport; 240 | } 241 | }; 242 | }); 243 | 244 | wrapFn(GPURenderPassEncoder, 'setScissorRect', function(origFn) { 245 | return function(...args) { 246 | const state = passEncoderToStateMap.get(this); 247 | if (!arrayEquals(state.scissor, args)) { 248 | state.scissor = args.slice(); 249 | origFn.call(this, ...args); 250 | } else { 251 | ++redundantCalls.setScissorRect; 252 | } 253 | }; 254 | }); 255 | 256 | wrapFn(GPURenderPassEncoder, 'setBlendConstant', function(origFn) { 257 | return function(newColor) { 258 | const state = passEncoderToStateMap.get(this); 259 | const color = normalizeColor(newColor); 260 | if (!arrayEquals(state.blendConstant, color)) { 261 | state.blendConstant = color.slice(); 262 | origFn.call(this, newColor); 263 | } else { 264 | ++redundantCalls.setBlendConstant; 265 | } 266 | }; 267 | }); 268 | 269 | wrapFn(GPURenderPassEncoder, 'setStencilReference', function(origFn) { 270 | return function(newRef) { 271 | const state = passEncoderToStateMap.get(this); 272 | if (state.stencilReference !== newRef) { 273 | state.stencilReference = newRef; 274 | origFn.call(this, newRef); 275 | } else { 276 | ++redundantCalls.setStencilReference; 277 | } 278 | }; 279 | }); 280 | 281 | function vertexBufferSame(v, buffer, offset, size) { 282 | return v && v.buffer === buffer && v.offset === (offset || 0) && v.size === (size === undefined ? buffer.size : size); 283 | } 284 | 285 | wrapFn(GPURenderPassEncoder, 'setVertexBuffer', function(origFn) { 286 | return function(ndx, ...args) { 287 | const [buffer, offset, size] = args; 288 | const {vertexState} = passEncoderToStateMap.get(this); 289 | if (!vertexBufferSame(vertexState[ndx], buffer, offset, size)) { 290 | vertexState[ndx] = {buffer, offset: offset || 0, size: size === undefined ? buffer.size : size}; 291 | origFn.call(this, ndx, ...args); 292 | } else { 293 | ++redundantCalls.setVertexBuffer; 294 | } 295 | }; 296 | }); 297 | 298 | wrapFn(GPURenderPassEncoder, 'setIndexBuffer', function(origFn) { 299 | return function(...args) { 300 | let [buffer, format, offset, size] = args; 301 | const {indexState} = passEncoderToStateMap.get(this); 302 | offset = offset || 0; 303 | size = size === undefined 304 | ? buffer.size - offset 305 | : size; 306 | if (buffer !== indexState.buffer || 307 | format !== indexState.format || 308 | offset !== indexState.offset || 309 | size !== indexState.size) { 310 | indexState.buffer = buffer; 311 | indexState.format = format; 312 | indexState.offset = offset; 313 | indexState.size = size; 314 | origFn.call(this, ...args); 315 | } else { 316 | ++redundantCalls.setIndexBuffer; 317 | } 318 | }; 319 | }); 320 | 321 | const endWrapper = function(origFn) { 322 | return function(...args) { 323 | const state = passEncoderToStateMap.get(this); 324 | passEncoderToStateMap.delete(this); 325 | freePassEncoderState.push(state.reset()); 326 | return origFn.call(this, ...args); 327 | }; 328 | }; 329 | wrapFn(GPURenderPassEncoder, 'end', endWrapper); 330 | wrapFn(GPUComputePassEncoder, 'end', endWrapper); 331 | 332 | } 333 | -------------------------------------------------------------------------------- /webgpu-check-redundant-state-setting.js: -------------------------------------------------------------------------------- 1 | import { getAndResetRedundantCallInfo } from './webgpu-avoid-redundant-state-setting.js'; 2 | 3 | window.requestAnimationFrame = (function(origFn) { 4 | return function(fn) { 5 | return origFn.call(this, (time) => { 6 | const info = getAndResetRedundantCallInfo(); 7 | console.log('rc:', info.setVertexBuffer + info.setIndexBuffer + info.setBindGroup, JSON.stringify(info)); 8 | fn(time); 9 | }); 10 | }; 11 | })(window.requestAnimationFrame); --------------------------------------------------------------------------------