diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f0140c4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules/ +/tmp +/src +/coverage +/tests +*.md +/.idea +/.git diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdfad22 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto eol=lf +*.{cmd,[cC}[mM][dD]} text eol=crlf +*.{bat,[bB}[aA][tT]} text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47c0519 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/lib/ +/tmp/ +/dist/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..046f843 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ + +/compiler.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/jelly.iml b/.idea/jelly.iml new file mode 100644 index 0000000..7eaef70 --- /dev/null +++ b/.idea/jelly.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..d23208f --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..67fd72b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8dc4d77 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f6c0580 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +scope=@cs-au-dk +access=public +@cs-au-dk:registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..926decc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +FROM ubuntu:20.04 as builder +WORKDIR /tools + +# install Node +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + wget \ + netcat \ + ca-certificates && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* +ARG NODE_VERSION=18.12.1 +ARG NODE_PACKAGE=node-v${NODE_VERSION}-linux +RUN arch="$(dpkg --print-architecture)"; \ + case "$arch" in \ + amd64) export ARCH='x64' ;; \ + arm64) export ARCH='arm64' ;; \ + esac; \ + \ + wget -c https://nodejs.org/dist/v$NODE_VERSION/$NODE_PACKAGE-$ARCH.tar.gz -O -| tar -xzC /tools/ && \ + mv /tools/$NODE_PACKAGE-$ARCH /tools/node + +# install GraalVM JavaScript and NodeProf +RUN apt-get update && apt-get install -y git gcc g++ make python3 python3-pip && pip3 install ninja_syntax +RUN git clone --depth=1 --branch 6.0.4 https://github.com/graalvm/mx.git +RUN /tools/mx/mx fetch-jdk --java-distribution labsjdk-ce-17 +RUN mv /root/.mx/jdks/labsjdk-ce-17-* /tools/jdk +ENV JAVA_HOME=/tools/jdk +RUN git clone --depth=1 https://github.com/Haiyang-Sun/nodeprof.js.git +WORKDIR /tools/nodeprof.js +RUN /tools/mx/mx sforceimports +RUN /tools/mx/mx build +RUN /tools/mx/mx --dy /compiler build +ENV GRAAL_HOME=/tools/graal/sdk/latest_graalvm_home + +FROM ubuntu:20.04 +RUN mkdir -p /usr/lib/jvm +COPY --from=builder /tools/node /opt/node +COPY --from=builder /tools/jdk /usr/lib/jvm/jdk +COPY --from=builder /tools/graal/sdk/latest_graalvm_home /usr/lib/jvm/graalvm +ENV NODE_PATH /opt/node/lib/node_modules +ENV PATH /opt/node/bin:$PATH +ENV JAVA_HOME=/usr/lib/jvm/jdk +ENV GRAAL_HOME=/usr/lib/jvm/graalvm +ENV NODE_OPTIONS --max-old-space-size=8000 + +# install Jelly files built locally +RUN mkdir /jelly +WORKDIR /jelly +COPY ./package.json ./package-lock.json ./ +RUN npm install --omit=dev +COPY ./lib ./lib +RUN npm link +ENTRYPOINT ["node", "/jelly/lib/main.js"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cdb4bbf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Anders Møller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..864118f --- /dev/null +++ b/README.md @@ -0,0 +1,214 @@ +# Jelly + +[![MIT License](https://img.shields.io/github/license/cs-au-dk/jelly)](LICENSE) +[![npm version](https://img.shields.io/npm/v/@cs-au-dk/jelly)](https://www.npmjs.com/package/@cs-au-dk/jelly) + +#### Copyright © 2023 Anders Møller + +Jelly is a static analyzer for performing + +* *call graphs construction*, +* *library usage pattern matching*, and +* *vulnerability exposure analysis* + +for JavaScript (and TypeScript) programs that use the Node.js platform. + +The analyzer design is based on ideas from JAM [1], TAPIR [2] and ACG [3]. +Its core is a flow-insensitive control-flow and points-to analysis that uses a hybrid of field-based and allocation-site abstraction, +together with access paths for tracking library usage. +It models the main parts of the ECMAScript language and standard library (intentionally not fully soundly!), +and not (yet) the Node.js standard library. + +[1] Benjamin Barslev Nielsen, Martin Toldam Torp, Anders Møller: +[Modular call graph construction for security scanning of Node.js applications](https://dl.acm.org/doi/10.1145/3460319.3464836). +ISSTA 2021: 29-41 + +[2] Anders Møller, Benjamin Barslev Nielsen, Martin Toldam Torp: +[Detecting locations in JavaScript programs affected by breaking library changes](https://dl.acm.org/doi/10.1145/3428255). +Proc. ACM Program. Lang. 4(OOPSLA): 187:1-187:25 (2020) + +[3] Asger Feldthaus, Max Schäfer, Manu Sridharan, Julian Dolby, Frank Tip: +[Efficient construction of approximate call graphs for JavaScript IDE services](https://ieeexplore.ieee.org/document/6606621/). ICSE 2013: 752-761 + +## Installing + +```bash +npm install -g @cs-au-dk/jelly +``` + +Other options are described below at [How to build](#how-to-build). + +## Usage + +See the full usage: +```bash +jelly --help +``` + +When running the Jelly static analyzer, one or more entry files are given as input. +Directories are expanded (using heuristics to skip certain files and directories, see [files.ts](src/misc/files.ts)). +All files reachable from entry files are analyzed, except +if option `--ignore-dependencies` is used, in which case only entry files are analyzed, +and only files within the base directory (auto-detected or specified using option `--basedir` or `-b`) are included. + +As an example, generate a call graph for the `winston` package and all its dependencies, both in JSON format and for HTML visualization: +```bash +jelly -j cg.json -m cg.html node_modules/winston -b node_modules +``` + +Viewing `cg.html` in a browser: + +![call graph visualization](misc/winston-cg.png) + +To set the heap limit, prefix commands by, for example: +```bash +NODE_OPTIONS=--max-old-space-size=4096 +``` + +Note that analyzing with all dependencies (i.e., not using `--ignore-dependencies`) can take a long time. +The options `--max-rounds` or `--timeout` can be used to terminate the analysis early to provide partial (unsound) results. + +## How to build + +Install dependencies: +```bash +npm install +``` + +Compile TypeScript code: +```bash +npm run build +``` + +After compilation, Jelly can be run like this: +```bash +node lib/main.js +``` + +Build binary executables (optional), placed in `dist/`: +```bash +sudo npm install -g pkg +npm run pkg +``` + +## Docker + +Build Docker image (including support for dynamic call graph construction): +```bash +npm run build-docker +``` + +Run Jelly in Docker with the directory specified as first argument as current working directory: +```bash +./bin/jelly-docker . tests/helloworld/app.js --callgraph-html cg.html +``` + +## Server-mode + +Jelly can be run in server-mode as an alternative to the command-line interface: +```bash +jelly-server +``` +or +```bash +node lib/server.js +``` +See also the instructions above for how to build binary executables. + +Requests to the server are sent on stdin using the JSON format described in `typings/ipc.d.ts`. +Responses are returned (asynchronously) on stdout with the two-line header (including the empty line) +``` +Content-Length: + +``` +with `\r\n` linebreaks. + +## Dynamic call graph construction + +Jelly supports dynamic call graph construction via [NodeProf](https://github.com/Haiyang-Sun/nodeprof.js/), +which can be used for measuring recall (or unsoundness) of the static analysis. + +Install NodeProf (see also the information about Docker above): +```bash +sudo dnf install g++ libstdc++-static +mkdir -p ~/tools; cd ~/tools +git clone --depth 1 --branch 6.0.4 https://github.com/graalvm/mx.git +export PATH=$PATH:$HOME/tools/mx +mx -y fetch-jdk --java-distribution labsjdk-ce-17 +export JAVA_HOME=$HOME/.mx/jdks/labsjdk-ce-17-jvmci-22.2-b01 +git clone --depth 1 https://github.com/Haiyang-Sun/nodeprof.js.git +cd nodeprof.js +mx sforceimports +mx --dy /compiler build +``` + +As an example, run `tests/micro/classes.js` or `tests/helloworld/app.js` with instrumentation for call graph construction: +```bash +export GRAAL_HOME=$HOME/tools/graal/sdk/latest_graalvm_home + +jelly tests/micro/classes.js -d cg.json +jelly tests/helloworld/app.js -d cg.json +``` +Extra arguments to the JavaScript program can be added after `--`. + +It is also possible to run `npm test` with instrumentation: +```bash +jelly --npm-test tests/mochatest -d cg.json +``` + +Another approach is to add `$JELLY_HOME/lib/bin/node` to `PATH` and set `JELLY_OUT`, for example to run Mocha directly: +```bash +cd tests/mochatest +PATH=$JELLY_HOME/lib/bin:$PATH JELLY_OUT=cg.json node_modules/.bin/mocha +``` +where `JELLY_HOME` is the home directory of Jelly. +This results in a file `cg.json-` for each instrumented file that is executed. + +Call graphs (generated either statically or dynamically) can be compared for precision and recall: +```bash +jelly --compare-callgraphs cg1.json cg2.json +``` + +## For developers + +Compile TypeScript code in watch mode: +```bash +npm run build-watch +``` + +Install as scripts (`jelly` and `jelly-server`) for development: +```bash +sudo npm link +``` + +Install dependencies for tests: +```bash +npm run tests-install +``` + +Run all tests: +```bash +npm test +``` + +Run individual tests (specified by regex), for example: +```bash +npm test -- -t tests/helloworld +``` + +### Differential testing + +Differential testing can be used to test if updated code results in lower recall than the previous version by comparing the dataflow graph and call graphs of the two versions. + +Run the following command to test the testing framework: +```bash +TAG= npm run differential -- -t tiny +``` +where `` is the git tag of the previous version you want to compare to. + +Then run the following commands to start full test: +```bash +TAG= npm run differential +``` + +During the test, the old version of Jelly will be installed in `tests/node_modules/jelly-previous` and test packages will be installed in `tmp/packages`. \ No newline at end of file diff --git a/bin/jelly-docker b/bin/jelly-docker new file mode 100755 index 0000000..3ddbebc --- /dev/null +++ b/bin/jelly-docker @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +if [[ -z "$1" ]]; then + echo "Error: Workspace root directory missing, aborting" + exit -1 +fi +docker run --rm --name jelly --network none -v $(readlink -f "$1"):/workspace -w /workspace --user $(id -u):$(id -g) jelly ${@:2} diff --git a/bin/node b/bin/node new file mode 100755 index 0000000..92f6f34 --- /dev/null +++ b/bin/node @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +export JELLY_BIN=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +export JELLY_OUT=${JELLY_OUT:-'/tmp/jelly.json'} +DYNJS=$( dirname $JELLY_BIN )/dynamic/dyn.js + +if [[ -z "${GRAAL_HOME}" ]]; then + echo "Error: Environment variable GRAAL_HOME not set, aborting" + exit -1 +fi + +if [[ ! -x $GRAAL_HOME/bin/node ]]; then + echo "Error: $GRAAL_HOME/bin/node executable not found, aborting" + exit -1 +fi + +if [[ ! -f $GRAAL_HOME/tools/nodeprof/jalangi.js ]]; then + echo "Error: $GRAAL_HOME/tools/nodeprof/jalangi.js not found, aborting" + exit -1 +fi + +if [[ ! -f $DYNJS ]]; then + echo "Error: $DYNJS not found, aborting" + exit -1 +fi + +PATH=$GRAAL_HOME/bin:$PATH exec node --jvm --experimental-options --nodeprof $GRAAL_HOME/tools/nodeprof/jalangi.js --analysis $DYNJS $@ diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..7d5631b --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,211 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +const base = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/tmp/jest_rs", + + // Automatically clear mock calls, instances and results before every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "tmp/coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + maxWorkers: 1, + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + preset: "ts-jest", + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + roots: [ + "tests" + ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + "setupFilesAfterEnv": ["jest-expect-message"], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: "jest-environment-node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; + +export default { + projects: [ + { + displayName: "default", + ...base, + testMatch: [ + "**/*.test.ts" + ], + }, + { + displayName: "differential", + ...base, + // A path to a module which exports an async function that is triggered once before all test suites + globalSetup: "/tests/differential/install.ts", + // The glob patterns Jest uses to detect test files + testMatch: [ + "**/*.difftest.ts" + ], + transformIgnorePatterns: ["node_modules/(?!(jelly-previous)/)"] + }, + ], +} \ No newline at end of file diff --git a/misc/winston-cg.png b/misc/winston-cg.png new file mode 100644 index 0000000..803870f Binary files /dev/null and b/misc/winston-cg.png differ diff --git a/nodes.md b/nodes.md new file mode 100644 index 0000000..cf20412 --- /dev/null +++ b/nodes.md @@ -0,0 +1,294 @@ +# Babel AST node types + +https://babeljs.io/docs/en/babel-types +https://github.com/babel/babel/blob/master/packages/babel-parser/ast/spec.md + +### Program + +- [X] File (ignore) +- [X] Program + +### Declarations (excluding classes) + +- [X] VariableDeclaration + - [X] VariableDeclarator +- [X] FunctionDeclaration + +### Statements + +- [X] ReturnStatement +- [X] ThrowStatement +- [ ] CatchClause +- [X] DoWhileStatement (ignore) +- [X] EmptyStatement (ignore) +- [X] ContinueStatement (ignore) +- [X] ExpressionStatement (ignore) +- [X] SwitchStatement (ignore) + - [X] SwitchCase (ignore) +- [X] TryStatement (ignore) +- [X] WhileStatement (ignore) +- [X] BlockStatement (ignore) +- [X] BreakStatement (ignore) +- [X] IfStatement (ignore) +- [X] LabeledStatement (ignore) +- [X] ForStatement (ignore) +- [X] ForInStatement (ignore) +- [X] ForOfStatement +- [X] WithStatement (warn) + +### Literals + +- [X] StringLiteral +- [X] BooleanLiteral (ignore) +- [X] NullLiteral (ignore) +- [X] NumericLiteral (ignore) +- [X] BigIntLiteral (ignore) +- [X] RegExpLiteral (ignore) + +### Expressions (excluding literals, classes, etc.) + +- [X] Identifier +- [ ] PrivateName (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields) +- [X] ArrowFunctionExpression +- [X] AssignmentExpression +- [X] CallExpression +- [X] ConditionalExpression +- [X] FunctionExpression +- [X] LogicalExpression +- [X] MemberExpression +- [X] NewExpression +- [X] SequenceExpression +- [X] ObjectExpression (ignore) + - [X] ObjectMethod + - [X] ObjectProperty +- [X] ArrayExpression (ignore) +- [X] UnaryExpression (ignore) +- [X] BinaryExpression (ignore) +- [X] UpdateExpression (ignore) +- [X] ParenthesizedExpression (ignore) +- [X] TemplateLiteral (ignore) +- [X] TemplateElement (ignore) +- [X] OptionalCallExpression +- [X] OptionalMemberExpression +- [X] ThisExpression +- [X] YieldExpression +- [X] TaggedTemplateExpression +- [X] AwaitExpression + +### Classes + +- [X] ClassDeclaration +- [X] ClassExpression +- [X] ClassBody + - [X] ClassMethod + - [X] ClassPrivateMethod + - [X] ClassPrivateProperty + - [X] ClassProperty + - [X] StaticBlock +- [ ] Super (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super) + +### Patterns, rest, spread + +- [X] AssignmentPattern +- [X] ObjectPattern +- [X] ArrayPattern +- [X] RestElement + + +- [ ] SpreadElement (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) + + +### Module/import/export + +- [X] ImportDeclaration + - [X] ImportSpecifier + - [X] ImportDefaultSpecifier + - [X] ImportNamespaceSpecifier + - [ ] ImportAttribute (https://github.com/tc39/proposal-import-assertions) +- [X] Import +- [X] ExportNamedDeclaration + - [X] ExportSpecifier + - [ ] ExportNamespaceSpecifier (`export * as x from "m"`, like `import * as x from "m"; export {x}`) + - [X] ExportDefaultSpecifier +- [X] ExportDefaultDeclaration +- [X] ExportAllDeclaration + +### Other + +- [X] Directive (ignore) + - [X] DirectiveLiteral (ignore) +- [X] Noop (ignore) +- [X] DebuggerStatement (ignore) +- [X] Placeholder (ignore) +- [X] InterpreterDirective (ignore) +- [X] V8IntrinsicIdentifier (see the 'v8intrinsic' Babel plugin) + +### JSX + +https://github.com/facebook/jsx/blob/main/AST.md + +- [X] JSXElement +- [X] JSXIdentifier +- [X] JSXMemberExpression +- [X] JSXFragment (ignore) +- [X] JSXNamespacedName (ignore) +- [X] JSXOpeningFragment (ignore) +- [X] JSXClosingFragment (ignore) +- [X] JSXExpressionContainer (ignore) +- [X] JSXEmptyExpression (ignore) +- [X] JSXSpreadChild (ignore) +- [X] JSXOpeningElement (ignore) +- [X] JSXClosingElement (ignore) +- [X] JSXAttribute (ignore) +- [X] JSXSpreadAttribute (ignore) +- [X] JSXText (ignore) + +# Language extensions (currently all ignored) + +### Module blocks + +https://github.com/tc39/proposal-js-module-blocks + +- ModuleExpression + +### Grouped accessors and auto-accessors + +https://github.com/tc39/proposal-grouped-and-auto-accessors + +- ClassAccessorProperty + +### Decimal + +https://github.com/tc39/proposal-decimal + +- DecimalLiteral + +### Generator function.sent meta property + +https://babeljs.io/docs/en/babel-plugin-proposal-function-sent + +- MetaProperty + +### Pipeline operator + +https://github.com/tc39/proposal-pipeline-operator +https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator + +- PipelineBareFunction +- PipelineTopicExpression +- PipelinePrimaryTopicReference +- TopicReference + +### Decorators + +https://github.com/tc39/proposal-decorators +https://babeljs.io/docs/en/babel-plugin-proposal-decorators + +- Decorator + +### Records and tuples + +https://github.com/tc39/proposal-record-tuple +https://babeljs.io/docs/en/babel-plugin-proposal-record-and-tuple + +- RecordExpression +- TupleExpression + +### Do expressions + +https://github.com/tc39/proposal-do-expressions +https://babeljs.io/docs/en/babel-plugin-proposal-do-expressions + +- DoExpression + +### Partial application + +https://github.com/tc39/proposal-partial-application +https://babeljs.io/docs/en/babel-plugin-proposal-partial-application + +- ArgumentPlaceholder + +### Bind expressions + +https://github.com/tc39/proposal-bind-operator +https://babeljs.io/docs/en/babel-plugin-proposal-function-bind + +- BindExpression + +### TypeScript + +https://babeljs.io/docs/en/babel-types#typescript +https://babeljs.io/docs/en/babel-plugin-transform-typescript + +- TS* + +### Flow + +https://babeljs.io/docs/en/babel-types#flow +https://babeljs.io/docs/en/babel-plugin-transform-flow-strip-types + +- AnyTypeAnnotation +- ArrayTypeAnnotation +- BooleanLiteralTypeAnnotation +- BooleanTypeAnnotation +- ClassImplements +- DeclareClass +- DeclareExportAllDeclaration +- DeclareExportDeclaration +- DeclareFunction +- DeclareInterface +- DeclareModule +- DeclareModuleExport +- DeclareOpaqueType +- DeclareTypeAlias +- DeclareVariable +- DeclaredPredicate +- EmptyTypeAnnotation +- EnumBooleanBody +- EnumBooleanMember +- EnumDeclaration +- EnumDefaultedMember +- EnumNumberBody +- EnumNumberMember +- EnumStringBody +- EnumStringMember +- EnumSymbolBody +- ExistsTypeAnnotation +- FunctionTypeAnnotation +- FunctionTypeParam +- GenericTypeAnnotation +- IndexedAccessType +- InferredPredicate +- InterfaceDeclaration +- InterfaceExtends +- InterfaceTypeAnnotation +- IntersectionTypeAnnotation +- MixedTypeAnnotation +- NullLiteralTypeAnnotation +- NullableTypeAnnotation +- NumberLiteralTypeAnnotation +- NumberTypeAnnotation +- ObjectTypeAnnotation +- ObjectTypeCallProperty +- ObjectTypeIndexer +- ObjectTypeInternalSlot +- ObjectTypeProperty +- ObjectTypeSpreadProperty +- OpaqueType +- OptionalIndexedAccessType +- QualifiedTypeIdentifier +- StringLiteralTypeAnnotation +- StringTypeAnnotation +- SymbolTypeAnnotation +- TupleTypeAnnotation +- ThisTypeAnnotation +- TypeAlias +- TypeAnnotation +- TypeCastExpression +- TypeParameter +- TypeParameterDeclaration +- TypeParameterInstantiation +- TypeofTypeAnnotation +- UnionTypeAnnotation +- Variance +- VoidTypeAnnotation diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4513aad --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9521 @@ +{ + "name": "@cs-au-dk/jelly", + "version": "0.5.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@cs-au-dk/jelly", + "version": "0.5.0", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.12", + "@babel/parser": "^7.20.15", + "@babel/plugin-proposal-decorators": "^7.20.13", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typescript": "^7.20.13", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7", + "@types/semver": "^7.3.13", + "commander": "^9.4.1", + "micromatch": "^4.0.5", + "semver": "^7.3.8", + "stringify2stream": "^1.1.0", + "typescript": "^4.9.5", + "winston": "^3.7.2" + }, + "bin": { + "jelly": "lib/main.js", + "jelly-server": "lib/server.js" + }, + "devDependencies": { + "@types/babel__core": "^7.20.0", + "@types/babel__traverse": "^7.18.3", + "@types/jest": "^28.1.8", + "@types/micromatch": "^4.0.2", + "@types/node": "^18.13.0", + "jest": "^28.1.3", + "jest-expect-message": "^1.1.3", + "ts-jest": "^28.0.7", + "ts-node": "^10.9.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "dependencies": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz", + "integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", + "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "dependencies": { + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.13.tgz", + "integrity": "sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", + "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.13.tgz", + "integrity": "sha512-7T6BKHa9Cpd7lCueHBBzP0nkXNina+h5giOZw+a8ZpMfPFY19VjJAjIxyFHuWkhCWgL6QMqRiY/wB1fLXzm6Mw==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.20.12", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", + "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.13.tgz", + "integrity": "sha512-O7I/THxarGcDZxkgWKMUrk7NK1/WbHAg3Xx86gqS6x9MTrNL6AwIluuZ96ms4xeDe6AVx6rjHbWHP7x26EPQBA==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.20.12", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", + "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.13", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "dependencies": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.50", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.50.tgz", + "integrity": "sha512-k8ETQOOQDg5FtK7y9KJWpsGLik+QlPmIi8zzl/dGUgshV2QitprkFlCR/AemjWOTyKn9UwSSGRTzLVotvgCjYQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/braces": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.1.tgz", + "integrity": "sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==", + "dev": true + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "dependencies": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "node_modules/@types/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==", + "dev": true, + "dependencies": { + "@types/braces": "*" + } + }, + "node_modules/@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", + "dev": true + }, + "node_modules/@types/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", + "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001449", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001449.tgz", + "integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", + "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "dependencies": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-expect-message": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/jest-expect-message/-/jest-expect-message-1.1.3.tgz", + "integrity": "sha512-bTK77T4P+zto+XepAX3low8XVQxDgaEqh3jSTQOG8qvPpD69LsIdyJTa+RmnJh3HNSzJng62/44RPPc7OIlFxg==", + "dev": true + }, + "node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "dev": true, + "dependencies": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "dev": true, + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dependencies": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz", + "integrity": "sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify2stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stringify2stream/-/stringify2stream-1.1.0.tgz", + "integrity": "sha512-69LPWdFoBFzPug93gJStxiBGaahUglPSomITXi9umiLhcyxEx1oyW27qcw5A7QVp9xwBygcAQJzkvoCZUSfrAw==" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "node_modules/ts-jest": { + "version": "28.0.8", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", + "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^28.0.0", + "babel-jest": "^28.0.0", + "jest": "^28.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "dependencies": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", + "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==" + }, + "@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "requires": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz", + "integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", + "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==" + }, + "@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/helpers": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.13.tgz", + "integrity": "sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==", + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.20.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", + "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==" + }, + "@babel/plugin-proposal-decorators": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.20.13.tgz", + "integrity": "sha512-7T6BKHa9Cpd7lCueHBBzP0nkXNina+h5giOZw+a8ZpMfPFY19VjJAjIxyFHuWkhCWgL6QMqRiY/wB1fLXzm6Mw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.20.12", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/plugin-syntax-decorators": "^7.19.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz", + "integrity": "sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.13.tgz", + "integrity": "sha512-O7I/THxarGcDZxkgWKMUrk7NK1/WbHAg3Xx86gqS6x9MTrNL6AwIluuZ96ms4xeDe6AVx6rjHbWHP7x26EPQBA==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.20.12", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", + "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.13", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "dev": true, + "requires": { + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" + } + }, + "@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" + } + }, + "@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.24.1" + } + }, + "@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@sinclair/typebox": { + "version": "0.24.50", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.50.tgz", + "integrity": "sha512-k8ETQOOQDg5FtK7y9KJWpsGLik+QlPmIi8zzl/dGUgshV2QitprkFlCR/AemjWOTyKn9UwSSGRTzLVotvgCjYQ==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", + "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/braces": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.1.tgz", + "integrity": "sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==", + "dev": true + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "28.1.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz", + "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==", + "dev": true, + "requires": { + "expect": "^28.0.0", + "pretty-format": "^28.0.0" + } + }, + "@types/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==", + "dev": true, + "requires": { + "@types/braces": "*" + } + }, + "@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", + "dev": true + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", + "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "babel-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", + "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "dev": true, + "requires": { + "@jest/transform": "^28.1.3", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^28.1.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", + "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", + "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^28.1.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001449", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001449.tgz", + "integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", + "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "requires": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", + "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/types": "^28.1.3", + "import-local": "^3.0.2", + "jest-cli": "^28.1.3" + } + }, + "jest-changed-files": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", + "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", + "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "p-limit": "^3.1.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-cli": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", + "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "dev": true, + "requires": { + "@jest/core": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-config": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", + "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^28.1.3", + "@jest/types": "^28.1.3", + "babel-jest": "^28.1.3", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^28.1.3", + "jest-environment-node": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", + "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", + "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "jest-util": "^28.1.3", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-node": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", + "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "jest-expect-message": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/jest-expect-message/-/jest-expect-message-1.1.3.tgz", + "integrity": "sha512-bTK77T4P+zto+XepAX3low8XVQxDgaEqh3jSTQOG8qvPpD69LsIdyJTa+RmnJh3HNSzJng62/44RPPc7OIlFxg==", + "dev": true + }, + "jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true + }, + "jest-haste-map": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", + "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", + "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "dev": true, + "requires": { + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + } + }, + "jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "dev": true + }, + "jest-resolve": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", + "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", + "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "dev": true, + "requires": { + "jest-regex-util": "^28.0.2", + "jest-snapshot": "^28.1.3" + } + }, + "jest-runner": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", + "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "dev": true, + "requires": { + "@jest/console": "^28.1.3", + "@jest/environment": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "graceful-fs": "^4.2.9", + "jest-docblock": "^28.1.1", + "jest-environment-node": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-leak-detector": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-resolve": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-util": "^28.1.3", + "jest-watcher": "^28.1.3", + "jest-worker": "^28.1.3", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", + "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/globals": "^28.1.3", + "@jest/source-map": "^28.1.2", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", + "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-haste-map": "^28.1.3", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "natural-compare": "^1.4.0", + "pretty-format": "^28.1.3", + "semver": "^7.3.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", + "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^28.0.2", + "leven": "^3.1.0", + "pretty-format": "^28.1.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dev": true, + "requires": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz", + "integrity": "sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve.exports": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", + "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==" + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "stringify2stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stringify2stream/-/stringify2stream-1.1.0.tgz", + "integrity": "sha512-69LPWdFoBFzPug93gJStxiBGaahUglPSomITXi9umiLhcyxEx1oyW27qcw5A7QVp9xwBygcAQJzkvoCZUSfrAw==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "ts-jest": { + "version": "28.0.8", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", + "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^28.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + } + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", + "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "requires": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", + "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a5e17e1 --- /dev/null +++ b/package.json @@ -0,0 +1,74 @@ +{ + "name": "@cs-au-dk/jelly", + "version": "0.5.0", + "description": "Jelly - call graph and library usage analyzer for JavaScript", + "author": "Anders Møller ", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/cs-au-dk/jelly.git" + }, + "homepage": "https://github.com/cs-au-dk/jelly#readme", + "keywords": [ + "call graph", + "library usage", + "static analysis", + "program analysis" + ], + "files": [ + "lib" + ], + "bin": { + "jelly": "lib/main.js", + "jelly-server": "lib/server.js" + }, + "scripts": { + "build": "npm run copy-bin-resources; npm run chmod; tsc --build tsconfig-build.json", + "build-watch": "npm run copy-bin-resources; npm run chmod; tsc --build tsconfig-build.json -w", + "start": "node lib/main.js", + "copy-bin-resources": "mkdir -p lib; cp -r bin resources lib", + "chmod": "touch lib/main.js lib/server.js; chmod +x lib/main.js lib/server.js bin/node", + "tests-install": "tests/install.sh", + "test": "jest --selectProjects default", + "differential": "jest --selectProjects differential", + "pkg": "npm run clean; npm run build; pkg lib/main.js -C Brotli --options \"expose-gc,max-old-space-size=8192\" -c package.json --out-path dist; pkg lib/server.js -C Brotli --options \"expose-gc,max-old-space-size=8192\" -c package.json -o dist/jelly-server", + "clean": "rm -rf lib dist tmp/tsbuildinfo tmp/coverage tests/node_modules/jelly-previous", + "build-docker": "npm run build && docker build -t jelly .", + "jelly-docker": "./bin/jelly-docker" + }, + "dependencies": { + "@babel/core": "^7.20.12", + "@babel/parser": "^7.20.15", + "@babel/plugin-proposal-decorators": "^7.20.13", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typescript": "^7.20.13", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7", + "@types/semver": "^7.3.13", + "commander": "^9.4.1", + "micromatch": "^4.0.5", + "semver": "^7.3.8", + "stringify2stream": "^1.1.0", + "typescript": "^4.9.5", + "winston": "^3.7.2" + }, + "devDependencies": { + "@types/babel__core": "^7.20.0", + "@types/babel__traverse": "^7.18.3", + "@types/jest": "^28.1.8", + "@types/micromatch": "^4.0.2", + "@types/node": "^18.13.0", + "jest": "^28.1.3", + "jest-expect-message": "^1.1.3", + "ts-jest": "^28.0.7", + "ts-node": "^10.9.1" + }, + "pkg": { + "targets": [ + "node16-linux-x64", + "node16-macos-x64", + "node16-win-x64" + ], + "assets": "resources/**" + } +} diff --git a/resources/visualizer.html b/resources/visualizer.html new file mode 100644 index 0000000..89784e7 --- /dev/null +++ b/resources/visualizer.html @@ -0,0 +1,717 @@ + + + + Jelly + + + + + + + + + + + + + + + +
+

Jelly visualizer

+
+
+
+ + + + + +
+
+
+ + +
+
+
+ +
+
+
+ + +
+ +
+ +
+ + + +
+ +
+ +
+
+
+ +
+
+ +
+
+
+ + diff --git a/src/analysis/accesspaths.ts b/src/analysis/accesspaths.ts new file mode 100644 index 0000000..7feb6c7 --- /dev/null +++ b/src/analysis/accesspaths.ts @@ -0,0 +1,97 @@ +import {DummyModuleInfo, ModuleInfo} from "./infos"; +import {ConstraintVar} from "./constraintvars"; + +/** + * Access paths used for describing package/module interfaces. + */ +export abstract class AccessPath { + + private readonly str: string; + + protected constructor(str: string) { + this.str = str; + } + + toString(): string { + return this.str; + } +} + +/** + * Access path that represents module.exports values (for exports interfaces) or require("...") values (for imports interfaces). + */ +export class ModuleAccessPath extends AccessPath { + + readonly moduleInfo: ModuleInfo | DummyModuleInfo; + + readonly requireName: string | undefined; + + constructor(moduleInfo: ModuleInfo | DummyModuleInfo, requireName: string) { + const t = !"./#".includes(requireName[0]) && requireName !== moduleInfo.getOfficialName() ? requireName : undefined; // only use require name if not relative and different from official name + super(`<${moduleInfo.getOfficialName()}${t ? `(${t})` : ""}>`); + this.moduleInfo = moduleInfo; + this.requireName = t; + } +} + +/** + * Access path that represents an object property. + */ +export class PropertyAccessPath extends AccessPath { + + readonly base: ConstraintVar; + + readonly prop: string; + + constructor(base: ConstraintVar, prop: string) { + super(`${base}.${prop}`); + this.base = base; + this.prop = prop; + } +} + +/** + * Access path that represents the result of a function call (possibly with 'new'). + */ +export class CallResultAccessPath extends AccessPath { + + readonly caller: ConstraintVar; + + constructor(caller: ConstraintVar) { + super(`${caller}()`); + this.caller = caller; + } +} + +/** + * Access path that represents the result of a JSX component instantiation. + */ +export class ComponentAccessPath extends AccessPath { + + readonly component: ConstraintVar; + + constructor(component: ConstraintVar) { + super(`${component}<>`); + this.component = component; + } +} + +/** + * Access path that represents values from ignored modules. + */ +export class IgnoredAccessPath extends AccessPath { + + constructor() { + super("Ignored"); + } +} + +/** + * Access path that represents unknown values. + */ +export class UnknownAccessPath extends AccessPath { + + constructor() { + super("Unknown"); + } +} diff --git a/src/analysis/analysisstate.ts b/src/analysis/analysisstate.ts new file mode 100644 index 0000000..d34c5b5 --- /dev/null +++ b/src/analysis/analysisstate.ts @@ -0,0 +1,686 @@ +import { + CallExpression, + Class, + Function, + identifier, + Identifier, + isArrowFunctionExpression, + isExpression, + isNode, + NewExpression, + Node, + OptionalCallExpression, + SourceLocation +} from "@babel/types"; +import { + FilePath, + getOrSet, + mapGetSet, + sourceLocationToString, + sourceLocationToStringWithFile, + sourceLocationToStringWithFileAndEnd, + SourceLocationWithFilename, + strHash +} from "../misc/util"; +import {ConstraintVar, NodeVar} from "./constraintvars"; +import logger from "../misc/logger"; +import {ObjectToken, PackageObjectToken, Token} from "./tokens"; +import {dirname, relative, resolve} from "path"; +import {NodePath} from "@babel/traverse"; +import assert from "assert"; +import {getPackageJsonInfo, PackageJsonInfo} from "../misc/packagejson"; +import {DummyModuleInfo, FunctionInfo, ModuleInfo, PackageInfo} from "./infos"; +import {options} from "../options"; +import { + AccessPath, + CallResultAccessPath, + ComponentAccessPath, + ModuleAccessPath, + PropertyAccessPath +} from "./accesspaths"; +import {ConstraintVarProducer} from "./constraintvarproducer"; +import Timer from "../misc/timer"; +import {VulnerabilityDetector} from "../patternmatching/vulnerabilitydetector"; + +export const globalLoc: SourceLocation = {start: {line: 0, column: 0}, end: {line: 0, column: 0}}; + +export const undefinedIdentifier = identifier("undefined"); // TODO: prevent writes to 'undefined'? +undefinedIdentifier.loc = globalLoc; + +export class AnalysisState { // TODO: move some of these fields to FragmentState? + + /** + * Map from constraint variable string hash to canonical ConstraintVar object. + */ + readonly canonicalConstraintVars: Map = new Map; + + /** + * Map from AST node to canonical NodeVar object. + */ + readonly canonicalNodeVars: Map = new Map; + + /** + * Map from token string hash to canonical Token object. + */ + readonly canonicalTokens: Map = new Map; + + /** + * Map from access path string hash to canonical AccessPath object. + */ + readonly canonicalAccessPaths: Map = new Map; + + /** + * Map that provides for each function/module the set of modules being required. + */ + readonly requireGraph: Map> = new Map; + + /** + * Map that provides for each function/module the set of functions that may be called. + */ + readonly functionToFunction: Map> = new Map; + + /** + * Map that provides for each call site location the set of functions that may be called. (For output only.) + */ + readonly callToFunction: Map> = new Map; // TODO: redundant? see callToFunctionOrModule + + /** + * Map from call/require/import node to functions/modules that may be called/imported. + */ + readonly callToFunctionOrModule: Map> = new Map; + + readonly callToContainingFunction: Map = new Map; + + /** + * Map from require/import call to the set of modules being required. (For output only.) + */ + readonly callToModule: Map> = new Map; // TODO: redundant? see callToFunctionOrModule + + /** + * Total number of function->function call graph edges. (For statistics only.) + */ + numberOfFunctionToFunctionEdges: number = 0; + + /** + * Total number of call->function call graph edges. (For statistics only.) + */ + numberOfCallToFunctionEdges: number = 0; + + /** + * Map from "name@version" to package info, "
" is used for entry files if no package.json is found. + */ + readonly packageInfos: Map = new Map; + + /** + * Map from normalized module path to ModuleInfo. + * Note: different paths may refer to the same ModuleInfo! (if they belong to the same package and version) + */ + readonly moduleInfos: Map = new Map; + + /** + * Set of DummyModuleInfos created (for module files that haven't been found). + */ + readonly dummyModuleInfos: Map = new Map; + + /** + * Map from Function AST object to FunctionInfo. + */ + readonly functionInfos: Map = new Map; + + /** + * Functions that use 'arguments'. + */ + readonly functionsWithArguments: Set = new Set; + + /** + * Functions that use 'this'. + */ + readonly functionsWithThis: Set = new Set; + + /** + * Source code locations that correspond to the start of artificial functions in dyn.ts. + * Such functions are ignored during soundness testing. + */ + readonly artificialFunctions: Array<[ModuleInfo, Node]> = []; + + /** + * Call nodes for each module. (Only used with options.callgraphJson.) + */ + readonly calls: Map> = new Map; + + /** + * Source locations of all calls (including accessor calls). + */ + readonly callLocations: Set = new Set; + + /** + * Source locations of calls to/from known native functions. + */ + readonly nativeCallLocations: Set = new Set; + + /** + * Source locations of calls to external functions. + */ + readonly externalCallLocations: Set = new Set; + + /** + * Calls with unused result. + * Used by PatternMatcher. + */ + readonly callsWithUnusedResult: Set = new Set; + + /** + * Calls where the result may be used as a promise. + * Used by PatternMatcher. + */ + readonly callsWithResultMaybeUsedAsPromise: Set = new Set; + + /** + * Constraint variables that represent function parameters. + */ + readonly functionParameters: Map> = new Map; + + /** + * Expressions that are invoked at calls. + * Used by PatternMatcher. + */ + readonly invokedExpressions: Set = new Set; + + /** + * Constraint variables that represent expressions whose values may escape to other modules. + * Includes arguments to functions from other modules. + */ + readonly maybeEscaping: Set = new Set; + + /** + * Object tokens that have been widened. + */ + readonly widened: Set = new Set; + + /** + * Unhandled dynamic property write operations. + */ + readonly unhandledDynamicPropertyWrites: Map = new Map; + + /** + * Unhandled dynamic property read operations. + */ + readonly unhandledDynamicPropertyReads: Set = new Set; + + /** + * Number of errors. (For statistics only.) + */ + errors: number = 0; + + /** + * Number of warnings. (For statistics only.) + */ + warnings: number = 0; + + /** + * AST nodes where a warning has been emitted. + */ + readonly nodesWithWarning: WeakSet = new WeakSet; + + /** + * Entry files. + */ + readonly entryFiles: Set = new Set; + + /** + * Files reached during analysis. + */ + readonly reachedFiles: Set = new Set; + + /** + * Files reached and waiting to be analyzed. + */ + readonly pendingFiles: Array = []; + + /** + * Files that could not be parsed. + */ + readonly filesWithParseErrors: Array = []; + + /** + * Files that have been analyzed (without parse error). + */ + readonly filesAnalyzed: Array = []; + + /** + * 'require' expressions and import/export declarations/expressions where ModuleAccessPaths are created. + * Used by PatternMatcher. + */ + readonly moduleAccessPaths: Map> = new Map; + + /** + * Property read expressions where PropertyAccessPaths are created. + * ap |-> prop |-> n |-> {bp, sub} means that ap appears at the base sub-expression of the property read expression n with + * property prop, bp is the access path for n, and sub is the constraint variable of the sub-expression. + * Used by PatternMatcher. + */ + readonly propertyReadAccessPaths: Map>> = new Map; + + /** + * Property write expressions where PropertyAccessPaths are created. + * ap |-> prop |-> n |-> {bp, sub} means that ap appears at the base sub-expression of the property write expression n with + * property prop, bp is the access path for n, and sub is the constraint variable of the sub-expression. + * Used by PatternMatcher. + */ + readonly propertyWriteAccessPaths: Map>> = new Map; + + /** + * Expressions and associated CallResultAccessPaths where CallResultAccessPaths are created. + * ap |-> n |-> {bp, sub} means that ap appears at the function sub-expression of the call expression n, + * bp is the access path for n, and sub is the constraint variable of the sub-expression. + * Used by PatternMatcher. + */ + readonly callResultAccessPaths: Map> = new Map; + + /** + * Expressions and associated ComponentAccessPaths where ComponentAccessPaths are created. + * ap |-> n |-> {bp, sub} means that ap appears at the function sub-expression of the component creation expression n, + * bp is the access path for n, and sub is the constraint variable of the sub-expression. + * Used by PatternMatcher. + */ + readonly componentAccessPaths: Map> = new Map; + + /** + * Map from identifier declarations at imports to uses. + * Used by PatternMatcher. + */ + readonly importDeclRefs: Map> = new Map; + + /** + * Constraint variable producer. + */ + readonly varProducer = new ConstraintVarProducer(this); + + /** + * Timeout timer. + */ + readonly timeoutTimer = new Timer; + + /** + * Cache of PackageJsonInfos. + */ + readonly packageJsonInfos = new Map(); + + /** + * Property reads that may have empty result. + */ + maybeEmptyPropertyReads: Array<{result: ConstraintVar, base: ConstraintVar, pck: PackageObjectToken}> = []; + + /** + * Dynamic property writes. + */ + dynamicPropertyWrites = new Set(); + + /** + * Number of calls to canonicalizeVar. + */ + numberOfCanonicalizeVarCalls = 0; + + /** + * Number of calls to canonicalizeToken. + */ + numberOfCanonicalizeTokenCalls = 0; + + /** + * Number of calls to canonicalizeAccessPath. + */ + numberOfCanonicalizeAccessPathCalls = 0; + + /** + * Dynamic analysis time. + */ + dynamicAnalysisTime: number = 0; + + /** + * Vulnerability information, only used if options.vulnerabilities is set. + */ + vulnerabilities: VulnerabilityDetector | undefined; + + /** + * Returns the canonical representative of the given constraint variable (possibly the given one). + */ + canonicalizeVar(v: T): T { + this.numberOfCanonicalizeVarCalls++; + if (v instanceof NodeVar) + return getOrSet(this.canonicalNodeVars, v.node, () => v) as unknown as T; + else + return getOrSet(this.canonicalConstraintVars, strHash(v.toString()), () => v) as T; + } + + /** + * Returns the canonical representative of the given token (possibly the given one). + */ + canonicalizeToken(t: T): T { + this.numberOfCanonicalizeTokenCalls++; + return getOrSet(this.canonicalTokens, strHash(t.toString()), () => t) as T; + } + + /** + * Returns the canonical representative of the given access path (possibly the given one). + */ + canonicalizeAccessPath(t: T): T { + this.numberOfCanonicalizeAccessPathCalls++; + return getOrSet(this.canonicalAccessPaths, strHash(t.toString()), () => t) as T; + } + + /** + * Adds an edge in the call graph (both function->function and call->function). + */ + registerCallEdge(call: Node, from: FunctionInfo | ModuleInfo, to: FunctionInfo, + {native, accessor, external}: {native?: boolean, accessor?: boolean, external?: boolean} = {}) { + if ((!accessor || options.callgraphImplicit) && + (!native || options.callgraphNative) && + (!external || options.callgraphExternal)) { + // register function->function + let fs = mapGetSet(this.functionToFunction, from); + if (!fs.has(to)) + this.numberOfFunctionToFunctionEdges++; + fs.add(to); + // register call->function + let cs = mapGetSet(this.callToFunction, call); + if (!cs.has(to)) { + this.numberOfCallToFunctionEdges++; + if (logger.isVerboseEnabled()) + logger.verbose(`Adding call edge from call ${sourceLocationToStringWithFileAndEnd(call.loc)}, function ${from} -> ${to}`); + } + cs.add(to); + } + // register call->function/module + mapGetSet(this.callToFunctionOrModule, call).add(to); + this.callToContainingFunction.set(call, from); + } + + /** + * Registers a call location. + */ + registerCall(n: Node, m: ModuleInfo, {native, external, accessor}: {native?: boolean, external?: boolean, accessor?: boolean} = {}) { + if (accessor && !options.callgraphImplicit) + return; + if (options.callgraphJson) + mapGetSet(this.calls, m).add(n); + if (!this.callLocations.has(n) || + (native && !this.nativeCallLocations.has(n)) || + (external && !this.externalCallLocations.has(n))) { + if (logger.isDebugEnabled()) + logger.debug(`Adding ${native ? "native " : external ? "external " : accessor ? "accessor " : ""}call ${sourceLocationToStringWithFileAndEnd(n.loc!)}`); + this.callLocations.add(n); + if (native) + this.nativeCallLocations.add(n); + else if (external) + this.externalCallLocations.add(n); + } + } + + /** + * Registers a require/import call. + */ + registerRequireCall(node: Node, from: ModuleInfo | FunctionInfo, m: ModuleInfo | DummyModuleInfo) { + if (options.callgraphRequire) + mapGetSet(this.callToModule, node).add(m); + mapGetSet(this.callToFunctionOrModule, node).add(m); + this.callToContainingFunction.set(node, from); + } + + /** + * Registers a call node whose result is unused. + */ + registerCallWithUnusedResult(n: CallExpression | OptionalCallExpression | NewExpression) { + this.callsWithUnusedResult.add(n); + } + + /** + * Registers a call node whose result may be used as a promise. + */ + registerCallWithResultMaybeUsedAsPromise(n: CallExpression | OptionalCallExpression | NewExpression) { + this.callsWithResultMaybeUsedAsPromise.add(n); + } + + /** + * Registers a constraint variable that represents a function parameter. + */ + registerFunctionParameter(v: ConstraintVar, fun: Function) { + mapGetSet(this.functionParameters, fun).add(v); + } + + /** + * Registers that values of the expression represented by the given constraint variable may escape to other modules. + */ + registerEscaping(v: ConstraintVar | undefined) { + if (v) + this.maybeEscaping.add(v); + } + + /** + * Registers a call to another module. + */ + registerEscapingArguments(args: CallExpression["arguments"], path: NodePath) { + for (const arg of args) + if (isExpression(arg)) // TODO: handle non-Expression arguments? + this.registerEscaping(this.varProducer.expVar(arg, path)); + } + + /** + * Registers an expression that is invoked at a call. + */ + registerInvokedExpression(n: Node) { + this.invokedExpressions.add(n); + } + + /** + * Registers a function that may be ignored in output from dyn.ts. + */ + registerArtificialFunction(m: ModuleInfo, n: Node) { + this.artificialFunctions.push([m, n]); + } + + /** + * Registers an unhandled dynamic property write operation. + */ + registerUnhandledDynamicPropertyWrite(node: Node, src: ConstraintVar, source: string | undefined) { + this.unhandledDynamicPropertyWrites.set(node, {src, source}); + } + + /** + * Registers an unhandled dynamic property read operation. + */ + registerUnhandledDynamicPropertyRead(node: Node) { + this.unhandledDynamicPropertyReads.add(node); + } + + /** + * Emits an error message. + */ + error(msg: string) { + logger.error(`Error: ${msg}`); + this.errors++; + } + + /** + * Emits a warning message. + */ + warn(msg: string) { + logger.warn(`Warning: ${msg}`); + this.warnings++; + } + + /** + * Emits a warning message about an unsupported language feature or library function. + * If avoidDuplicates is set, at most one warning is generated per node. + */ + warnUnsupported(node: Node, msg: string = node.type, avoidDuplicates: boolean = false) { + if (avoidDuplicates) { + if (this.nodesWithWarning.has(node)) + return; + this.nodesWithWarning.add(node); + } + if (options.warningsUnsupported) + this.warn(`${msg} at ${sourceLocationToStringWithFile(node.loc)}`); + else + this.warnings++; + } + + /** + * Returns the ModuleInfo of the given module. + */ + getModuleInfo(file: FilePath): ModuleInfo { + const m = this.moduleInfos.get(file); + if (!m) + assert.fail(`ModuleInfo for ${file} not found`); + return m; + } + + /** + * Registers a new FunctionInfo for a function/method/constructor. + */ + registerFunctionInfo(file: FilePath, path: NodePath, name: string | undefined, fun: Function, loc: SourceLocation | null | undefined) { + const m = this.moduleInfos.get(file)!; + const f = new FunctionInfo(name, loc, m); + this.functionInfos.set(fun, f); + const parent = path.getFunctionParent()?.node; + (parent ? this.functionInfos.get(parent)!.functions : m.functions).set(fun, f); + if (this.vulnerabilities) + this.vulnerabilities.reachedFunction(path, f); + } + + /** + * Registers that the current function uses 'arguments'. + * Returns the enclosing (non-arrow) function, or undefined if no such function. + */ + registerArguments(path: NodePath): Function | undefined { + let p: NodePath | NodePath | null | undefined = path, f: Function | undefined; + do { + f = (p = p?.getFunctionParent())?.node; + } while (f && isArrowFunctionExpression(f)); + if (f) { + this.functionsWithArguments.add(f); + if (logger.isDebugEnabled()) + logger.debug(`Function uses 'arguments': ${sourceLocationToStringWithFile(f.loc)}`); + } + return f; + } + + /** + * Registers that the current function uses 'this'. + */ + registerThis(path: NodePath): Function | undefined { + let p: NodePath | NodePath | null | undefined = path, f: Function | undefined; + do { + f = (p = p?.getFunctionParent())?.node; + } while (f && isArrowFunctionExpression(f)); + if (f) { + this.functionsWithThis.add(f); + if (logger.isDebugEnabled()) + logger.debug(`Function uses 'this': ${sourceLocationToStringWithFile(f.loc)}`); + } + return f; + } + + /** + * Finds the nearest enclosing function or module. + */ + getEnclosingFunctionOrModule(path: NodePath, moduleInfo: ModuleInfo): FunctionInfo | ModuleInfo { + const p = path.getFunctionParent()?.node; + const caller = p ? this.functionInfos.get(p)! : moduleInfo; + if (!caller) + assert.fail(`Function/module info not found at ${moduleInfo}:${sourceLocationToString(path.node.loc)}!?!`); + return caller; + } + + /** + * Records that the given file has been reached, and returns its ModuleInfo. + */ + reachedFile(tofile: FilePath, from?: Function | FilePath): ModuleInfo { + let moduleInfo; + if (this.reachedFiles.has(tofile)) + moduleInfo = this.moduleInfos.get(tofile)!; + else { + + // find package.json and extract name, version, and main file + const p = getOrSet(this.packageJsonInfos, dirname(tofile), () => getPackageJsonInfo(tofile)); + const rel = relative(p.dir, tofile); + + // find or create PackageInfo + let packageInfo = this.packageInfos.get(p.packagekey); + let otherfile: string | undefined; + if (!packageInfo) { + + // package has not been reached before (also not in another directory) + packageInfo = new PackageInfo(p.name, p.version, p.main, p.dir, from === undefined); + this.packageInfos.set(p.packagekey, packageInfo); + if (!options.modulesOnly && options.printProgress && logger.isVerboseEnabled()) + logger.verbose(`Reached package ${packageInfo} at ${p.dir}`); + if (this.vulnerabilities) + this.vulnerabilities.reachedPackage(packageInfo); + + } else { + + // package has been reached before, but maybe in another directory, so look for ModuleInfo there + otherfile = resolve(packageInfo.dir, rel); + moduleInfo = this.moduleInfos.get(otherfile); + } + + if (moduleInfo) { + + // modules has been reached before in another directory + if (logger.isVerboseEnabled()) + logger.verbose(`${moduleInfo} already encountered in another directory`); + } else { + + // module has not been reached before, create new ModuleInfo + moduleInfo = new ModuleInfo(rel, packageInfo, tofile, from === undefined); + packageInfo.modules.set(rel, moduleInfo); + + // record that module has been reached + this.reachedFiles.add(tofile); + if (from && options.ignoreDependencies) + logger.info(`Ignoring module ${moduleInfo}`); + else + this.pendingFiles.push(tofile); + if (this.vulnerabilities) + this.vulnerabilities.reachedModule(moduleInfo); + + // if the package was reached before in another directory, record the ModuleInfo for the file in that directory + if (otherfile) + this.moduleInfos.set(otherfile, moduleInfo); + } + + // record the ModuleInfo for the given file + this.moduleInfos.set(tofile, moduleInfo); + } + + // unless this is an entry file... + if (from) { + + // extend the require graph + const fr = typeof from === "string" ? this.moduleInfos.get(from)! : this.functionInfos.get(from)!; + const to = this.moduleInfos.get(tofile)!; + mapGetSet(this.requireGraph, fr).add(to); + + // extend the package dependencies and dependents + const pf = fr.packageInfo; + const pt = to.packageInfo; + if (pf !== pt && !pf.directDependencies.has(pt)) { + pf.directDependencies.add(pt); + if (logger.isVerboseEnabled()) + logger.verbose(`Package ${pf} depends on ${pt}`); + } + } + return moduleInfo; + } + + /** + * Finds the module or package that the given constraint variable belongs to, + * returns undefined for constraint variables that do not belong to a specific package. + */ + getConstraintVarParent(v: ConstraintVar): PackageInfo | ModuleInfo | undefined { + const p = v.getParent(); + if (isNode(p) || (p && "loc" in p && p.loc)) + return this.moduleInfos.get((p.loc as SourceLocationWithFilename).filename)!; + return undefined; + } +} diff --git a/src/analysis/analyzer.ts b/src/analysis/analyzer.ts new file mode 100644 index 0000000..2a2b39e --- /dev/null +++ b/src/analysis/analyzer.ts @@ -0,0 +1,320 @@ +import fs from "fs"; +import {resolve} from "path"; +import logger, {writeStdOutIfActive} from "../misc/logger"; +import Solver, {AbortedException} from "./solver"; +import Timer, {TimeoutException} from "../misc/timer"; +import {addAll, FilePath, mapMapSize, percent} from "../misc/util"; +import {visit} from "./astvisitor"; +import {PackageInfo} from "./infos"; +import {options, resolveBaseDir} from "../options"; +import assert from "assert"; +import {getComponents, nuutila} from "../misc/scc"; +import {widenObjects} from "./widening"; +import {findModules} from "./modulefinder"; +import {parseAndDesugar} from "../parsing/parser"; +import {findEscapingObjects} from "./escaping"; +import {buildNatives} from "../natives/nativebuilder"; +import {AnalysisStateReporter} from "../output/analysisstatereporter"; +import {Operations} from "./operations"; +import {Program} from "@babel/types"; +import {UnknownAccessPath} from "./accesspaths"; +import {Token} from "./tokens"; + +export async function analyzeFiles(files: Array, solver: Solver, returnFileMap: boolean = false): Promise> { + const a = solver.analysisState; + const timer = new Timer(); + resolveBaseDir(); + const fileMap = new Map(); + try { + if (files.length === 0) + logger.info("Error: No files to analyze"); + else { + + // resolve entry files relative to basedir + for (const file of files) + a.entryFiles.add(resolve(options.basedir, file)); // TODO: optionally resolve using require.resolve instead? + + // analyze files reachable from the entry files top-down + for (const file of a.entryFiles) + a.reachedFile(file); + while (a.pendingFiles.length > 0) { + const file = a.pendingFiles.shift()!; + const moduleInfo = a.getModuleInfo(file); + + solver.diagnostics.modules++; + if (!options.modulesOnly && options.printProgress) + logger.info(`Analyzing module ${file} (${solver.diagnostics.modules})`); + + writeStdOutIfActive(`Parsing ${file}...`); + const str = fs.readFileSync(file, "utf8"); // TODO: OK to assume utf8? (ECMAScript says utf16??) + solver.diagnostics.codeSize += str.length; + const ast = parseAndDesugar(str, file, a); + if (!ast) { + a.filesWithParseErrors.push(file); + continue; + } + if (returnFileMap) + fileMap.set(file, {sourceCode: str, program: ast.program}) + + if (options.modulesOnly) { + + // find modules only, no actual analysis + findModules(ast, file, solver); + + } else { + + // initialize analysis state for the module + solver.prepare(); + + // prepare model of native library + const [globals, specials] = buildNatives(solver, moduleInfo); + + // traverse the AST + writeStdOutIfActive("Traversing AST..."); + visit(ast, new Operations(file, solver, globals, specials)); + + // propagate tokens until fixpoint reached for the module + await solver.propagate(); + + // find escaping objects and add UnknownAccessPaths + const escaping = findEscapingObjects(moduleInfo, solver); + + // if enabled, widen escaping objects for this module + if (options.alloc && options.widening) + widenObjects(moduleInfo, escaping, solver); + + // propagate tokens (again) until fixpoint reached + await solver.propagate(); + + // store the analysis state for the module + solver.store(moduleInfo); + + a.filesAnalyzed.push(file); + solver.updateDiagnostics(); + } + } + + if (!options.modulesOnly) { + + if (!options.bottomUp) { + + // combine analysis states for all modules + solver.prepare(); + const totalNumPackages = Array.from(a.packageInfos.values()).reduce((acc, p) => + acc + (Array.from(p.modules.values()).some(m => m.fragmentState) ? 1 : 0), 0); + for (const p of a.packageInfos.values()) { + await solver.checkAbort(); + + // skip the package if it doesn't contain any modules that have been analyzed + if (!Array.from(p.modules.values()).some(m => m.fragmentState)) + continue; + + solver.diagnostics.packages++; + if (options.printProgress) + logger.info(`Analyzing package ${p} (${solver.diagnostics.packages}/${totalNumPackages})`); + + // restore analysis state for each module + for (const m of p.modules.values()) + solver.restore(m); + + // connect neighbors + for (const d of p.directDependencies) + solver.addPackageNeighbor(p, d); + } + + // propagate tokens until fixpoint reached + await solver.propagate(); + + await patchDynamics(solver); + + } else { + + // compute strongly connected components from the package dependency graph + if (logger.isVerboseEnabled()) + for (const p of a.packageInfos.values()) { + logger.verbose(`Package ${p} dependencies:${p.directDependencies.size === 0 ? " -" : ""}`); + for (const d of p.directDependencies) + logger.verbose(` ${d}`); + } + const components = getComponents(nuutila(a.packageInfos.values(), (p: PackageInfo) => p.directDependencies)); + + // combine local analysis results bottom-up in the package structure + const transdeps = new Map>(); // direct and transitive dependencies in other components + const totalNumPackages = components.reduce((acc, scc) => + acc + scc.reduce((acc, p) => + acc + (Array.from(p.modules.values()).some(m => m.fragmentState) ? 1 : 0), 0), 0); + for (const scc of components) { + + // skip the component if it doesn't contain any modules that have been analyzed + if (!(Array.from(scc).some(p => Array.from(p.modules.values()).some(m => m.fragmentState)))) + continue; + + solver.diagnostics.packages += scc.length; + if (options.printProgress) + if (scc.length === 1) + logger.info(`Analyzing package ${scc[0]} (${solver.diagnostics.packages}/${totalNumPackages})`); + else + logger.info(`Analyzing mutually dependent packages ${scc.join(", ")}`); + + // compute solution for the strongly connected component + solver.prepare(); + const deps = new Set(); // direct dependencies in other components + const trans = new Set(); // transitive dependencies in other components + for (const p of scc) { + + // restore the tokens of the modules of the packages in the component + for (const m of p.modules.values()) + solver.restore(m); + + // collect dependencies in other components + const pd = new Set(); + for (const d of p.directDependencies) + if (!scc.includes(d)) { + pd.add(d); + const td = transdeps.get(d); + if (td) + for (const dd of td) { + pd.add(dd); + trans.add(dd); + } + } + transdeps.set(p, pd); + + // collect direct dependencies in other components and connect neighbors + for (const d of p.directDependencies) { + if (!scc.includes(d)) + deps.add(d); + solver.addPackageNeighbor(p, d); + } + } + // restore tokens from the direct dependencies in other components + for (const d of deps) + if (!trans.has(d)) // transitive dependencies can safely be skipped + solver.restore(d); + + // propagate tokens until fixpoint reached for the scc packages with their dependencies + await solver.propagate(); + + await patchDynamics(solver); + + // store the tokens for the packages in the component + for (const p of scc) + solver.store(p); + // TODO: persist package(/module?) tokens, seed when analyzing the package/module again (note: modules are only analyzed when reached) + + assert(a.pendingFiles.length === 0, "Unexpected module"); // (new modules shouldn't be discovered in the bottom-up phase) + } + + // restore all tokens (safe to restore for one package of each component and to skip the last component) + let lastComponent = true; + for (const scc of components.reverse()) { + if (lastComponent) { + lastComponent = false; + continue; + } + solver.restore(scc[0], false); + } + } + + solver.updateDiagnostics(); + } + } + + } catch (ex) { + solver.updateDiagnostics(); + if (ex instanceof TimeoutException) { + solver.diagnostics.timeout = true; + } else if (ex instanceof AbortedException) { + solver.diagnostics.aborted = true; + } else + throw ex; + } + solver.diagnostics.time = timer.elapsed(); + solver.diagnostics.cpuTime = timer.elapsedCPU(); + if (solver.diagnostics.aborted) + logger.warn("Received abort signal, analysis aborted"); + else if (solver.diagnostics.timeout) + logger.warn("Time limit reached, analysis aborted"); + + // output statistics + if (!options.modulesOnly && logger.isInfoEnabled() && files.length > 0) { + const f = solver.fragmentState; // current fragment (not final if aborted due to timeout) + const r = new AnalysisStateReporter(a, f); + if (options.warningsUnsupported && !solver.diagnostics.aborted && !solver.diagnostics.timeout) { + r.reportNonemptyUnhandledDynamicPropertyWrites(); + r.reportNonemptyUnhandledDynamicPropertyReads(); + } + logger.info(`Analyzed packages: ${solver.diagnostics.packages}, modules: ${solver.diagnostics.modules}, functions: ${a.functionInfos.size}, code size: ${Math.round(solver.diagnostics.codeSize / 1024)}KB`); + const one = r.getOneCalleeCalls(), zero = r.getZeroCalleeCalls().size, native = r.getZeroButNativeCalleeCalls(), external = r.getZeroButExternalCalleeCalls(), nativeOrExternal = r.getZeroButNativeOrExternalCalleeCalls(), all = a.callLocations.size; + logger.info(`Call edges function->function: ${a.numberOfFunctionToFunctionEdges}, call->function: ${a.numberOfCallToFunctionEdges}`); + logger.info(`Calls with unique callee: ${one}/${all - zero - native - external - nativeOrExternal}${all - zero - native - external - nativeOrExternal > 0 ? ` (${percent(one / (all - zero - native - external - nativeOrExternal))})` : ""}` + + ` (excluding ${zero} zero-callee, ${native} native-only, ${external} external-only and ${nativeOrExternal} native-or-external-only)`) + logger.info(`Functions with zero callers: ${r.getZeroCallerFunctions().size}/${a.functionInfos.size}`); + logger.info(`Analysis time: ${solver.diagnostics.time}ms, memory usage: ${solver.diagnostics.maxMemoryUsage}MB${!options.gc ? " (without --gc)" : ""}`); + logger.info(`Analysis errors: ${a.errors}, warnings: ${a.warnings}${a.warnings > 0 && !options.warningsUnsupported ? " (show with --warnings-unsupported)" : ""}`); + if (options.diagnostics) { + logger.info(`Iterations: ${solver.diagnostics.iterations}, listener notification rounds: ${solver.listenerNotificationRounds}`); + if (options.maxRounds !== undefined) + logger.info(`Fixpoint round limit reached: ${solver.roundLimitReached} time${solver.roundLimitReached !== 1 ? "s" : ""}`); + logger.info(`Constraint vars: ${f.getNumberOfVarsWithTokens()} (${f.vars.size}), tokens: ${f.numberOfTokens}, subset edges: ${f.numberOfSubsetEdges}, max tokens: ${solver.largestTokenSetSize}, max subset out: ${solver.largestSubsetEdgeOutDegree}`); + logger.info(`Listeners (notifications) normal: ${mapMapSize(f.tokenListeners)} (${solver.tokenListenerNotifications}), ` + + `pair: ${mapMapSize(f.pairListeners1) + mapMapSize(f.pairListeners2)} (${solver.pairListenerNotifications}), ` + + `neighbor: ${mapMapSize(f.packageNeighborListeners)} (${solver.packageNeighborListenerNotifications}), ` + + `inheritance: ${mapMapSize(f.ancestorListeners)} (${solver.ancestorListenerNotifications}), ` + + `array: ${mapMapSize(f.arrayEntriesListeners)} (${solver.arrayEntriesListenerNotifications}), ` + + `obj: ${mapMapSize(f.objectPropertiesListeners)} (${solver.objectPropertiesListenerNotifications})`); + logger.info(`Canonicalize vars: ${a.canonicalConstraintVars.size} (${a.numberOfCanonicalizeVarCalls}), tokens: ${a.canonicalTokens.size} (${a.numberOfCanonicalizeTokenCalls}), access paths: ${a.canonicalAccessPaths.size} (${a.numberOfCanonicalizeAccessPathCalls})`); + logger.info(`CPU time: ${solver.diagnostics.cpuTime}ms, propagation: ${solver.totalPropagationTime}ms, listeners: ${solver.totalListenerCallTime}` + + `ms${options.alloc && options.widening ? `, widening: ${solver.totalWideningTime}ms` : ""}`); + if (options.cycleElimination) + logger.info(`Cycle elimination time: ${solver.totalCycleEliminationTime}ms, runs: ${solver.totalCycleEliminationRuns}, nodes removed: ${f.redirections.size}`); + } + } + + return fileMap; +} + +/** + * Patches empty object property constraint variables that may be affected by dynamic property writes. + */ +async function patchDynamics(solver: Solver) { + if (!options.patchDynamics) + return; + const a = solver.analysisState; + const f = solver.fragmentState; + const dyns = new Set(); + for (const v of a.dynamicPropertyWrites) + addAll(f.getTokens(f.getRepresentative(v)), dyns); + // constraint: for all E.p (or E[..]) where ⟦E.p⟧ (or ⟦E[..]⟧) is empty and ⟦E⟧ contains a token that is base of a dynamic property write + let count = 0; + const r: typeof a.maybeEmptyPropertyReads = []; + for (const e of a.maybeEmptyPropertyReads) { + const {result, base, pck} = e; + const bs = f.getTokens(f.getRepresentative(base)); + const [size] = f.getTokensSize(f.getRepresentative(result)); + if (size === 0) { + let dpw = false; + for (const t of bs) + if (dyns.has(t)) { + dpw = true; // base has a token that is base of a dynamic property write + break; + } + if (dpw) { + if (logger.isDebugEnabled()) + logger.debug(`Empty object property read ${result} with dynamic write to base object ${base}`); + + // constraint: ...: @Unknown ∈ ⟦E⟧ and k ∈ ⟦E.p⟧ (or ⟦E[..]⟧) where k is the package containing the property read operation + solver.addAccessPath(a.canonicalizeAccessPath(new UnknownAccessPath()), base); + solver.addTokenConstraint(pck, result); // TODO: omit? + count++; + } else + r.push(e); // keep only the property reads that are still empty + } + } + a.maybeEmptyPropertyReads = r; + if (count > 0) { + if (logger.isVerboseEnabled()) + logger.verbose(`${count} empty object property read${count === 1 ? "" : "s"} patched, propagating again`); + await solver.propagate(); + } +} diff --git a/src/analysis/astvisitor.ts b/src/analysis/astvisitor.ts new file mode 100644 index 0000000..bef987e --- /dev/null +++ b/src/analysis/astvisitor.ts @@ -0,0 +1,790 @@ +import traverse, {NodePath} from "@babel/traverse"; +import { + ArrayExpression, + ArrowFunctionExpression, + AssignmentExpression, + AssignmentPattern, + AwaitExpression, + CallExpression, + ClassAccessorProperty, + ClassDeclaration, + ClassExpression, + ClassMethod, + ClassPrivateMethod, + ClassPrivateProperty, + ClassProperty, + ConditionalExpression, + ExportAllDeclaration, + ExportDefaultDeclaration, + ExportNamedDeclaration, + File, + ForOfStatement, + Function, + FunctionDeclaration, + FunctionExpression, + Identifier, + ImportDeclaration, + isArrayPattern, + isArrowFunctionExpression, + isAssignmentExpression, + isAssignmentPattern, + isClassAccessorProperty, + isClassExpression, + isClassMethod, + isClassPrivateMethod, + isClassPrivateProperty, + isClassProperty, + isDeclaration, + isExportDeclaration, + isExportDefaultDeclaration, + isExportDefaultSpecifier, + isExportNamedDeclaration, + isExpression, + isFunctionDeclaration, + isFunctionExpression, + isIdentifier, + isImportDefaultSpecifier, + isImportSpecifier, + isLVal, + isObjectMethod, + isObjectPattern, + isObjectProperty, + isPattern, + isRestElement, + isSpreadElement, + JSXElement, + JSXMemberExpression, + LogicalExpression, + LVal, + MemberExpression, + NewExpression, + ObjectExpression, + ObjectMethod, + ObjectProperty, + OptionalCallExpression, + OptionalMemberExpression, + Program, + RegExpLiteral, + ReturnStatement, + SequenceExpression, + StaticBlock, + Super, + TaggedTemplateExpression, + ThisExpression, + ThrowStatement, + variableDeclaration, + variableDeclarator, + VariableDeclarator, + WithStatement, + YieldExpression +} from "@babel/types"; +import { + AccessPathToken, + AllocationSiteToken, + ClassToken, + FunctionToken, + NativeObjectToken, + ObjectToken, + PackageObjectToken, + Token +} from "./tokens"; +import {ModuleInfo} from "./infos"; +import logger from "../misc/logger"; +import {mapArrayAdd, sourceLocationToStringWithFile, SourceLocationWithFilename} from "../misc/util"; +import assert from "assert"; +import {globalLoc, undefinedIdentifier} from "./analysisstate"; +import {options} from "../options"; +import {ComponentAccessPath, PropertyAccessPath} from "./accesspaths"; +import {ConstraintVar} from "./constraintvars"; +import { + getBaseAndProperty, + getClass, + getExportName, + getImportName, + getKey, + getProperty, + isParentExpressionStatement +} from "../misc/asthelpers"; +import { + ASYNC_GENERATOR_PROTOTYPE_NEXT, + GENERATOR_PROTOTYPE_NEXT, + PROMISE_FULFILLED_VALUES +} from "../natives/ecmascript"; +import {Operations} from "./operations"; +import {TokenListener} from "./listeners"; + +export const IDENTIFIER_KIND = Symbol(); + +export function visit(ast: File, op: Operations) { + const solver = op.solver; + const a = solver.analysisState; + let nextNodeIndex = 1; + + a.maybeEscaping.clear(); // TODO: move to method in AnalysisState? + + // traverse the AST and extend the analysis result with information about the current module + if (logger.isVerboseEnabled()) + logger.verbose(`Traversing AST of ${op.file}`); + traverse(ast, { + + enter(path: NodePath) { + + // workaround to ensure that AST nodes with undefined location (caused by desugaring) can be identified uniquely + if (!path.node.loc) { + let p: NodePath | null = path; + while (p && !p.node.loc) + p = p.parentPath; + path.node.loc = {filename: op.file, start: p?.node.loc?.start, end: p?.node.loc?.end, nodeIndex: nextNodeIndex++} as any; // see sourceLocationToString + } + }, + + Program(path: NodePath) { + + // set module source location + op.moduleInfo.loc = path.node.loc; + + // artificially declare all globals in the program scope (if not already declared) + const decls = [...op.globals, undefinedIdentifier] // TODO: treat 'undefined' like other globals? + .filter(d => path.scope.getBinding(d.name) === undefined) + .map(id => { + const d = variableDeclarator(id); + d.loc = globalLoc; + return d; + }); + const d = variableDeclaration("var", decls); + d.loc = globalLoc; + path.scope.registerDeclaration(path.unshiftContainer("body", d)[0]); + }, + + ThisExpression(path: NodePath) { + + // this + a.registerThis(path); + + // constraint: t ∈ ⟦this⟧ where t denotes the package + solver.addTokenConstraint(op.packageObjectToken, a.varProducer.nodeVar(path.node)); + + const fun = path.getFunctionParent(); + if (fun) { + + // constraint: ⟦this_f⟧ ⊆ ⟦this⟧ where f is the enclosing function + solver.addSubsetConstraint(a.varProducer.thisVar(fun.node), a.varProducer.nodeVar(path.node)); + } else { + + // constraint %globalThis ∈ ⟦this⟧ + solver.addTokenConstraint(op.natives.get("globalThis")!, a.varProducer.nodeVar(path.node)); + } + + // constraint: @Unknown ∈ ⟦this⟧ + solver.addAccessPath(op.theUnknownAccessPath, a.varProducer.nodeVar(path.node)); // TODO: omit this constraint in certain situations? + }, + + Identifier(path: NodePath) { + if (path.node.name === "arguments") // registers use of 'arguments' + a.varProducer.identVar(path.node, path); // FIXME: registerArguments may be called too late if the function is recursive + + if (options.variableKinds) { + const binding = path.scope.getBinding(path.node.name); + if (binding) + (binding.identifier as any)[IDENTIFIER_KIND] = binding.kind; + } + }, + + MemberExpression: { // TODO: actually more efficient to visit nodes bottom-up? (if not, most rules don't need to use 'exit') + exit(path: NodePath) { + visitMemberExpression(path); + } + }, + + OptionalMemberExpression: { + exit(path: NodePath) { + visitMemberExpression(path); + } + }, + + JSXMemberExpression: { + exit(path: NodePath) { + visitMemberExpression(path); + } + }, + + ReturnStatement: { + exit(path: NodePath) { + if (path.node.argument) { + const fun = path.getFunctionParent(); + if (fun) { + const expVar = op.expVar(path.node.argument, path); + + // return E + let resVar; + if (fun.node.generator) { + // find the iterator object (it is returned via Function) + // constraint: ... ⊆ ⟦i.value⟧ where i is the iterator object for the function + const iter = a.canonicalizeToken(new AllocationSiteToken("Iterator", fun.node.body, op.packageInfo)); + resVar = a.varProducer.objPropVar(iter, "value"); + } else { + // constraint: ... ⊆ ⟦ret_f⟧ where f is the enclosing function (ignoring top-level returns) + resVar = a.varProducer.returnVar(fun.node); + } + + if (fun.node.async && !fun.node.generator) { + // make a new promise with the fulfilled value being the return value + const promise = op.newPromiseToken(fun.node); + solver.addSubsetConstraint(expVar, a.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + solver.addTokenConstraint(promise!, resVar); + } else + solver.addSubsetConstraint(expVar, resVar); + } + } + } + }, + + Function(path: NodePath) { // FunctionDeclaration | FunctionExpression | ObjectMethod | ArrowFunctionExpression | ClassMethod | ClassPrivateMethod + // record that a function/method/constructor/getter/setter has been reached, connect to its enclosing function or module + const fun = path.node; + let cls; + if (isClassMethod(fun) && fun.kind === "constructor") + cls = getClass(path); + const name = isFunctionDeclaration(path.node) || isFunctionExpression(path.node) ? path.node.id?.name : + (isObjectMethod(path.node) || isClassMethod(path.node)) ? getKey(path.node) : + cls ? cls.id?.name : undefined;// for constructors, use the class name if present + const anon = isFunctionDeclaration(path.node) || isFunctionExpression(path.node) ? path.node.id === null : isArrowFunctionExpression(path.node); + const loc = cls ? cls.loc : // for constructors, use the class location (workaround to match dyn.ts) + isClassMethod(fun) && fun.static ? {filename: (fun.key.loc as SourceLocationWithFilename).filename, start: fun.key.loc!.start, end: fun.loc!.end} : // for static methods, use the identifier location (workaround to match dyn.ts) + fun.loc; + const msg = cls ? "constructor" : `${name ?? (anon ? "" : "")}`; + if (logger.isVerboseEnabled()) + logger.verbose(`Reached function ${msg} at ${sourceLocationToStringWithFile(loc)}`); + a.registerFunctionInfo(op.file, path, name, fun, loc); + if (!name && !anon) + a.warnUnsupported(fun, `Computed ${isFunctionDeclaration(path.node) || isFunctionExpression(path.node) ? "function" : "method"} name`); // TODO: handle functions/methods with unknown name? + + if (fun.generator) { + + // function* + + // constraint: %(Async)Generator.prototype.next ⊆ ⟦i.next⟧ where i is the iterator object for the function + const iter = a.canonicalizeToken(new AllocationSiteToken("Iterator", fun.body, op.packageInfo)); + const iterNext = a.varProducer.objPropVar(iter, "next"); + solver.addTokenConstraint(op.natives.get(fun.async ? ASYNC_GENERATOR_PROTOTYPE_NEXT : GENERATOR_PROTOTYPE_NEXT)!, iterNext); + + // constraint i ∈ ⟦ret_f⟧ where i is the iterator object for the function + solver.addTokenConstraint(iter, a.varProducer.returnVar(fun)); + } + }, + + FunctionDeclaration: { + exit(path: NodePath) { + + // function f(...) {...} (as declaration) + // constraint: t ∈ ⟦f⟧ where t denotes the function + const to = path.node.id ? path.node.id : path.node; // export default functions may not have names, use the FunctionDeclaration node as constraint variable in that situation + solver.addTokenConstraint(op.newFunctionToken(path.node), a.varProducer.nodeVar(to)); + } + }, + + FunctionExpression: { + exit(path: NodePath) { + + // function f(...) {...} (as expression, possibly without name) + // constraint: t ∈ ⟦function f(...) {...}⟧ where t denotes the function + if (!isParentExpressionStatement(path)) + solver.addTokenConstraint(op.newFunctionToken(path.node), a.varProducer.nodeVar(path.node)); + // constraint: t ∈ ⟦f⟧ (if the function is named) where t denotes the function + if (path.node.id) + solver.addTokenConstraint(op.newFunctionToken(path.node), a.varProducer.nodeVar(path.node.id)); + } + }, + + ArrowFunctionExpression: { + exit(path: NodePath) { + + // (...) => E + // constraint: t ∈ ⟦(...) => E⟧ where t denotes the function + if (!isParentExpressionStatement(path)) + solver.addTokenConstraint(op.newFunctionToken(path.node), a.varProducer.nodeVar(path.node)); + // constraint: ⟦E⟧ ⊆ ⟦ret_f⟧ where f is the function + if (isExpression(path.node.body)) + solver.addSubsetConstraint(op.expVar(path.node.body, path), a.varProducer.returnVar(path.node)); + } + }, + + CallExpression: { + exit(path: NodePath) { + + // E0(E1,...) + visitCallOrNew(false, path); + } + }, + + OptionalCallExpression: { + exit(path: NodePath) { + + // E?.E0(E1,...) + visitCallOrNew(false, path); + } + }, + + NewExpression: { + exit(path: NodePath) { + + // new E0(E1,...) + visitCallOrNew(true, path); + } + }, + + AssignmentExpression: { + exit(path: NodePath) { + const oper = path.node.operator; + if (oper === '=' || oper === '||=' || oper === '&&=' || oper === '??=') { + const eVar = op.expVar(path.node.right, path); + op.assign(eVar, path.node.left, path.node, a.getEnclosingFunctionOrModule(path, op.moduleInfo), path, false); + + // constraint: ⟦E⟧ ⊆ ⟦... = E⟧ + if (!isParentExpressionStatement(path)) + solver.addSubsetConstraint(eVar, a.varProducer.nodeVar(path.node)); + } + } + }, + + AssignmentPattern: { + exit(path: NodePath) { + + // X = E (as default value) + // constraint: ⟦E⟧ ⊆ ⟦X⟧ (if X is a simple identifier...) + op.assign(op.expVar(path.node.right, path), path.node.left, path.node, a.getEnclosingFunctionOrModule(path, op.moduleInfo), path, false); + } + }, + + VariableDeclarator: { // handles VariableDeclaration + exit(path: NodePath) { + if (path.node.init) { + + // var/let/const X = E + // constraint: ⟦E⟧ ⊆ ⟦X⟧ (if X is a simple identifier...) + op.assign(op.expVar(path.node.init, path), path.node.id, path.node, a.getEnclosingFunctionOrModule(path, op.moduleInfo), path, false); + } + } + }, + + ConditionalExpression: { + exit(path: NodePath) { + + // E1 ? E2 : E3 + // constraints: ⟦E2⟧ ⊆ ⟦E1 ? E2 : E3⟧, ⟦E3⟧ ⊆ ⟦E1 ? E2 : E3⟧ + if (!isParentExpressionStatement(path)) { + solver.addSubsetConstraint(op.expVar(path.node.consequent, path), a.varProducer.nodeVar(path.node)); + solver.addSubsetConstraint(op.expVar(path.node.alternate, path), a.varProducer.nodeVar(path.node)); + } + } + }, + + LogicalExpression: { + exit(path: NodePath) { + + // E1 op E2 where op is ||, && or ?? + // constraints: ⟦E1⟧ ⊆ ⟦E1 op E2⟧, ⟦E2⟧ ⊆ ⟦E1 op E2⟧ + // (the former can safely be omitted for && when only tracking functions and objects) + if (!isParentExpressionStatement(path)) { + if (path.node.operator !== "&&") + solver.addSubsetConstraint(op.expVar(path.node.left, path), a.varProducer.nodeVar(path.node)); + solver.addSubsetConstraint(op.expVar(path.node.right, path), a.varProducer.nodeVar(path.node)); + } + } + }, + + SequenceExpression: { + exit(path: NodePath) { // TODO: handle in expVar (like ParenthesizedExpression) to reduce size of constraint graph? + + // (..., ..., E) + // constraint: ⟦E⟧ ⊆ ⟦(..., ..., E)⟧ + if (!isParentExpressionStatement(path)) + solver.addSubsetConstraint(op.expVar(path.node.expressions[path.node.expressions.length - 1], path), a.varProducer.nodeVar(path.node)); + } + }, + + Property: { // ObjectProperty | ClassProperty | ClassAccessorProperty | ClassPrivateProperty + exit(path: NodePath) { + if (isPattern(path.parent)) + return; // pattern properties are handled at assign + if (isClassAccessorProperty(path.node)) + assert.fail(`Encountered ClassAccessorProperty at ${sourceLocationToStringWithFile(path.node.loc)}`); // https://github.com/tc39/proposal-grouped-and-auto-accessors + const key = getKey(path.node); + if (key) { + if (path.node.value) { + if (!isExpression(path.node.value)) + assert.fail(`Unexpected Property value type ${path.node.value?.type} at ${sourceLocationToStringWithFile(path.node.loc)}`); + + // {..., p: E, ...} or class... {...; p = E; ...} (static or non-static, private or public) + const rightvar = op.expVar(path.node.value, path); + let dst; + if (options.alloc && isObjectProperty(path.node)) { + // constraint: ⟦E⟧ ⊆ ⟦i.p⟧ where i is the object literal + dst = a.varProducer.objPropVar(a.canonicalizeToken(new ObjectToken(path.parentPath.node, op.packageInfo)), key); + } else if (options.alloc && (isClassProperty(path.node) || isClassAccessorProperty(path.node) || isClassPrivateProperty(path.node)) && path.node.static) { + // constraint: ⟦E⟧ ⊆ ⟦c.p⟧ where c is the class + const cls = getClass(path); + assert(cls); + dst = a.varProducer.objPropVar(a.canonicalizeToken(new ClassToken(cls, op.packageInfo)), key); + } else { + // constraint: ⟦E⟧ ⊆ ⟦k.p⟧ where k is the current package + dst = a.varProducer.packagePropVar(op.file, key); + } + solver.addSubsetConstraint(rightvar, dst); + // TODO: special treatment for ClassPrivateProperty? static properties? + } + } else + a.warnUnsupported(path.node, "Computed property name"); // TODO: nontrivial computed property name + if (isClassProperty(path.node)) // dyn.ts treats class property initializers as functions + a.registerArtificialFunction(op.moduleInfo, path.node.key); + } + }, + + Method: { // ObjectMethod | ClassMethod | ClassPrivateMethod + exit(path: NodePath) { + switch (path.node.kind) { + case "method": + case "get": + case "set": + const key = getKey(path.node); + if (key) { + + // [class C...] {... p(..) {...} ...} (static or non-static, private or public) + const t = op.newFunctionToken(path.node); + const ac = path.node.kind === "method" ? "normal" : path.node.kind; + let dst; + if (options.alloc && isObjectMethod(path.node)) { + // constraint: t ∈ ⟦(ac)i.p⟧ where t denotes the function, i is the object literal, + // and (ac) specifies whether it is a getter, setter or normal property + dst = a.varProducer.objPropVar(a.canonicalizeToken(new ObjectToken(path.parentPath.node, op.packageInfo)), key, ac); + } else if (options.alloc && (isClassMethod(path.node) || isClassPrivateMethod(path.node)) && path.node.static) { + // constraint: t ∈ ⟦(ac)c.p⟧ where t denotes the function, c is the class, + // and (ac) specifies whether it is a getter, setter or normal property + const cls = getClass(path); + assert(cls); + dst = a.varProducer.objPropVar(a.canonicalizeToken(new ClassToken(cls, op.packageInfo)), key, ac); + + } else { + // constraint: t ∈ ⟦(ac)k.p⟧ where t denotes the function and k is the current package, + // and (ac) specifies whether it is a getter, setter or normal property + dst = a.varProducer.packagePropVar(op.file, key, ac); + } + solver.addTokenConstraint(t, dst); + // TODO: special treatment for ClassPrivateMethod? static properties? + } else + a.warnUnsupported(path.node, "Computed method name"); // TODO: nontrivial computed method name + break; + case "constructor": + + // class C... {... constructor(..) {...} ...} + // constraint: t ∈ ⟦C⟧ where t denotes the constructor function + const cls = getClass(path); + if (cls && cls.id) // note: to match dyn.ts, the FunctionToken uses the actual constructor location, but the FunctionInfo uses the location of the class + solver.addTokenConstraint(op.newFunctionToken(path.node), a.varProducer.nodeVar(cls.id)); + break; + } + // TODO: currently ignoring generator, async, static (often easy to resolve!), override, optional, abstract + } + }, + + Class: { // ClassExpression | ClassDeclaration + exit(path: NodePath) { + + if (path.node.superClass) { + + // class C extends E {...} + // constraint: ⟦E⟧ ⊆ ⟦extends_c⟧ where c is the class + solver.addSubsetConstraint(op.expVar(path.node.superClass, path), a.varProducer.extendsVar(path.node)); // TODO: test class inheritance (see C11 in classes.js) + } + + let constructor: ClassMethod | ClassPrivateMethod | undefined; + for (const b of path.node.body.body) + if ((isClassMethod(b) || isClassPrivateMethod(b)) && b.kind === "constructor") + constructor = b; + const exported = isExportDeclaration(path.parent); + if (constructor) { + if (isClassExpression(path.node) || exported) { + + // class ... {...} + // constraint: t ∈ ⟦class ... {...}⟧ where t denotes the constructor function + if (!isParentExpressionStatement(path) || exported) + solver.addTokenConstraint(op.newFunctionToken(constructor), a.varProducer.nodeVar(path.node)); + } + } else // no explicit constructor (dyn.ts records a call to an implicit constructor) + a.registerArtificialFunction(op.moduleInfo, path.node); + + // class ... {...} + // constraint: c ∈ ⟦class ... {...}⟧ where c is the ClassToken + const ct = op.newClassToken(path.node); + if (isClassExpression(path.node) || exported) + solver.addTokenConstraint(ct, a.varProducer.nodeVar(path.node)); + + // constraint: c ∈ ⟦C⟧ where c is the ClassToken + if (path.node.id) + solver.addTokenConstraint(ct, a.varProducer.nodeVar(path.node.id)); + } + }, + + ObjectExpression(path: NodePath) { + + // {...} + if (!isParentExpressionStatement(path)) { + + // constraint: t ∈ ⟦{...}⟧ where t is the object for this allocation site + solver.addTokenConstraint(op.newObjectToken(path.node), a.varProducer.nodeVar(path.node)); + // TODO: fall back to field-based if an object token appears in a constraint variable together with >k other object tokens? (see analysisstate.add) + + for (const p of path.node.properties) + if (isSpreadElement(p)) { + a.warnUnsupported(p, "SpreadElement in ObjectExpression"); // TODO: SpreadElement in ObjectExpression + } // (ObjectProperty and ObjectMethod are handled at rules Property and Method respectively) + } + }, + + ArrayExpression(path: NodePath) { + + // [...] + if (!isParentExpressionStatement(path)) { + + // constraint: t ∈ ⟦{...}⟧ where t is the array for this allocation site + const t = op.newArrayToken(path.node); + solver.addTokenConstraint(t, a.varProducer.nodeVar(path.node)); + + for (const [index, e] of path.node.elements.entries()) + if (isExpression(e)) { + + // constraint: ⟦E⟧ ⊆ ⟦t.i⟧ for each array element E with index i + const prop = String(index); + solver.addSubsetConstraint(op.expVar(e, path), a.varProducer.objPropVar(t, prop)); + } else if (e) + a.warnUnsupported(e, "SpreadElement in ArrayExpression"); // TODO: SpreadElement in ArrayExpression + } + }, + + StaticBlock(path: NodePath) { + a.registerArtificialFunction(op.moduleInfo, path.node); // dyn.ts treats static blocks as functions + }, + + ThrowStatement: { + exit(path: NodePath) { + a.registerEscaping(op.expVar(path.node.argument, path)); + } + }, + + CatchClause: { + // TODO: CatchClause + }, + + Super(path: NodePath) { + a.warnUnsupported(path.node); // TODO: super + }, + + ImportDeclaration(path: NodePath) { + + // model 'import' like 'require' + op.requireModule(path.node.source.value, a.varProducer.nodeVar(path.node), path); // TODO: see TODO in requireResolve about using import.meta.resolve + + let any = false; + for (const imp of path.node.specifiers) { + switch (imp.type) { + case "ImportNamespaceSpecifier": + + // bind the module export object to the namespace identifier + solver.addSubsetConstraint(a.varProducer.nodeVar(path.node), a.varProducer.nodeVar(imp.local)); + break; + + case "ImportSpecifier": + case "ImportDefaultSpecifier": + any = true; + break; + } + // record identifier uses for pattern matcher + const refs = path.scope.getBinding(imp.local.name)?.referencePaths; + if (refs) + for (const ref of refs) + mapArrayAdd(imp.local, ref.node, a.importDeclRefs); + } + // bind each module export object property to the local identifier + if (any) { + + // constraint: ∀ objects t ∈ ⟦import...⟧: ⟦t.p⟧ ⊆ ⟦x⟧ where p is the property and x is the local identifier + // for each import specifier + solver.addForAllConstraint(a.varProducer.nodeVar(path.node), TokenListener.IMPORT_BASE, path.node, (t: Token) => { + for (const imp of path.node.specifiers) + if (isImportSpecifier(imp) || isImportDefaultSpecifier(imp)) { + const prop = getImportName(imp); + if (t instanceof AllocationSiteToken || t instanceof FunctionToken || t instanceof NativeObjectToken || t instanceof PackageObjectToken) + solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), a.varProducer.nodeVar(imp.local)); + else if (t instanceof AccessPathToken) // TODO: treat as object along with other tokens above? + solver.addAccessPath(a.canonicalizeAccessPath(new PropertyAccessPath(a.varProducer.nodeVar(path.node), prop)), a.varProducer.nodeVar(imp.local), t.ap); // TODO: describe this constraint... + } + }); + } + }, + + ExportDeclaration(path: NodePath) { + switch (path.node.type) { + case "ExportNamedDeclaration": // examples: export const { prop: name } = { prop: ... }, export { x as y } + case "ExportDefaultDeclaration": // example: export default E; + if (path.node.declaration) { + assert(!isExportNamedDeclaration(path.node) || path.node.specifiers.length === 0, "Unexpected specifiers at ExportNamedDeclaration with declaration"); + assert(!isExportNamedDeclaration(path.node) || !path.node.source, "Unexpected source at ExportNamedDeclaration with declaration"); + const decl = path.node.declaration; + switch (decl.type) { + case "FunctionDeclaration": // example: export function x() {...} + case "ClassDeclaration": { // example: export class x {...} + const from = decl.id ? decl.id : decl; // using the declaration node as constraint variable for anonymous functions and classes + assert(isExportDefaultDeclaration(path.node) || decl.id, "Unexpected missing id"); + const prop = isExportDefaultDeclaration(path.node) ? "default" : decl.id!.name; + solver.addSubsetConstraint(a.varProducer.nodeVar(from), a.varProducer.objPropVar(op.exportsObjectToken, prop)); + break; + } + case "VariableDeclaration": { // example: export var x = ... (local declaration and init value handled at rule VariableDeclarator) + function exportDeclared(lval: LVal) { + if (isIdentifier(lval)) + solver.addSubsetConstraint(a.varProducer.nodeVar(lval), a.varProducer.objPropVar(op.exportsObjectToken, lval.name)); + else if (isAssignmentPattern(lval)) + exportDeclared(lval.left); + else if (isObjectPattern(lval)) { + for (const p of lval.properties) + if (isRestElement(p)) + exportDeclared(p.argument); + else { + if (!isLVal(p.value)) + assert.fail(`Unexpected expression ${p.value.type}, expected LVal`); + exportDeclared(p.value); + } + } else if (isArrayPattern(lval)) { + for (const p of lval.elements) + if (p) + if (isRestElement(p)) + exportDeclared(p.argument); + else + exportDeclared(p); + } else + assert.fail(`Unexpected LVal type ${lval.type}`); + } + for (const decl2 of decl.declarations) + exportDeclared(decl2.id); + break; + } + default: { + if (isDeclaration(decl)) + assert.fail(`Unexpected declaration type ${decl.type} in ExportDeclaration`); + if (isExpression(decl)) + solver.addSubsetConstraint(op.expVar(decl, path), a.varProducer.objPropVar(op.exportsObjectToken, "default")); + break; + } + } + } else { + if (!isExportNamedDeclaration(path.node)) + assert.fail(`Unexpected node type ${path.node.type}`); + const node = path.node; + function getExportVar(name: string): ConstraintVar | undefined { + const m = node.source ? op.requireModule(node.source.value, a.varProducer.nodeVar(node), path) : undefined; + return m instanceof ModuleInfo ? a.varProducer.objPropVar(a.canonicalizeToken(new NativeObjectToken("exports", m)), name) : undefined; + } + for (const spec of path.node.specifiers) + switch (spec.type) { + case "ExportSpecifier": // example: export {x as y} ... + case "ExportDefaultSpecifier": // example: export x from "m" + const from = isExportDefaultSpecifier(spec) ? getExportVar("default") : node.source ? getExportVar(spec.local.name) : a.varProducer.identVar(spec.local, path); + solver.addSubsetConstraint(from, a.varProducer.objPropVar(op.exportsObjectToken, getExportName(spec.exported))); + break; + case "ExportNamespaceSpecifier": // example: export * as x from "m" + a.warnUnsupported(spec); // TODO: ExportNamespaceSpecifier, see https://babeljs.io/docs/en/babel-plugin-proposal-export-namespace-from + break; + } + + } + break; + case "ExportAllDeclaration": // example: export * from "m" + const m = op.requireModule(path.node.source.value, a.varProducer.nodeVar(path.node), path); + if (m instanceof ModuleInfo) { + const t = a.canonicalizeToken(new NativeObjectToken("exports", m)); + solver.addForAllObjectPropertiesConstraint(t, TokenListener.EXPORT_BASE, path.node, (prop: string) => { // TODO: only exporting explicitly defined properties, not unknown computed + solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), a.varProducer.objPropVar(op.exportsObjectToken, prop)); + }); + } + break; + } + }, + + ForOfStatement(path: NodePath) { + // read iterator using path.node for the temporary result + op.readIteratorValue(op.expVar(path.node.right, path), a.varProducer.nodeVar(path.node), path.node); + // assign the temporary result to the l-value + const lval = isLVal(path.node.left) ? path.node.left : path.node.left.declarations.length === 1 ? path.node.left.declarations[0]?.id : undefined; + assert(lval, "Unexpected number of declarations at for-of"); + op.assign(a.varProducer.nodeVar(path.node), lval, path.node, a.getEnclosingFunctionOrModule(path, op.moduleInfo), path, false); + // note: 'for await' is handled trivially because the same abstract object is used for the AsyncGenerator and the iterator objects + }, + + YieldExpression(path: NodePath) { + const fun = path.getFunctionParent()?.node; + assert(fun, "yield not in function?!"); + const iter = a.canonicalizeToken(new AllocationSiteToken("Iterator", fun.body, op.packageInfo)); + const iterValue = a.varProducer.objPropVar(iter, "value"); + if (path.node.argument) { + if (path.node.delegate) { + // yield* E + // constraint: ∀ i2 ∈ ⟦iterators(E)⟧: ⟦i2.value⟧ ⊆ ⟦i.value⟧ where i is the iterator object for the function + op.readIteratorValue(op.expVar(path.node.argument, path), iterValue, fun.body); + } else { + // yield E + // constraint: ⟦E⟧ ⊆ ⟦i.value⟧ where i is the iterator object for the function + solver.addSubsetConstraint(op.expVar(path.node.argument, path), iterValue); + } + } + // constraint: ⟦i.value⟧ ⊆ ⟦yield(*) E⟧ where i is the iterator object for the function + if (!isParentExpressionStatement(path)) + solver.addSubsetConstraint(iterValue, a.varProducer.nodeVar(path.node)); + }, + + AwaitExpression(path: NodePath) { + op.awaitPromise(op.expVar(path.node.argument, path), op.expVar(path.node, path), path.node); + }, + + TaggedTemplateExpression(path: NodePath) { + assert.fail("TaggedTemplateExpression should be handled by @babel/plugin-transform-template-literals"); + }, + + RegExpLiteral(path: NodePath) { + solver.addTokenConstraint(op.newRegExpToken(), a.varProducer.nodeVar(path.node)); + }, + + WithStatement(path: NodePath) { + a.warnUnsupported(path.node); + }, + + JSXElement(path: NodePath) { + const componentVar = op.expVar(path.node.openingElement.name, path); + if (componentVar) + solver.addForAllConstraint(componentVar, TokenListener.JSX_ELEMENT, path.node, (t: Token) => { + if (t instanceof AccessPathToken) + solver.addAccessPath(a.canonicalizeAccessPath(new ComponentAccessPath(componentVar)), a.varProducer.nodeVar(path.node), t.ap); + }); + } + }); + + /** + * Visits a CallExpression, OptionalCallExpression, or NewExpression. + */ + function visitCallOrNew(isNew: boolean, path: NodePath) { + const calleeVar = isExpression(path.node.callee) ? op.expVar(path.node.callee, path) : undefined; + const bp = getBaseAndProperty(path); + const baseVar = bp ? op.expVar(bp.base, path) : undefined; + const resultVar = op.expVar(path.node, path); + op.callFunction(calleeVar, baseVar, path.node.arguments, resultVar, isNew, path); + } + + /** + * Visits a MemberExpression or OptionalMemberExpression. + */ + function visitMemberExpression(path: NodePath) { + if (isAssignmentExpression(path.parent) && path.parent.left === path.node) + return; // don't treat left-hand-sides of assignments as expressions + + op.readProperty(op.expVar(path.node.object, path), getProperty(path.node), isParentExpressionStatement(path) ? undefined : a.varProducer.nodeVar(path.node), path.node, a.getEnclosingFunctionOrModule(path, op.moduleInfo)); + } +} \ No newline at end of file diff --git a/src/analysis/constraintvarproducer.ts b/src/analysis/constraintvarproducer.ts new file mode 100644 index 0000000..a60bf54 --- /dev/null +++ b/src/analysis/constraintvarproducer.ts @@ -0,0 +1,172 @@ +import { + Class, + Expression, + Function, + identifier, + Identifier, + isBigIntLiteral, + isBinaryExpression, + isBooleanLiteral, + isIdentifier, + isJSXIdentifier, + isNullLiteral, + isNumericLiteral, + isParenthesizedExpression, + isStringLiteral, + isSuper, + isUnaryExpression, + isUpdateExpression, + JSXIdentifier, + JSXMemberExpression, + JSXNamespacedName, + Node +} from "@babel/types"; +import {NodePath} from "@babel/traverse"; +import { + AccessorType, + ArgumentsVar, + ArrayValueVar, + ClassExtendsVar, + ConstraintVar, + FunctionReturnVar, + IntermediateVar, + NodeVar, + ObjectPropertyVar, + ObjectPropertyVarObj, + ThisVar +} from "./constraintvars"; +import logger from "../misc/logger"; +import {ArrayToken, ObjectToken, PackageObjectToken} from "./tokens"; +import {FilePath, sourceLocationToStringWithFile} from "../misc/util"; +import {PackageInfo} from "./infos"; +import {AnalysisState, globalLoc, undefinedIdentifier} from "./analysisstate"; +import {getClass} from "../misc/asthelpers"; + +export class ConstraintVarProducer { + + private readonly a: AnalysisState + + constructor(a: AnalysisState) { + this.a = a; + } + + /** + * Finds the constraint variable for the given expression in the current module. + * For parenthesized expressions, the inner expression is used. + * If the expression definitely cannot evaluate to a function value, undefined is returned. + * For Identifier expressions, the declaration node is used as constraint variable; + * For Super expressions, a ClassExtendsVar is used. + * For other expressions, the expression itself is used. + */ + expVar(exp: Expression | JSXIdentifier | JSXMemberExpression | JSXNamespacedName, path: NodePath): ConstraintVar | undefined { + while (isParenthesizedExpression(exp)) + exp = exp.expression; // for parenthesized expressions, use the inner expression + if (isIdentifier(exp) || isJSXIdentifier(exp)) { + const id = this.identVar(exp, path); + if (id instanceof NodeVar && id.node === undefinedIdentifier) + return undefined; + return id; + } else if (isNumericLiteral(exp) || isBigIntLiteral(exp) || isNullLiteral(exp) || isBooleanLiteral(exp) || + isStringLiteral(exp) || // note: currently skipping string literals + isUnaryExpression(exp) || isBinaryExpression(exp) || isUpdateExpression(exp)) + return undefined; // those expressions never evaluate to functions or objects and can safely be skipped + else if (isSuper(exp)) { + const cl = getClass(path); + if (!cl) { + this.a.warnUnsupported(exp, `Ignoring super in object expression at ${sourceLocationToStringWithFile(exp.loc)}`, true); // TODO: object expressions may have prototypes, e.g. __proto__ + return undefined; + } + return this.extendsVar(cl); + } + return this.a.varProducer.nodeVar(exp); // other expressions are already canonical + } + + /** + * Finds the constraint variable for the given identifier in the current module. + * If not found, it is added to the program scope (except for 'arguments'). + */ + identVar(id: Identifier | JSXIdentifier, path: NodePath): ConstraintVar { + const binding = path.scope.getBinding(id.name); + let d; + if (binding) { + d = binding.identifier; + } else { + if (id.name === "arguments") { + const fun = this.a.registerArguments(path); + return fun ? this.a.canonicalizeVar(new ArgumentsVar(fun)) : this.a.varProducer.nodeVar(id); // using the identifier itself as fallback if no enclosing function + } else { + const ps = path.scope.getProgramParent(); + d = ps.getBinding(id.name)?.identifier; + if (!d) { + d = identifier(id.name); + d.loc = globalLoc; + ps.push({id: d}); + if (logger.isDebugEnabled()) + logger.debug(`No binding for identifier ${id.name}, creating one in program scope`); + } else if (logger.isDebugEnabled()) + logger.debug(`No binding for identifier ${id.name}, using the one in program scope`); + } + } + return this.a.varProducer.nodeVar(d); // Identifiers are already canonical + } + + /** + * Finds the constraint variable for a named object property. + */ + objPropVar(obj: ObjectPropertyVarObj, prop: string, accessor: AccessorType = "normal"): ObjectPropertyVar { + if (obj instanceof ObjectToken && this.a.widened.has(obj)) + return this.packagePropVar(obj.packageInfo, prop, accessor); + return this.a.canonicalizeVar(new ObjectPropertyVar(obj, prop, accessor)); + } + + /** + * Finds the constraint variable for an array value. + */ + arrayValueVar(arr: ArrayToken): ArrayValueVar { + return this.a.canonicalizeVar(new ArrayValueVar(arr)); + } + + /** + * Finds the constraint variable for an object property for a package. + */ + packagePropVar(pck: FilePath | PackageInfo, prop: string, accessor: AccessorType = "normal"): ObjectPropertyVar { + return this.objPropVar(this.a.canonicalizeToken(new PackageObjectToken(pck instanceof PackageInfo ? pck : this.a.getModuleInfo(pck).packageInfo)), prop, accessor); + } + + /** + * Finds the constraint variable representing the return values of the given function. + */ + returnVar(fun: Function): FunctionReturnVar { + return this.a.canonicalizeVar(new FunctionReturnVar(fun)); + } + + /** + * Finds the constraint variable representing the super-class of the given class. + */ + extendsVar(cl: Class): ClassExtendsVar { + return this.a.canonicalizeVar(new ClassExtendsVar(cl)); + } + + /** + * Finds the constraint variable representing 'this' for the given function. + */ + thisVar(fun: Function): ThisVar { + return this.a.canonicalizeVar(new ThisVar(fun)); + } + + /** + * Finds the constraint variable representing the given intermediate result. + */ + intermediateVar(n: Node, label: string): IntermediateVar { + return this.a.canonicalizeVar(new IntermediateVar(n, label)); + } + + /** + * Finds the constraint variable representing the given AST node (or undefined). + */ + nodeVar(n: Node): NodeVar + nodeVar(n: Node | undefined): NodeVar | undefined + nodeVar(n: Node | undefined): NodeVar | undefined { + return n !== undefined ? this.a.canonicalizeVar(new NodeVar(n)) : undefined; + } +} \ No newline at end of file diff --git a/src/analysis/constraintvars.ts b/src/analysis/constraintvars.ts new file mode 100644 index 0000000..e94d6a0 --- /dev/null +++ b/src/analysis/constraintvars.ts @@ -0,0 +1,223 @@ +import {Class, Function, isIdentifier, Node} from "@babel/types"; +import {nodeToString, sourceLocationToStringWithFileAndEnd} from "../misc/util"; +import { + AllocationSiteToken, + ArrayToken, + FunctionToken, + NativeObjectToken, + PackageObjectToken +} from "./tokens"; +import {ModuleInfo, PackageInfo} from "./infos"; +import {IDENTIFIER_KIND} from "./astvisitor"; + +/** + * A constraint variable. + */ +export abstract class ConstraintVar { + + abstract toString(): string; + + /** + * Finds the AST node, function, module or package this constraint variable belongs to. + */ + abstract getParent(): Node | PackageInfo | ModuleInfo | undefined; + + /** + * Returns the kind of the constraint variable. + * Assumes options.variableKinds is set. + */ + getKind(): string { + return this.constructor.name; + } +} + +/** + * A constraint variable for an AST node. + */ +export class NodeVar extends ConstraintVar { + + node: Node; + + constructor(node: Node) { + super(); + this.node = node; + } + + toString(): string { + return nodeToString(this.node); + } + + getParent(): Node { + return this.node; + } + + getKind(): string { + return isIdentifier(this.node) ? `Identifier[${(this.node as any)[IDENTIFIER_KIND]}]` : this.node.type; + } +} + +/** + * Kind of ObjectPropertyVar. + */ +export type AccessorType = "get" | "set" | "normal"; + +export type ObjectPropertyVarObj = AllocationSiteToken | FunctionToken | NativeObjectToken | PackageObjectToken; + +/** + * A constraint variable for an object property. + */ +export class ObjectPropertyVar extends ConstraintVar { + + readonly obj: ObjectPropertyVarObj; + + readonly prop: string + + readonly accessor: AccessorType; + + constructor(obj: ObjectPropertyVarObj, prop: string, accessor: AccessorType = "normal") { + super(); + this.prop = prop; + this.obj = obj; + this.accessor = accessor; + } + + toString(): string { + return `${this.accessor === "get" ? "(get)" : this.accessor === "set" ? "(set)" : ""}${this.obj}.${this.prop}`; + } + + getParent() { + return this.obj instanceof AllocationSiteToken ? this.obj.allocSite : + this.obj instanceof FunctionToken ? this.obj.fun : + this.obj instanceof NativeObjectToken ? this.obj.moduleInfo : + this.obj.packageInfo; + } +} + +/** + * A constraint variable for an unknown array entry. + */ +export class ArrayValueVar extends ConstraintVar { + + readonly array: ArrayToken; + + constructor(array: ArrayToken) { + super(); + this.array = array; + } + + toString(): string { + return `${this.array}.*`; + } + + getParent(): Node { + return this.array.allocSite; + } +} + +/** + * A constraint variable for a function return. + */ +export class FunctionReturnVar extends ConstraintVar { + + readonly fun: Function; + + constructor(fun: Function) { + super(); + this.fun = fun; + } + + toString() { + return `Return[${sourceLocationToStringWithFileAndEnd(this.fun.loc)}]` + } + + getParent(): Node { + return this.fun; + } +} + +/** + * A constraint variable for 'this'. + */ +export class ThisVar extends ConstraintVar { + + readonly fun: Function; + + constructor(fun: Function) { + super(); + this.fun = fun; + } + + toString() { + return `This[${sourceLocationToStringWithFileAndEnd(this.fun.loc)}]`; + } + + getParent(): Node { + return this.fun; + } +} + +/** + * A constraint variable for 'arguments'. + */ +export class ArgumentsVar extends ConstraintVar { + + readonly fun: Function; + + constructor(fun: Function) { + super(); + this.fun = fun; + } + + toString() { + return `Arguments[${sourceLocationToStringWithFileAndEnd(this.fun.loc)}]`; + } + + getParent(): Node { + return this.fun; + } +} + +/** + * A constraint variable for the super-class of a class. + */ +export class ClassExtendsVar extends ConstraintVar { + + readonly cl: Class; + + constructor(cl: Class) { + super(); + this.cl = cl; + } + + toString() { + return `Extends[${sourceLocationToStringWithFileAndEnd(this.cl.loc)}]` + } + + getParent(): Node { + return this.cl; + } +} + +/** + * A constraint variable for an intermediate result. + */ +export class IntermediateVar extends ConstraintVar { + + readonly node: Node; + + readonly label: string; + + constructor(node: Node, label: string) { + super(); + this.node = node; + this.label = label; + } + + toString() { + return `#${this.label}[${sourceLocationToStringWithFileAndEnd(this.node.loc)}]` + } + + getParent(): Node { + return this.node; + } +} \ No newline at end of file diff --git a/src/analysis/escaping.ts b/src/analysis/escaping.ts new file mode 100644 index 0000000..d0c3d27 --- /dev/null +++ b/src/analysis/escaping.ts @@ -0,0 +1,98 @@ +import {ModuleInfo} from "./infos"; +import {AccessPathToken, AllocationSiteToken, FunctionToken, NativeObjectToken, ObjectToken, Token} from "./tokens"; +import {ConstraintVar, FunctionReturnVar, ObjectPropertyVar} from "./constraintvars"; +import {mapGetArray} from "../misc/util"; +import logger from "../misc/logger"; +import {isIdentifier} from "@babel/types"; +import Solver from "./solver"; +import {UnknownAccessPath} from "./accesspaths"; + +/** + * Finds the ObjectTokens that may be accessed from outside the module via exporting to or importing from other modules. + * Also adds UnknownAccessPath at parameters of escaping functions. + * Note: objects that are assigned to 'exports' (or to properties of such objects) are not considered escaping + * (unless also returned by an escaping function or passed as argument to an external function). + */ +export function findEscapingObjects(m: ModuleInfo, solver: Solver): Set { + const a = solver.analysisState; + const f = solver.fragmentState; + const worklist: Array = []; + const visited = new Set(); + const escaping = new Set(); + const theUnknownAccessPathToken = a.canonicalizeToken(new AccessPathToken(a.canonicalizeAccessPath(new UnknownAccessPath()))); + + /** + * Adds the tokens of the given constraint variable to the worklist if not already visited. + * Note: PackageObjectTokens and AccessPathTokens are ignored. + */ + function addToWorklist(v: ConstraintVar) { + for (const t of f.getTokens(v)) + if ((t instanceof AllocationSiteToken || t instanceof FunctionToken || t instanceof NativeObjectToken) && !visited.has(t)) { + worklist.push(t); + visited.add(t); + } + } + + // find object properties + const objprops = new Map>(); + for (const v of f.vars) + if (v instanceof ObjectPropertyVar) + mapGetArray(objprops, v.obj).push(v); + + // first round, seed worklist with module.exports, find functions accessible via property reads + addToWorklist(a.varProducer.objPropVar(a.canonicalizeToken(new NativeObjectToken("module", m)), "exports")); + const w2: Array = []; + while (worklist.length !== 0) { + const t = worklist.shift()!; // breadth-first + if (t instanceof FunctionToken) + w2.push(t); + else if (t instanceof ObjectToken || (t instanceof NativeObjectToken && t.name === "exports")) + for (const w of mapGetArray(objprops, t)) + addToWorklist(w); + } + worklist.push(...w2); + // add expressions collected during AST traversal + for (const v of solver.analysisState.maybeEscaping) + addToWorklist(v); + + // FIXME: arguments to (non-modeled) native functions should also be considered escaped? + + // second round, find objects that are accessible externally via functions and expressions found in first round + while (worklist.length !== 0) { + const t = worklist.shift()!; // breadth-first + if (t instanceof ObjectToken) { + if (logger.isDebugEnabled()) + logger.debug(`Escaping object: ${t}`); + escaping.add(t); + } + if (t instanceof FunctionToken) { + + // values returned from escaping functions are escaping + addToWorklist(a.canonicalizeVar(new FunctionReturnVar(t.fun))); + + // add UnknownAccessPath at parameters + for (const param of t.fun.params) + if (isIdentifier(param)) // TODO: Pattern|RestElement? + solver.addToken(theUnknownAccessPathToken, f.getRepresentative(a.varProducer.nodeVar(param))); + + // TODO: also consider inheritance, ClassExtendsVar? + } + + // properties of escaping objects are escaping + for (const w of mapGetArray(objprops, t)) { + addToWorklist(w); + solver.addToken(theUnknownAccessPathToken, w); + } + } + + if (logger.isVerboseEnabled()) { + const objecttokens = new Set(); + for (const [, ts] of f.getAllVarsAndTokens()) + for (const t of ts) + if (t instanceof ObjectToken) + objecttokens.add(t); + logger.verbose(`Escaping objects: ${escaping.size}/${objecttokens.size}`); + } + + return escaping; +} diff --git a/src/analysis/fragmentstate.ts b/src/analysis/fragmentstate.ts new file mode 100644 index 0000000..5a1e5d6 --- /dev/null +++ b/src/analysis/fragmentstate.ts @@ -0,0 +1,274 @@ +import {ConstraintVar, ObjectPropertyVarObj} from "./constraintvars"; +import {AllocationSiteToken, ArrayToken, FunctionToken, Token} from "./tokens"; +import {PackageInfo} from "./infos"; +import {Node} from "@babel/types"; +import assert from "assert"; + +export type ListenerID = number; + +/** + * Analysis state for a fragment (a module or a package with dependencies, depending on the analysis phase). + */ +export class FragmentState { + + /** + * The current analysis solution. + * Singleton sets are represented as plain references, larger sets are represented as ES2015 sets. + */ + private readonly tokens: Map> = new Map; + + /** + * The set of constraint variables (including those with no tokens or no subset edges, but excluding those that are redirected). + */ + readonly vars: Set = new Set; + + /** + * Indirection introduced by cycle elimination. + */ + readonly redirections: Map = new Map; + + /** + * Number of tokens for the currently analyzed fragment. (For statistics only.) + */ + numberOfTokens: number = 0; + + numberOfSubsetEdges: number = 0; + + readonly subsetEdges: Map> = new Map; + + readonly reverseSubsetEdges: Map> = new Map; // (used by solver.redirect) + + /** + * Inheritance relation. For each token, the map provides the tokens it may inherit from directly. + */ + inherits: Map> = new Map; + + reverseInherits: Map> = new Map; + + readonly arrayEntries: Map> = new Map; + + objectProperties: Map> = new Map; + + readonly tokenListeners: Map void>> = new Map; + + readonly pairListeners1: Map void]>> = new Map; + + readonly pairListeners2: Map void]>> = new Map; + + readonly pairListenersProcessed: Map>> = new Map; + + readonly packageNeighborListeners: Map void>> = new Map; + + ancestorListeners: Map void>> = new Map; + + readonly ancestorListenersProcessed: Map> = new Map; // TODO: make similar map/set for other kinds of listeners to avoid redundant listener calls? + + readonly arrayEntriesListeners: Map void>> = new Map; + + objectPropertiesListeners: Map void>> = new Map; + + readonly packageNeighbors: Map> = new Map; + + readonly postponedListenerCalls: Array<[(t: Token) => void, Token] | [(t1: AllocationSiteToken, t2: FunctionToken) => void, [AllocationSiteToken, FunctionToken]] | [(neighbor: PackageInfo) => void, PackageInfo] | [(prop: string) => void, string]> = []; + + /** + * Returns the representative of the given constraint variable. + * Also shortcuts redirections that involve multiple steps. + */ + getRepresentative(v: ConstraintVar): ConstraintVar { + let w = v; + const ws = []; + while (true) { + const w2 = this.redirections.get(w); + if (!w2) + break; + assert(ws.length < 100); + ws.push(w); + w = w2; + } + for (let i = 0; i + 1 < ws.length; i++) { + assert(ws[i] !== w); + this.redirections.set(ws[i], w); + } + return w; + } + + /** + * Returns the tokens in the solution for the given constraint variable + * (or empty if v is undefined). + */ + getTokens(v: ConstraintVar | undefined): Iterable { + if (v) { + const ts = this.tokens.get(v); + if (ts) { + if (ts instanceof Token) + return [ts]; + return ts; + } + } + return []; + } + + /** + * Returns the number of tokens in the solution for the given constraint variable, and the tokens. + */ + getTokensSize(v: ConstraintVar | undefined): [number, Iterable] { + if (v) { + const ts = this.tokens.get(v); + if (ts) { + if (ts instanceof Token) + return [1, [ts]]; + return [ts.size, ts]; + } + } + return [0, []]; + } + + /** + * Returns all constraint variables with their tokens and number of tokens. + */ + *getAllVarsAndTokens(): Iterable<[ConstraintVar, Iterable, number]> { + for (const [v, ts] of this.tokens) + if (ts instanceof Token) + yield [v, [ts], 1]; + else + yield [v, ts, ts.size]; + } + + /** + * Returns the number of tokens and a 'has' function for the given constraint variable. + */ + getSizeAndHas(v: ConstraintVar | undefined): [number, (t: Token) => boolean] { + if (v) { + const ts = this.tokens.get(v); + if (ts) { + if (ts instanceof Token) + return [1, (t: Token) => ts === t]; + return [ts.size, (t: Token) => ts.has(t)]; + } + } + return [0, (_t: Token) => false]; + } + + /** + * Returns the number of constraint variables with tokens. + */ + getNumberOfVarsWithTokens() { + return this.tokens.size; + } + + /** + * Checks whether the given variable has tokens. + */ + hasVar(v: ConstraintVar) { + return this.tokens.has(v); + } + + /** + * Removes all tokens from the given variable. + */ + deleteVar(v: ConstraintVar) { + this.tokens.delete(v); + } + + /** + * Replaces all tokens according to the given function. + */ + replaceTokens(f: (ts: Iterable) => Set) { + for (const [v, ts] of this.tokens) { + const r = f(ts instanceof Token ? [ts] : ts); + this.tokens.set(v, r.size === 1 ? r.values().next().value : r); + this.numberOfTokens += r.size - (ts instanceof Token ? 1 : ts.size); + } + } + + /** + * Adds the given token to the solution for the given constraint variable. + * @return true if not already there, false if already there + */ + addToken(t: Token, v: ConstraintVar): boolean { + const ts = this.tokens.get(v); + if (!ts) + this.tokens.set(v, t); + else if (ts instanceof Token) { + if (ts === t) + return false; + this.tokens.set(v, new Set([ts, t])); + } else { + if (ts.has(t)) + return false; + ts.add(t); + } + this.numberOfTokens++; + return true; + } + + /** + * Adds the given tokens to the solution for the given constraint variable. + * It is assumed that the given set does not contain any duplicates. + * @return the tokens that have been added, excluding those already there + */ + addTokens(ts: Iterable, v: ConstraintVar): Array { + const added: Array = []; + let vs = this.tokens.get(v); + for (const t of ts) { + let add = false; + if (!vs) { + vs = t; + this.tokens.set(v, vs); + add = true; + } else if (vs instanceof Token) { + if (vs !== t) { + vs = new Set([vs, t]); + this.tokens.set(v, vs); + add = true; + } + } else if (!vs.has(t)) { + vs.add(t); + add = true; + } + if (add) + added.push(t); + } + this.numberOfTokens += added.length; + return added; + } + + /** + * Returns the tokens the given token inherits from (reflexively and transitively). + */ + getAncestors(t: Token): Set { + const res = new Set(); + res.add(t); + const w = [t]; + while (w.length !== 0) { + const s = this.inherits.get(w.shift()!); + if (s) + for (const p of s) + if (!res.has(p)) { + res.add(p); + w.push(p); + } + } + return res; + } + + /** + * Returns the tokens that inherit from the given token (reflexively and transitively). + */ + getDescendants(t: Token): Set { + const res = new Set(); + res.add(t); + const w = [t]; + while (w.length !== 0) { + const s = this.reverseInherits.get(w.shift()!); + if (s) + for (const p of s) + if (!res.has(p)) { + res.add(p); + w.push(p); + } + } + return res; + } +} diff --git a/src/analysis/infos.ts b/src/analysis/infos.ts new file mode 100644 index 0000000..08c6e53 --- /dev/null +++ b/src/analysis/infos.ts @@ -0,0 +1,132 @@ +import {FilePath, sourceLocationToString} from "../misc/util"; +import {Function, SourceLocation} from "@babel/types"; +import {FragmentState} from "./fragmentstate"; + +/** + * Information about a package. + */ +export class PackageInfo { + + readonly name: string; // package name, "
" is used for entry files if no package.json is found + + readonly version: string | undefined; // package version, undefined if not available + + readonly main: string | undefined; // package main file, undefined if not available + + readonly dir: FilePath; // absolute path to package root directory, or "." for the entry files if no package.json is found + + readonly modules: Map = new Map; // map from path relative to the package root to module info + + readonly directDependencies: Set = new Set; // the direct dependencies of this package + + readonly isEntry: boolean; // true for entry packages + + fragmentState: FragmentState | undefined = undefined; // analysis solutions after analysis of this package and its dependencies + + constructor(name: string, version: string | undefined, main: string | undefined, dir: FilePath, isEntry: boolean) { + this.name = name; + this.version = version; + this.main = main; + this.dir = dir; + this.isEntry = isEntry; + } + + toString(): string { + return `${this.name}${this.version ? `@${this.version}` : ""}`; + } +} + +export function normalizeModuleName(s: string): string { + return s.endsWith("/index.js") ? s.substring(0, s.length - 9) : // (ignoring index.json and index.node) + s.endsWith(".js") ? s.substring(0, s.length - 3) : + s.endsWith(".mjs") ? s.substring(0, s.length - 4) : s; +} + +/** + * Information about a module/file. + */ +export class ModuleInfo { + + readonly relativePath: string; // path relative to the package root + + readonly packageInfo: PackageInfo; // package containing this module + + readonly path: FilePath; // normalized file path of the representative file (in analysisstate, different paths in moduleInfos may refer to the same ModuleInfo) + + readonly functions: Map = new Map; // functions directly inside this module + + readonly isEntry: boolean; // true for entry modules + + loc: SourceLocation | null | undefined = null; // top-level source location (set by astvisitor) + + fragmentState: FragmentState | undefined = undefined; // analysis solution after local analysis of this module + + constructor(relativePath: string, packageInfo: PackageInfo, path: FilePath, isEntry: boolean) { + this.relativePath = relativePath; + this.packageInfo = packageInfo; + this.path = path; + this.isEntry = isEntry; + } + + toString(): string { + return `${this.packageInfo}:${this.relativePath}`; + } + + /** + * Returns the official name of this module, using the package name if the module is the main module, + * and otherwise stripping /index.js, .js and .mjs. + */ + getOfficialName(): string { + if (this.relativePath === this.packageInfo.main) + return this.packageInfo.name; + return normalizeModuleName(`${this.packageInfo.name}/${this.relativePath}`); + } +} + +/** + * Information about a module that is not being analyzed. + */ +export class DummyModuleInfo { // used for module files that can't be found (typically because they haven't been installed) + + readonly requireName: string; // require string (normalized but not resolved to a file) + + constructor(requireName: string) { + this.requireName = normalizeModuleName(requireName); + } + + toString(): string { + return `${this.requireName}[unresolved]`; + } + + getOfficialName(): string { + return this.requireName; + } +} + +/** + * Information about a function. + */ +export class FunctionInfo { + + readonly name: string | undefined; // function name + + readonly loc: SourceLocation | null | undefined; // function source location + + readonly moduleInfo: ModuleInfo; // module containing this function + + readonly functions: Map = new Map; // functions directly inside this function + + get packageInfo(): PackageInfo { + return this.moduleInfo.packageInfo; + } + + constructor(name: string | undefined, loc: SourceLocation | null | undefined, moduleInfo: ModuleInfo) { + this.name = name; + this.loc = loc; + this.moduleInfo = moduleInfo; + } + + toString() { + return `${this.moduleInfo}:${sourceLocationToString(this.loc)}:${this.name ?? ""}`; + } +} diff --git a/src/analysis/listeners.ts b/src/analysis/listeners.ts new file mode 100644 index 0000000..cf020cf --- /dev/null +++ b/src/analysis/listeners.ts @@ -0,0 +1,64 @@ +/** + * IDs for token listeners. + */ +export enum TokenListener { + CALL_FUNCTION_CALLEE, + CALL_FUNCTION_EXTERNAL, + CALL_FUNCTION_BASE_CALLEE, + READ_PROPERTY_BASE, + READ_PROPERTY_GETTER, + READ_PROPERTY_GETTER2, + READ_PROPERTY_BASE_DYNAMIC, + READ_PROPERTY_BASE_DYNAMIC_ARRAY, + ASSIGN_MEMBER_BASE, + ASSIGN_SETTER, + ASSIGN_DYNAMIC_BASE, + ASSIGN_DYNAMIC_BASE_ARRAY, + ASSIGN_OBJECT_PATTERN_REST, + ASSIGN_OBJECT_PATTERN_REST_PROPERTIES, + ASSIGN_ARRAY_PATTERN_REST, + ASSIGN_ARRAY_PATTERN_REST_ARRAY, + READ_ITERATOR_VALUE, + READ_ITERATOR_VALUE_ARRAY, + IMPORT_BASE, + EXPORT_BASE, + NATIVE_INVOKE_CALLBACK, + NATIVE_INVOKE_CALLBACK2, + CALL_PROMISE_EXECUTOR, + CALL_PROMISE_RESOLVE, + CALL_PROMISE_ONFULFILLED, + CALL_PROMISE_ONREJECTED, + CALL_PROMISE_ONFINALLY, + MAKE_PROMISE_RESOLVE, + MAKE_PROMISE_REJECT, + MAKE_PROMISE_ALL, + MAKE_PROMISE_ALLSETTLED, + MAKE_PROMISE_ANY, + MAKE_PROMISE_RACE, + AWAIT, + JSX_ELEMENT, + NATIVE_1, // TODO: better names to these token listeners? + NATIVE_2, + NATIVE_3, + NATIVE_4, + NATIVE_5, + NATIVE_6, + NATIVE_7, + NATIVE_8, + NATIVE_9, + NATIVE_10, + NATIVE_11, + NATIVE_12, + NATIVE_13, + NATIVE_14, + NATIVE_15, + NATIVE_16, + NATIVE_17, + NATIVE_18, + NATIVE_19, + NATIVE_20, + NATIVE_21, + NATIVE_22, + NATIVE_23, + NATIVE_24, +} diff --git a/src/analysis/modulefinder.ts b/src/analysis/modulefinder.ts new file mode 100644 index 0000000..c81ddd7 --- /dev/null +++ b/src/analysis/modulefinder.ts @@ -0,0 +1,69 @@ +import { + CallExpression, + ExportAllDeclaration, + ExportNamedDeclaration, + File, + ImportDeclaration, + isIdentifier, + isImport, + isStringLiteral +} from "@babel/types"; +import {FilePath, sourceLocationToStringWithFile} from "../misc/util"; +import traverse, {NodePath} from "@babel/traverse"; +import logger from "../misc/logger"; +import Solver from "./solver"; +import {options} from "../options"; +import {builtinModules} from "../natives/nodejs"; +import {requireResolve} from "../misc/files"; + +/** + * Scans AST for 'require', 'import' and 'export' only (no proper analysis). + */ +export function findModules(ast: File, file: FilePath, solver: Solver) { + const a = solver.analysisState; + + function requireModule(str: string, path: NodePath) { // see requireModule in operations.ts + if (!(builtinModules.has(str) || (str.startsWith("node:") && builtinModules.has(str.substring(5))))) + try { + const filepath = requireResolve(str, file, path.node.loc, a); + if (filepath) + a.reachedFile(filepath, path.getFunctionParent()?.node ?? file); + } catch { + if (options.ignoreUnresolved || options.ignoreDependencies) { + if (logger.isVerboseEnabled()) + logger.verbose(`Ignoring unresolved module '${str}' at ${sourceLocationToStringWithFile(path.node.loc)}`); + } else// TODO: special warning if the require/import is placed in a try-block, an if statement, or a switch case? + a.warn(`Unable to resolve module '${str}' at ${sourceLocationToStringWithFile(path.node.loc)}`); + } + } + + traverse(ast, { + + CallExpression(path: NodePath) { + if (((isIdentifier(path.node.callee) && + path.node.callee.name === "require" && + !path.scope.getBinding(path.node.callee.name)) || + isImport(path.node.callee)) && + path.node.arguments.length >= 1) { + const arg = path.node.arguments[0]; + if (isStringLiteral(arg)) + requireModule(arg.value, path); + else + a.error(`Unhandled 'require' at ${sourceLocationToStringWithFile(path.node.loc)}`); + } + }, + + ImportDeclaration(path: NodePath) { + requireModule(path.node.source.value, path); + }, + + ExportAllDeclaration(path: NodePath) { + requireModule(path.node.source.value, path); + }, + + ExportNamedDeclaration(path: NodePath) { + if (path.node.source) + requireModule(path.node.source.value, path); + } + }); +} \ No newline at end of file diff --git a/src/analysis/operations.ts b/src/analysis/operations.ts new file mode 100644 index 0000000..dd225d8 --- /dev/null +++ b/src/analysis/operations.ts @@ -0,0 +1,700 @@ +import { + CallExpression, + Expression, + Function, + Identifier, + isArrayPattern, + isAssignmentPattern, + isExportDeclaration, + isExpression, + isIdentifier, + isImport, + isLVal, + isMemberExpression, + isObjectPattern, + isParenthesizedExpression, + isRestElement, + isStringLiteral, + JSXIdentifier, + JSXMemberExpression, + JSXNamespacedName, + LVal, + NewExpression, + Node, + OptionalCallExpression, + ParenthesizedExpression +} from "@babel/types"; +import {NodePath} from "@babel/traverse"; +import {getKey, getProperty, isMaybeUsedAsPromise, isParentExpressionStatement} from "../misc/asthelpers"; +import {AccessPathToken, AllocationSiteToken, ArrayToken, ClassToken, FunctionToken, NativeObjectToken, ObjectToken, PackageObjectToken, Token} from "./tokens"; +import {ArgumentsVar, ConstraintVar, IntermediateVar, NodeVar} from "./constraintvars"; +import {CallResultAccessPath, IgnoredAccessPath, ModuleAccessPath, PropertyAccessPath, UnknownAccessPath} from "./accesspaths"; +import Solver from "./solver"; +import {AnalysisState, globalLoc} from "./analysisstate"; +import {DummyModuleInfo, FunctionInfo, ModuleInfo, normalizeModuleName, PackageInfo} from "./infos"; +import logger from "../misc/logger"; +import {builtinModules} from "../natives/nodejs"; +import {requireResolve} from "../misc/files"; +import {options} from "../options"; +import {FilePath, getOrSet, isArrayIndex, nodeToString, sourceLocationToStringWithFile} from "../misc/util"; +import assert from "assert"; +import {ARRAY_PROTOTYPE, FUNCTION_PROTOTYPE, MAP_KEYS, MAP_VALUES, OBJECT_PROTOTYPE, PROMISE_FULFILLED_VALUES, PROMISE_PROTOTYPE, REGEXP_PROTOTYPE, SET_VALUES} from "../natives/ecmascript"; +import {SpecialNativeObjects} from "../natives/nativebuilder"; +import {ConstraintVarProducer} from "./constraintvarproducer"; +import {TokenListener} from "./listeners"; +import micromatch from "micromatch"; +import {callPromiseResolve} from "../natives/nativehelpers"; + +/** + * Models of core JavaScript operations used by astvisitor and nativehelpers. + */ +export class Operations { + + readonly file: FilePath; + + readonly solver: Solver; + + readonly globals: Array; + + readonly natives: SpecialNativeObjects + + readonly a: AnalysisState; // shortcut to this.solver.analysisState + + readonly varProducer: ConstraintVarProducer; // shortcut to this.solver.analysisState.varProducer + + readonly moduleInfo: ModuleInfo; + + readonly packageInfo: PackageInfo; + + readonly packageObjectToken: PackageObjectToken; + + readonly exportsObjectToken: NativeObjectToken; + + readonly theUnknownAccessPath: UnknownAccessPath; + + readonly theIgnoredModuleAccessPath: IgnoredAccessPath; + + constructor(file: FilePath, solver: Solver, globals: Array, natives: SpecialNativeObjects) { + this.file = file; + this.solver = solver; + this.globals = globals; + this.natives = natives; + + this.a = this.solver.analysisState; + this.varProducer = this.a.varProducer; + + this.moduleInfo = this.a.getModuleInfo(file); + this.packageInfo = this.moduleInfo.packageInfo; + this.packageObjectToken = this.a.canonicalizeToken(new PackageObjectToken(this.packageInfo)); + this.exportsObjectToken = this.a.canonicalizeToken(new NativeObjectToken("exports", this.moduleInfo)); + this.theUnknownAccessPath = this.a.canonicalizeAccessPath(new UnknownAccessPath()); + this.theIgnoredModuleAccessPath = this.a.canonicalizeAccessPath(new IgnoredAccessPath()); + } + + /** + * Finds the constraint variable for the given expression in the current module using ConstraintVarProducer.expVar. + * Also adds @Unknown and a subset constraint for globalThis.E if the given expression E is an implicitly declared global variable. + */ + expVar(exp: Expression | JSXIdentifier | JSXMemberExpression | JSXNamespacedName, path: NodePath): ConstraintVar | undefined { + const v = this.varProducer.expVar(exp, path); + + // if the expression is a variable that has not been declared normally... + if (v instanceof NodeVar && isIdentifier(v.node) && v.node.loc === globalLoc) { + + // the variable may be a property of globalThis + // constraint: globalThis.X ∈ ⟦X⟧ + this.solver.addSubsetConstraint(this.varProducer.objPropVar(this.natives.get("globalThis")!, v.node.name), v); + + // the variable may be declared explicitly by unknown code + // constraint: @Unknown ∈ ⟦X⟧ + this.solver.addAccessPath(this.theUnknownAccessPath, v); + } + return v; + } + + /** + * Models calling a function. + * @param calleeVar constraint variable describing the callee, undefined if not applicable + * @param baseVar constraint variable describing the method call base, undefined if not applicable + * @param args arguments array of arguments + * @param resultVar constraint variable describing the call result, undefined if not applicable + * @param isNew true if this is a 'new' call + * @param path path of the call expression + */ + callFunction(calleeVar: ConstraintVar | undefined, baseVar: ConstraintVar | undefined, args: CallExpression["arguments"], + resultVar: ConstraintVar | undefined, isNew: boolean, path: NodePath) { + const caller = this.a.getEnclosingFunctionOrModule(path, this.moduleInfo); + let pars: NodePath = path; // (workaround to match dyn.ts which has wrong source location for calls in parenthesized expressions) + while (isParenthesizedExpression(pars.parentPath!.node)) + pars = pars.parentPath!; + this.a.registerCall(pars.node, this.moduleInfo); + + // collect special information for pattern matcher + if (isParentExpressionStatement(pars)) + this.a.registerCallWithUnusedResult(path.node); + if (isMaybeUsedAsPromise(path)) + this.a.registerCallWithResultMaybeUsedAsPromise(path.node); + this.a.registerInvokedExpression(path.node.callee); + + const a = this.a; + function* getStrings(exp: Expression | any): Iterable { + if (isStringLiteral(exp)) + yield exp.value; + else // TODO: currently supporting only string literals at 'require' and 'import' + a.warnUnsupported(path.node, "Unhandled 'require'", true); + } + + // expression E0(E1,...,En) or new E0(E1,...,En) + // constraint: ∀ functions t ∈ ⟦E0⟧: ... + this.solver.addForAllConstraint(calleeVar, TokenListener.CALL_FUNCTION_CALLEE, path.node, (t: Token) => { + if (t instanceof FunctionToken) { + this.a.registerCallEdge(pars.node, caller, this.a.functionInfos.get(t.fun)!); + if (t.moduleInfo !== this.moduleInfo) + this.a.registerEscapingArguments(args, path); + const hasArguments = this.a.functionsWithArguments.has(t.fun); + const argumentsToken = hasArguments ? this.a.canonicalizeToken(new ArrayToken(t.fun.body, this.packageInfo)) : undefined; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + // constraint: ...: ⟦Ei⟧ ⊆ ⟦Xi⟧ for each argument/parameter i (Xi may be a pattern) + if (isExpression(arg)) { + const argVar = this.expVar(arg, path); + if (i < t.fun.params.length) + this.assign(argVar, t.fun.params[i], pars.node, caller, path, true, isRestElement(t.fun.params[i]) ? args.slice(i) : undefined, t.fun); + // constraint ...: ⟦Ei⟧ ⊆ ⟦t_arguments[i]⟧ for each argument i if the function uses 'arguments' + if (hasArguments) + this.solver.addSubsetConstraint(argVar, this.varProducer.objPropVar(argumentsToken!, String(i))); + } else if (arg) + this.a.warnUnsupported(arg, "SpreadElement in arguments", true); // TODO: SpreadElement in arguments + } + // constraint: ...: ⟦ret_t⟧ ⊆ ⟦(new) E0(E1,...,En)⟧ + if (!isParentExpressionStatement(pars)) + this.solver.addSubsetConstraint(this.varProducer.returnVar(t.fun), resultVar); + // constraint: ...: t_arguments ∈ ⟦t_arguments⟧ if the function uses 'arguments' + if (hasArguments) { + const argumentsVar = this.a.canonicalizeVar(new ArgumentsVar(t.fun)); + this.solver.addTokenConstraint(argumentsToken!, argumentsVar); + } + + } else if (t instanceof NativeObjectToken) { + this.a.registerCall(pars.node, this.moduleInfo, {native: true}); + if (t.invoke && (!isNew || t.constr)) + t.invoke({path, solver: this.solver, op: this, moduleInfo: this.moduleInfo, natives: this.natives}); + + if (t.name === "require" && args.length >= 1) { + + // require(...) + for (const str of getStrings(args[0])) + this.requireModule(str, resultVar, path); + } + + } else if (t instanceof AllocationSiteToken && (t.kind === "PromiseResolve" || t.kind === "PromiseReject") && !isNew) { + callPromiseResolve(t, args, path, this); + + } else if (t instanceof AccessPathToken) { + assert(calleeVar); + this.a.registerCall(pars.node, this.moduleInfo, {external: true}); + this.a.registerEscapingArguments(args, path); + + // constraint: add CallResultAccessPath + this.solver.addAccessPath(this.a.canonicalizeAccessPath(new CallResultAccessPath(calleeVar)), resultVar, t.ap); + + // constraint: assign UnknownAccessPath to arguments to function arguments for external functions, also add (artificial) call edge + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (isExpression(arg)) + this.solver.addForAllConstraint(this.expVar(arg, path), TokenListener.CALL_FUNCTION_EXTERNAL, arg, (at: Token) => { + if (at instanceof FunctionToken) { + this.a.registerCallEdge(pars.node, caller, this.a.functionInfos.get(at.fun)!, {external: true}); + for (let j = 0; j < at.fun.params.length; j++) + if (isIdentifier(at.fun.params[j])) // TODO: non-identifier parameters? + this.solver.addAccessPath(this.theUnknownAccessPath, this.a.varProducer.nodeVar(at.fun.params[j])); + } + }); + else + this.a.warnUnsupported(arg, "SpreadElement in arguments to external function"); // TODO: SpreadElement in arguments to external function + } + // TODO: also add arguments (and everything reachable from them) to escaping? + // TODO: also add UnknownAccessPath to properties of object arguments for external functions? (see also TODO at AssignmentExpression) + + // TODO: if caller is MemberExpression with property 'apply', 'call' or 'bind', treat as call to the native function of that name (relevant for lodash/atomizer TAPIR benchmark) + } + + // if 'new' and not a native object with an invoke function and not an access path token... + if (isNew && (!(t instanceof NativeObjectToken) || !t.invoke) && !(t instanceof AccessPathToken)) { + + // constraint: t ∈ ⟦new E0(E1,...,En)⟧ where t is the current PackageObjectToken + this.solver.addTokenConstraint(this.packageObjectToken, resultVar); // TODO: use allocation-site abstraction for 'new'? + } + }); + + // constraint: if E0 is a member expression E.m: ∀ t ∈ ⟦E⟧, functions f ∈ ⟦E0⟧: if f uses 'this'... + this.solver.addForAllConstraint(calleeVar, TokenListener.CALL_FUNCTION_BASE_CALLEE, path.node, (ft: Token) => { + if (ft instanceof FunctionToken && this.a.functionsWithThis.has(ft.fun)) + + // constraint: ... ⟦E⟧ ⊆ ⟦this_f⟧ + this.solver.addSubsetConstraint(baseVar, this.varProducer.thisVar(ft.fun)); // TODO: introduce special subset edge that only propagates FunctionToken and AllocationSiteToken? + }); + + // 'import' expression + if (calleeVar instanceof NodeVar && isImport(calleeVar.node) && args.length >= 1) { + const v = this.a.canonicalizeVar(new IntermediateVar(path.node, "import")); + for (const str of getStrings(args[0])) + this.requireModule(str, v, path); + const promise = this.newPromiseToken(path.node); + this.solver.addTokenConstraint(promise, this.expVar(path.node, path)); + this.solver.addSubsetConstraint(v, this.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + } + } + + /** + * Models reading a property of an object. + * @param base constraint variable representing the base variable + * @param prop property name, undefined if unknown + * @param dst constraint variable for the result, or undefined if not applicable + * @param node AST node where the operation occurs (used for constraint keys etc.) + * @param enclosing enclosing function/module of the AST node + */ + readProperty(base: ConstraintVar | undefined, prop: string | undefined, dst: ConstraintVar | undefined, node: Node, enclosing: FunctionInfo | ModuleInfo) { + this.solver.collectPropertyRead(dst, base, this.packageObjectToken); + + // expression E.p or E["p"] or E[i] + if (prop !== undefined) { + + const readFromGetter = (t: Token) => { + if (t instanceof FunctionToken && t.fun.params.length === 0) { + if (dst) + this.solver.addSubsetConstraint(this.varProducer.returnVar(t.fun), dst); + if (base && this.a.functionsWithThis.has(t.fun)) + this.solver.addSubsetConstraint(base, this.varProducer.thisVar(t.fun)); + this.a.registerCall(node, this.moduleInfo, {accessor: true}); + this.a.registerCallEdge(node, enclosing, this.a.functionInfos.get(t.fun)!, {accessor: true}); + } + } + + // constraint: ∀ objects t ∈ ⟦E⟧: ... + this.solver.addForAllConstraint(base, TokenListener.READ_PROPERTY_BASE, node, (t: Token) => { + + // constraint: ... ∀ ancestors t2 of t: ... + this.solver.addForAllAncestorsConstraint(t, node, (t2: Token) => { + + if (t2 instanceof AllocationSiteToken || t2 instanceof FunctionToken || t2 instanceof NativeObjectToken || t2 instanceof PackageObjectToken) { + + // constraint: ... ⟦t2.p⟧ ⊆ ⟦E.p⟧ + if (dst) + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t2, prop), dst); // TODO: exclude AccessPathTokens? + + // constraint: ... ∀ functions t3 ∈ ⟦(get)t2.p⟧: ⟦ret_t3⟧ ⊆ ⟦E.p⟧ + this.solver.addForAllConstraint(this.varProducer.objPropVar(t2, prop, "get"), TokenListener.READ_PROPERTY_GETTER, node, readFromGetter); + + if (t2 instanceof PackageObjectToken) { + // TODO: also reading from neighbor packages if t2 is a PackageObjectToken... + this.solver.addForAllPackageNeighborsConstraint(t2.packageInfo, node, (neighbor: PackageInfo) => { + if (dst) + this.solver.addSubsetConstraint(this.varProducer.packagePropVar(neighbor, prop), dst); // TODO: exclude AccessPathTokens? + this.solver.addForAllConstraint(this.varProducer.packagePropVar(neighbor, prop, "get"), TokenListener.READ_PROPERTY_GETTER2, node, readFromGetter); + }); + + } else if (t2 instanceof ArrayToken) { + if (isArrayIndex(prop)) { + + // constraint: ... ⟦t2.*⟧ ⊆ ⟦E.p⟧ + this.solver.addSubsetConstraint(this.varProducer.arrayValueVar(t2), dst); + } + } + + } else if (base && t2 instanceof AccessPathToken) { + + // constraint: ... if t2 is access path, @E.p ∈ ⟦E.p⟧ + this.solver.addAccessPath(this.a.canonicalizeAccessPath(new PropertyAccessPath(base, prop)), this.a.varProducer.nodeVar(node), t2.ap); + } + }); + + if (t instanceof FunctionToken && prop === "prototype") { // FIXME: also model reads from "__proto__" + + // constraint: ... p="prototype" ∧ t is a function ⇒ k ∈ ⟦E.p⟧ where k represents the package + if (dst) + this.solver.addTokenConstraint(this.packageObjectToken, dst); // FIXME: use special prototype objects instead of PackageObjectToken! + } + }); + + } else { // TODO: handle dynamic property reads? + + this.a.registerEscaping(base); // unknown properties of the base object may escape + this.solver.addAccessPath(this.theUnknownAccessPath, dst); + + // constraint: ∀ arrays t ∈ ⟦E⟧: ... + if (dst) + this.solver.addForAllConstraint(base, TokenListener.READ_PROPERTY_BASE_DYNAMIC, node, (t: Token) => { + if (t instanceof ArrayToken) { + + // constraint: ... ⟦t.*⟧ ⊆ ⟦E[i]⟧ + this.solver.addSubsetConstraint(this.varProducer.arrayValueVar(t), dst); + + // constraint: ...: ⟦t.p⟧ ⊆ ⟦E[i]⟧ where p is a property of t + this.solver.addForAllArrayEntriesConstraint(t, TokenListener.READ_PROPERTY_BASE_DYNAMIC_ARRAY, node, (prop: string) => { + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, prop), dst); + }); + // TODO: ignoring reads from prototype chain + + } else { // TODO: assuming dynamic reads from arrays only read array indices + if (logger.isInfoEnabled()) + this.a.registerUnhandledDynamicPropertyRead(node); + } + }); + + // TODO: PropertyAccessPaths for dynamic property reads? + } + // TODO: special treatment for E.prototype? and other standard properties? + // TODO: computed property assignments (with known prefix/suffix) (also handle PrivateName properties?) + // TODO: warn at reads from ‘arguments.callee’ + } + + /** + * Models 'require' and 'import'. + * If path denotes an ExportDeclaration, no constraints are generated. + * Returns the module info object, or undefined if not available. + */ + requireModule(str: string, resultVar: ConstraintVar | undefined, path: NodePath): ModuleInfo | DummyModuleInfo | undefined { // see requireModule in modulefinder.ts + const reexport = isExportDeclaration(path.node); + let m: ModuleInfo | DummyModuleInfo | undefined; + if (builtinModules.has(str) || (str.startsWith("node:") && builtinModules.has(str.substring(5)))) { + + if (!reexport) { + // standard library module: model with UnknownAccessPath + // constraint: @Unknown ∈ ⟦require(...)⟧ + this.solver.addAccessPath(this.theUnknownAccessPath, resultVar); + // TODO: models for parts of the standard library + } else + this.a.warnUnsupported(path.node, `Ignoring re-export from built-in module '${str}'`); // TODO: re-exporting from built-in module + } else { + try { + + // try to locate the module + const filepath = requireResolve(str, this.file, path.node.loc, this.a); + if (filepath) { + + // register that the module is reached + m = this.a.reachedFile(filepath, path.getFunctionParent()?.node ?? this.file); + + if (!reexport) { + // constraint: ⟦module_m.exports⟧ ⊆ ⟦require(...)⟧ where m denotes the module being loaded + this.solver.addSubsetConstraint(this.varProducer.objPropVar(this.a.canonicalizeToken(new NativeObjectToken("module", m)), "exports"), resultVar); + } + } + } catch { + if (options.ignoreUnresolved || options.ignoreDependencies) { + if (logger.isVerboseEnabled()) + logger.verbose(`Ignoring unresolved module '${str}' at ${sourceLocationToStringWithFile(path.node.loc)}`); + } else // TODO: special warning if the require/import is placed in a try-block, an if statement, or a switch case? + this.a.warn(`Unable to resolve module '${str}' at ${sourceLocationToStringWithFile(path.node.loc)}`); // TODO: may report duplicate error messages + + // couldn't find module file (probably hasn't been installed), use a DummyModuleInfo if absolute module name + if (!"./#".includes(str[0])) + m = getOrSet(this.a.dummyModuleInfos, str, () => new DummyModuleInfo(str)); + } + + if (m) { + + // add access path token + const analyzed = m instanceof ModuleInfo && (!options.ignoreDependencies || this.a.entryFiles.has(m.path)); + if (!analyzed || options.vulnerabilities) { + const s = normalizeModuleName(str); + const tracked = options.trackedModules && options.trackedModules.find(e => + micromatch.isMatch(m!.getOfficialName(), e) || micromatch.isMatch(s, e)) + this.solver.addAccessPath(tracked ? + this.a.canonicalizeAccessPath(new ModuleAccessPath(m, s)) : + this.theIgnoredModuleAccessPath, + resultVar); + } + + this.a.registerRequireCall(path.node, this.a.getEnclosingFunctionOrModule(path, this.moduleInfo), m); + } + } + return m; + } + + /** + * Models an assignment from a constraint variable to an l-value. + * @param src source constraint variable + * @param dst destination l-value + * @param node AST node of the assignment (used for setter call edges) + * @param enclosing enclosing function/module of the source + * @param path path of operation (at calls, this is the path of the call site, not the function definition) + * @param isCall true if the assignment is for parameter passing at a function call, otherwise false + * @param rest rest arguments, undefined if not applicable + * @param fun a function if the assignment is for parameter passing at a function call, otherwise undefined + */ + assign(src: ConstraintVar | undefined, dst: LVal | ParenthesizedExpression, node: Node, enclosing: FunctionInfo | ModuleInfo, path: NodePath, isCall: boolean, rest?: CallExpression["arguments"], fun?: Function) { + while (isParenthesizedExpression(dst)) + dst = dst.expression as LVal | ParenthesizedExpression; // for parenthesized expressions, use the inner expression (the definition of LVal in @babel/types misses ParenthesizedExpression) + if (isIdentifier(dst)) { + + // X = E + // constraint: ⟦E⟧ ⊆ ⟦X⟧ + const lVar = isCall ? this.a.varProducer.nodeVar(dst) : this.varProducer.identVar(dst, path); + this.solver.addSubsetConstraint(src, lVar); + + if (fun) + this.a.registerFunctionParameter(lVar, fun); // TODO: also register for some of the cases below? + + } else if (isMemberExpression(dst)) { + const lVar = this.expVar(dst.object, path); + const prop = getProperty(dst); + if (prop !== undefined) { + + // E1.p = E2 + + const writeToSetter = (t: Token) => { + if (t instanceof FunctionToken && t.fun.params.length === 1) { + this.assign(src, t.fun.params[0], node, enclosing, path, true); // TODO: recursive call to assign, skip if already called with these arguments? + if (this.a.functionsWithThis.has(t.fun)) + this.solver.addSubsetConstraint(lVar, this.varProducer.thisVar(t.fun)); + this.a.registerCall(node, this.moduleInfo, {accessor: true}); + this.a.registerCallEdge(node, enclosing, this.a.functionInfos.get(t.fun)!, {accessor: true}); + } + } + + // constraint: ∀ objects t ∈ ⟦E1⟧: ... + this.solver.addForAllConstraint(lVar, TokenListener.ASSIGN_MEMBER_BASE, path.node, (t: Token) => { + if (t instanceof AllocationSiteToken || t instanceof FunctionToken || t instanceof NativeObjectToken || t instanceof PackageObjectToken) { + + // FIXME: special treatment of writes to "prototype" and "__proto__" + + // constraint: ...: ⟦E2⟧ ⊆ ⟦t.p⟧ + this.solver.addSubsetConstraint(src, this.varProducer.objPropVar(t, prop)); + + // constraint: ...: ∀ functions t2 ∈ ⟦(set)t.p⟧: ⟦E2⟧ ⊆ ⟦x⟧ where x is the parameter of t2 + this.solver.addForAllConstraint(this.varProducer.objPropVar(t, prop, "set"), TokenListener.ASSIGN_SETTER, path.node, writeToSetter); + + } else if (lVar && t instanceof AccessPathToken) { + + // constraint: ...: ⟦E2⟧ ⊆ ⟦k.p⟧ where k is the current PackageObjectToken + this.solver.addSubsetConstraint(src, this.varProducer.packagePropVar(this.packageInfo, prop)); + + // collect property write operation @E1.p + this.solver.addAccessPath(this.a.canonicalizeAccessPath(new PropertyAccessPath(lVar, prop)), this.a.varProducer.nodeVar(path.node), t.ap); + + // TODO: the following apparently has no effect on call graph or pattern matching... + // // constraint: assign UnknownAccessPath to arguments to function values for external functions + // this.solver.addForAllConstraint2(eVar, TokenListener.ASSIGN_..., path.node, (at: Token) => { + // if (at instanceof FunctionToken) { + // for (let j = 0; j < at.fun.params.length; j++) + // if (isIdentifier(at.fun.params[j])) // TODO: non-identifier parameters? + // this.solver.addAccessPath(theUnknownAccessPath, at.fun.params[j]); + // } + // }); + // TODO: also add the assigned value (and everything reachable from it) to escaping? + } + }); + + // TODO: special treatment for E.prototype? and other standard properties? + + } else { + + // E1[...] = E2 + this.solver.collectDynamicPropertyWrite(lVar); + this.a.registerEscaping(src); + + // constraint: ∀ arrays t ∈ ⟦E1⟧: ... + this.solver.addForAllConstraint(lVar, TokenListener.ASSIGN_DYNAMIC_BASE, path.node, (t: Token) => { + if (t instanceof ArrayToken) { + + // constraint: ...: ⟦E2⟧ ⊆ ⟦t.*⟧ + this.solver.addSubsetConstraint(src, this.varProducer.arrayValueVar(t)); + + // TODO: write to array setters also? + + } else { + if (logger.isInfoEnabled() && src) + this.a.registerUnhandledDynamicPropertyWrite(path.node, src, options.warningsUnsupported && logger.isVerboseEnabled() ? path.getSource() : undefined); + } + }); + // TODO: computed property assignments (with known prefix/suffix) + + // TODO: PropertyAccessPath for dynamic property writes? + } + // TODO: warn at writes to properties of ‘arguments’ + + } else if (isAssignmentPattern(dst)) + // delegate to dst.left (the default value dst.right is handled at AssignmentPattern) + this.assign(src, dst.left, dst, enclosing, path, isCall, undefined, fun); + else if (isObjectPattern(dst)) { + const matched = new Set(); + for (const p of dst.properties) + if (isRestElement(p)) { + // read the remaining object properties of src into a fresh object at p + const t = this.newObjectToken(p); + this.solver.addForAllConstraint(src, TokenListener.ASSIGN_OBJECT_PATTERN_REST, p, (t2: Token) => { + if (t2 instanceof AllocationSiteToken || t2 instanceof FunctionToken || t2 instanceof NativeObjectToken || t2 instanceof PackageObjectToken) { + this.solver.addForAllObjectPropertiesConstraint(t2, TokenListener.ASSIGN_OBJECT_PATTERN_REST_PROPERTIES, p, (prop: string) => { // TODO: only copying explicit properties, not unknown computed + if (!matched.has(prop)) + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t2, prop), this.varProducer.objPropVar(t, prop)); + // TODO: PropertyAccessPaths for rest elements in destructuring assignments for objects? + }); + } + }); + this.solver.addTokenConstraint(t, this.a.varProducer.nodeVar(p)); + // assign the object to the sub-l-value + this.assign(this.a.varProducer.nodeVar(p), p.argument, dst, enclosing, path, isCall, undefined, fun); + } else { + const prop = getKey(p); + if (prop) { + matched.add(prop); + // read the property using p for the temporary result + this.readProperty(src, prop, this.a.varProducer.nodeVar(p), p, enclosing); + // assign the temporary result at p to the locations represented by p.value + if (!isLVal(p.value)) + assert.fail(`Unexpected expression ${p.value.type}, expected LVal at ${sourceLocationToStringWithFile(p.value.loc)}`); + this.assign(this.a.varProducer.nodeVar(p), p.value, dst, enclosing, path, isCall, undefined, fun); + } + } + } else if (isArrayPattern(dst)) { + for (const [i, p] of dst.elements.entries()) + if (p) + if (isRestElement(p)) { + // read the remaining array elements of src into a fresh array at p + const t = this.newArrayToken(p); + this.solver.addForAllConstraint(src, TokenListener.ASSIGN_ARRAY_PATTERN_REST, p, (t2: Token) => { + if (t2 instanceof ArrayToken) { + this.solver.addForAllArrayEntriesConstraint(t2, TokenListener.ASSIGN_ARRAY_PATTERN_REST_ARRAY, p, (prop: string) => { + const newprop = parseInt(prop) - i; + if (newprop >= 0) + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t2, prop), this.varProducer.objPropVar(t, String(newprop))); + }); + this.solver.addSubsetConstraint(this.varProducer.arrayValueVar(t2), this.varProducer.arrayValueVar(t)); + } // TODO: PropertyAccessPaths for rest elements in destructuring assignments for arrays? + }); + this.solver.addTokenConstraint(t, this.a.varProducer.nodeVar(p)); + // assign the array to the sub-l-value + this.assign(this.a.varProducer.nodeVar(p), p.argument, dst, enclosing, path, isCall, undefined, fun); + } else { + // read the property using p for the temporary result + this.readProperty(src, String(i), this.a.varProducer.nodeVar(p), p, enclosing); + // assign the temporary result at p to the locations represented by p + this.assign(this.a.varProducer.nodeVar(p), p, dst, enclosing, path, isCall, undefined, fun); + } + } else { + if (!isRestElement(dst)) + assert.fail(`Unexpected LVal type ${dst.type} at ${sourceLocationToStringWithFile(dst.loc)}`); + if (!rest) { + this.a.warnUnsupported(node, `rest arguments missing ${nodeToString(dst)}`); // FIXME: may occur when called (spuriously?) from writeToSetter (try analyzing package enzyme or jsdom) + return; + } + // read the remaining arguments into a fresh array at dst + const t = this.newArrayToken(dst); + for (const [i, arg] of rest.entries()) + if (isExpression(arg)) // TODO: SpreadElement in arguments (warning emitted at visitCallOrNew) + this.solver.addSubsetConstraint(this.expVar(arg, path), this.varProducer.objPropVar(t, String(i))); + this.solver.addTokenConstraint(t, this.a.varProducer.nodeVar(dst)); + // assign the array to the sub-l-value + this.assign(this.a.varProducer.nodeVar(dst), dst.argument, dst, enclosing, path, isCall, undefined, fun); + } + } + + /** + * Models reading an iterator value. + * @param src source expression evaluating to iterable + * @param dst destination constraint variable + * @param node node used for constraint keys and array allocation site + */ + readIteratorValue(src: ConstraintVar | undefined, dst: ConstraintVar, node: Node) { + this.solver.addForAllConstraint(src, TokenListener.READ_ITERATOR_VALUE, node, (t: Token) => { + if (t instanceof AllocationSiteToken) + switch (t.kind) { + case "Array": + this.solver.addSubsetConstraint(this.varProducer.arrayValueVar(t), dst); + this.solver.addForAllArrayEntriesConstraint(t, TokenListener.READ_ITERATOR_VALUE_ARRAY, node, (prop: string) => { + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, prop), dst); + }); + break; + case "Set": + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, SET_VALUES), dst); + break; + case "Map": + const pair = this.newArrayToken(node); + this.solver.addTokenConstraint(pair, dst); + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, MAP_KEYS), this.varProducer.objPropVar(pair, "0")); + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, MAP_VALUES), this.varProducer.objPropVar(pair, "1")); + break; + case "Iterator": + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, "value"), dst); + break; + } // TODO: also handle TypedArray (see also nativebuilder.ts:returnIterator) + // TODO: also handle user-defined... + }); + } + + /** + * Creates a new ObjectToken that inherits from Object.prototype + * (or, if allocation site is disabled or the token has been widened, returns the current PackageObjectToken). + */ + newObjectToken(n: Node): ObjectToken | PackageObjectToken { + if (options.alloc) { + const t = this.a.canonicalizeToken(new ObjectToken(n, this.packageInfo)); + if (!(this.a.widened && this.a.widened.has(t))) { + this.solver.addInherits(t, this.natives.get(OBJECT_PROTOTYPE)!); + return t; + } + } + return this.packageObjectToken; + } + + /** + * Creates a new ArrayToken that inherits from Array.prototype. + */ + newArrayToken(n: Node): ArrayToken { + const t = this.a.canonicalizeToken(new ArrayToken(n, this.packageInfo)); + this.solver.addInherits(t, this.natives.get(ARRAY_PROTOTYPE)!); + return t; + } + + /** + * Creates a new ClassToken that inherits from Function.prototype. + */ + newClassToken(n: Node): ClassToken { + const t = this.a.canonicalizeToken(new ClassToken(n, this.packageInfo)); + this.solver.addInherits(t, this.natives.get(FUNCTION_PROTOTYPE)!); + return t; + } + + /** + * Creates a new FunctionToken that inherits from Function.prototype. + */ + newFunctionToken(fun: Function): FunctionToken { + const t = this.a.canonicalizeToken(new FunctionToken(fun, this.moduleInfo)); + this.solver.addInherits(t, this.natives.get(FUNCTION_PROTOTYPE)!); + return t; + } + + /** + * Creates a PackageObjectToken of kind RegExp that inherits from RegExp.prototype. + */ + newRegExpToken(): PackageObjectToken { + const t = this.a.canonicalizeToken(new PackageObjectToken(this.packageInfo, "RegExp")); + this.solver.addInherits(t, this.natives.get(REGEXP_PROTOTYPE)!); + return t; + } + + /** + * Creates a AllocationSiteToken of kind Promise that inherits from Promise.prototype. + */ + newPromiseToken(n: Node): AllocationSiteToken { + const t = this.a.canonicalizeToken(new AllocationSiteToken("Promise", n, this.packageInfo)); + this.solver.addInherits(t, this.natives.get(PROMISE_PROTOTYPE)!); + return t; + } + + /** + * Models 'await'. + */ + awaitPromise(arg: ConstraintVar | undefined, res: ConstraintVar | undefined, node: Node) { + if (!arg || !res) + return; + this.solver.addForAllConstraint(arg, TokenListener.AWAIT, node, (t: Token) => { + if (t instanceof AllocationSiteToken && t.kind === "Promise") + this.solver.addSubsetConstraint(this.varProducer.objPropVar(t, PROMISE_FULFILLED_VALUES), res); + else + this.solver.addTokenConstraint(t, res); + }); + } +} diff --git a/src/analysis/solver.ts b/src/analysis/solver.ts new file mode 100644 index 0000000..23ecd86 --- /dev/null +++ b/src/analysis/solver.ts @@ -0,0 +1,1033 @@ +import { + ConstraintVar, + IntermediateVar, + NodeVar, + ObjectPropertyVar, + ObjectPropertyVarObj +} from "./constraintvars"; +import logger, {isTTY, writeStdOut} from "../misc/logger"; +import {AccessPathToken, AllocationSiteToken, ArrayToken, FunctionToken, PackageObjectToken, Token} from "./tokens"; +import {AnalysisState} from "./analysisstate"; +import {ModuleInfo, PackageInfo} from "./infos"; +import { + addAll, + isArrayIndex, + mapGetArray, + mapGetMap, + mapGetSet, + mapSetAddAll, + sourceLocationToStringWithFileAndEnd +} from "../misc/util"; +import assert from "assert"; +import { + AccessPath, + CallResultAccessPath, + ComponentAccessPath, + IgnoredAccessPath, + ModuleAccessPath, + PropertyAccessPath, + UnknownAccessPath +} from "./accesspaths"; +import {isAssignmentExpression, Node} from "@babel/types"; +import {FragmentState, ListenerID} from "./fragmentstate"; +import {TokenListener} from "./listeners"; +import {nuutila} from "../misc/scc"; +import {options, patternProperties} from "../options"; +import Timer from "../misc/timer"; +import {setImmediate} from "timers/promises"; +import {AnalysisDiagnostics} from "diagnostics"; +import {getMemoryUsage} from "../misc/memory"; + +export class AbortedException extends Error {} + +export default class Solver { + + readonly analysisState: AnalysisState = new AnalysisState; + + fragmentState: FragmentState = new FragmentState; + + unprocessedTokens: Map> = new Map; + + unprocessedSubsetEdges: Map> = new Map; + + idSymbol = Symbol(); + nextNodeID = 0; + + // TODO: move some of this into AnalysisDiagnostics? + // for diagnostics only + unprocessedTokensSize: number = 0; + unprocessedSubsetEdgesSize: number = 0; + fixpointRound: number = 0; + listenerNotificationRounds: number = 0; + largestTokenSetSize: number = 0; + largestSubsetEdgeOutDegree: number = 0; + lastPrintDiagnosticsTime: number = 0; + tokenListenerNotifications: number = 0; + pairListenerNotifications: number = 0; + packageNeighborListenerNotifications: number = 0; + ancestorListenerNotifications: number = 0; + arrayEntriesListenerNotifications: number = 0; + objectPropertiesListenerNotifications: number = 0; + roundLimitReached: number = 0; + totalCycleEliminationTime = 0; + totalCycleEliminationRuns = 0; + totalPropagationTime = 0; + totalListenerCallTime = 0; + totalWideningTime = 0; + + diagnostics: AnalysisDiagnostics = { + packages: 0, + modules: 0, + functions: 0, + functionToFunctionEdges: 0, + iterations: 0, + uniqueTokens: 0, + aborted: false, + timeout: false, + time: 0, + cpuTime: 0, + codeSize: 0, + maxMemoryUsage: 0 + }; + + readonly abort?: () => boolean; + + fixpointIterationsThrottled: number = 0; + + constructor(abort?: () => boolean) { + this.abort = abort; + } + + updateDiagnostics() { + const a = this.analysisState; + const d = this.diagnostics; + d.functions = a.functionInfos.size; + d.functionToFunctionEdges = a.numberOfFunctionToFunctionEdges; + d.uniqueTokens = a.canonicalTokens.size; + const usage = getMemoryUsage(); + if (logger.isVerboseEnabled()) + logger.verbose(`Memory usage: ${usage}MB`); + d.maxMemoryUsage = Math.max(d.maxMemoryUsage, usage); + } + + /** + * Adds a single token constraint. + */ + addTokenConstraint(t: Token, to: ConstraintVar | undefined) { + if (to === undefined) + return; + if (logger.isDebugEnabled()) // (avoid building message string if not needed) + logger.debug(`Adding constraint ${t} \u2208 ${to}`); + this.addToken(t, this.fragmentState.getRepresentative(to)); + } + + /** + * Enqueues a listener call consisting of a listener and its argument(s). + */ + private enqueueListenerCall(la: [(t: Token) => void, Token] + | [(t1: AllocationSiteToken, t2: FunctionToken) => void, [AllocationSiteToken, FunctionToken]] + | [(neighbor: PackageInfo) => void, PackageInfo] + | [(prop: string) => void, string]) { + this.fragmentState.postponedListenerCalls.push(la); + } + + /** + * Adds a single token if not already present. + * Also enqueues notification of listeners and registers object properties and array entries from the constraint variable. + */ + addToken(t: Token, toRep: ConstraintVar): boolean { + const f = this.fragmentState; + if (f.addToken(t, toRep)) { + f.vars.add(toRep); + this.tokenAdded(toRep, t, + f.subsetEdges.has(toRep), + f.tokenListeners.get(toRep), + f.pairListeners1.get(toRep), + f.pairListeners2.get(toRep)); + // collect statistics + this.updateTokenStats(toRep); + // add object property and array entry if applicable + if (toRep instanceof ObjectPropertyVar) { + this.addObjectProperty(toRep.obj, toRep.prop); + if (toRep.obj instanceof ArrayToken) + this.addArrayEntry(toRep.obj, toRep.prop); + } + return true; + } + return false; + } + + /** + * Adds a set of tokens if not already present. + * By default also adds to worklist and notifies listeners. + */ + addTokens(ts: Iterable, toRep: ConstraintVar, propagate: boolean = true) { + const f = this.fragmentState; + f.vars.add(toRep); + let hasEdges = f.subsetEdges.has(toRep); + let ws: Array | undefined = undefined; + let tr: Map void> | undefined = undefined; + let tr1: Map void]> | undefined = undefined; + let tr2: Map void]> | undefined = undefined; + let any = false; + for (const t of f.addTokens(ts, toRep)) { + any = true; + if (propagate) + ws = this.tokenAdded(toRep, t, hasEdges, + tr ??= f.tokenListeners.get(toRep), + tr1 ??= f.pairListeners1.get(toRep), + tr2 ??= f.pairListeners2.get(toRep), + ws); + } + // add object property and array entry if applicable + if (any && toRep instanceof ObjectPropertyVar) { + this.addObjectProperty(toRep.obj, toRep.prop, propagate); + if (toRep.obj instanceof ArrayToken) + this.addArrayEntry(toRep.obj, toRep.prop, propagate); + } + // collect statistics + this.updateTokenStats(toRep); + } + + private tokenAdded(toRep: ConstraintVar, t: Token, + hasEdges: boolean, + tr: Map void> | undefined, + tr1: Map void]> | undefined, + tr2: Map void]> | undefined, + ws?: Array): Array | undefined { + if (logger.isDebugEnabled()) + logger.debug(`Added token ${t} to ${toRep}`); + const f = this.fragmentState; + // add to worklist if the constraint variable has outgoing subset edges + if (hasEdges) { + (ws ??= mapGetArray(this.unprocessedTokens, toRep)).push(t); + this.unprocessedTokensSize++; + if (this.unprocessedTokensSize % 100 === 0) + this.printDiagnostics(); + } + // notify listeners + if (tr) + for (const listener of tr.values()) { + this.enqueueListenerCall([listener, t]); + this.tokenListenerNotifications++; + } + if (t instanceof AllocationSiteToken && tr1) + for (const [id, [v2, listener]] of tr1) + for (const t2 of f.getTokens(f.getRepresentative(v2))) + if (t2 instanceof FunctionToken) + this.callPairListener(id, listener, t, t2); + if (t instanceof FunctionToken && tr2) + for (const [id, [v1, listener]] of tr2) + for (const t1 of f.getTokens(f.getRepresentative(v1))) + if (t1 instanceof AllocationSiteToken) + this.callPairListener(id, listener, t1, t); + return ws; + } + + /** + * Adds an access path token if not already present (and not at an AssignmentExpression). + * Also collects information for PatternMatcher about where access paths are created. + * @param ap the access path to add + * @param to the AST node (constraint variable) where to add the token (if not an AssignmentExpression) + * @param subap access path of the sub-expression (for call or property access expressions) + */ + addAccessPath(ap: AccessPath, to: ConstraintVar | undefined, subap?: AccessPath) { // TODO: store access paths separately from other tokens? + if (!to) + return; + const abstractProp = ap instanceof PropertyAccessPath && ap.prop !== "default" && patternProperties && !patternProperties.has(ap.prop); + const ap2 = subap instanceof IgnoredAccessPath || (subap instanceof UnknownAccessPath && abstractProp) ? subap : + abstractProp ? this.analysisState.canonicalizeAccessPath(new PropertyAccessPath((ap as PropertyAccessPath).base, "?")) : ap; // abstracting irrelevant access paths + if (logger.isDebugEnabled()) + logger.debug(`Adding access path ${ap2}${ap2 !== ap ? ` (${ap})` : ""} at ${to}${subap ? ` (sub-expression access path: ${subap})` : ""}`); + const a = this.analysisState; + const f = this.fragmentState; + const asn = to instanceof NodeVar && isAssignmentExpression(to.node); + if (!asn) + this.addToken(a.canonicalizeToken(new AccessPathToken(ap2)), f.getRepresentative(to)); + // collect information for PatternMatcher + const t = to instanceof IntermediateVar && to.label === "import" ? to.node : to instanceof NodeVar? to.node : undefined; // special treatment of 'import' expressions + if (t !== undefined) { + if (ap2 instanceof ModuleAccessPath) + mapGetSet(a.moduleAccessPaths, ap2).add(t); + else if (ap2 instanceof PropertyAccessPath) + mapGetMap(mapGetMap(asn ? a.propertyWriteAccessPaths : a.propertyReadAccessPaths, subap!), ap2.prop).set(t, {bp: ap2, sub: ap2.base}); + else if (ap2 instanceof CallResultAccessPath) + mapGetMap(a.callResultAccessPaths, subap!).set(t, {bp: ap2, sub: ap2.caller}); + else if (ap2 instanceof ComponentAccessPath) + mapGetMap(a.componentAccessPaths, subap!).set(t, {bp: ap2, sub: ap2.component}); + else if (!(ap2 instanceof UnknownAccessPath || ap2 instanceof IgnoredAccessPath)) + assert.fail("Unexpected AccessPath"); + } + } + + /** + * Collects statistics after tokens have been added. + */ + private updateTokenStats(toRep: ConstraintVar) { + const [size] = this.fragmentState.getTokensSize(toRep); + if (size > this.largestTokenSetSize) + this.largestTokenSetSize = size; + } + + /** + * Reports diagnostics periodically (only if print progress is enabled, stdout is tty, and log level is "info"). + */ + private printDiagnostics() { + if (options.printProgress && options.tty && isTTY && logger.level === "info") { + const d = new Date().getTime(); + if (d > this.lastPrintDiagnosticsTime + 100) { // only report every 100ms + this.lastPrintDiagnosticsTime = d; + const a = this.analysisState; + const f = this.fragmentState; + writeStdOut(`Packages: ${a.packageInfos.size}, modules: ${a.moduleInfos.size}, call edges: ${a.numberOfFunctionToFunctionEdges}, ` + + (options.diagnostics ? `vars: ${f.getNumberOfVarsWithTokens()}, tokens: ${f.numberOfTokens}, subsets: ${f.numberOfSubsetEdges}, round: ${this.fixpointRound}, ` : "") + + `iterations: ${this.diagnostics.iterations}, worklists: ${this.unprocessedTokensSize}+${this.unprocessedSubsetEdgesSize}` + + (options.diagnostics ? `, listeners: ${f.postponedListenerCalls.length}` : "")); + a.timeoutTimer.checkTimeout(); + } + } + } + + /** + * Adds a subset constraint. + */ + addSubsetConstraint(from: ConstraintVar | undefined, to: ConstraintVar | undefined) { + if (from === undefined || to === undefined) + return; + if (logger.isDebugEnabled()) + logger.debug(`Adding constraint ${from} \u2286 ${to}`); + const f = this.fragmentState; + this.addSubsetEdge(f.getRepresentative(from), f.getRepresentative(to)); + } + + addSubsetEdge(fromRep: ConstraintVar, toRep: ConstraintVar, propagate: boolean = true) { + if (fromRep !== toRep) { + const f = this.fragmentState; + const s = mapGetSet(f.subsetEdges, fromRep); + if (!s.has(toRep)) { + // add the edge + s.add(toRep); + f.numberOfSubsetEdges++; + if (s.size > this.largestSubsetEdgeOutDegree) + this.largestSubsetEdgeOutDegree = s.size; + mapGetSet(f.reverseSubsetEdges, toRep).add(fromRep); + f.vars.add(fromRep); + f.vars.add(toRep); + if (propagate) { + // enqueue propagation of tokens and notification of listeners + mapGetSet(this.unprocessedSubsetEdges, fromRep).add(toRep); + this.unprocessedSubsetEdgesSize++; + } + } + } + } + + /** + * Provides a unique ID for the given key and node. + */ + private getListenerID(key: TokenListener, n: Node): ListenerID { + let id = (n as any)[this.idSymbol] as number | undefined; + if (id === undefined) { + id = this.nextNodeID++; + (n as any)[this.idSymbol] = id; + } + return (id << 10) + key; + } + + /** + * Adds a universally quantified constraint. + * The constraint variable, the key, and the node must together uniquely determine the function. + */ + addForAllConstraint(v: ConstraintVar | undefined, key: TokenListener, n: Node, listener: (t: Token) => void) { + if (v === undefined) + return; + const f = this.fragmentState; + const vRep = f.getRepresentative(v); + if (logger.isDebugEnabled()) + logger.debug(`Adding universally quantified constraint #${TokenListener[key]} to ${vRep} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + this.addForAllConstraintPrivate(vRep, this.getListenerID(key, n), listener); + } + + private addForAllConstraintPrivate(vRep: ConstraintVar, id: ListenerID, listener: (t: Token) => void) { + const f = this.fragmentState; + const m = mapGetMap(f.tokenListeners, vRep); + if (!m.has(id)) { + // run listener on all existing tokens + for (const t of f.getTokens(vRep)) { + this.enqueueListenerCall([listener, t]); + this.tokenListenerNotifications++; + } + // register listener for future tokens + m.set(id, listener); + } + } + + /** + * Adds a universally quantified constraint for a pair of constraint variables. + * Only allocation site tokens are considered for the first constraint variable, and only function tokens are considered for the second constraint variable. + * Each constraint variable together with the key and the node must uniquely determine the function and the other constraint variable. + */ + addForAllPairsConstraint(v1: ConstraintVar | undefined, v2: ConstraintVar | undefined, key: TokenListener, n: Node, listener: (t1: AllocationSiteToken, t2: FunctionToken) => void) { + if (v1 === undefined || v2 === undefined) + return; + assert(key !== undefined); + const f = this.fragmentState; + const v1Rep = f.getRepresentative(v1); + const v2Rep = f.getRepresentative(v2); + if (logger.isDebugEnabled()) + logger.debug(`Adding universally quantified pair constraint #${TokenListener[key]} to (${v1Rep}, ${v2Rep}) at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + this.addForAllPairsConstraintPrivate(v1Rep, v2Rep, this.getListenerID(key, n), listener); + } + + private addForAllPairsConstraintPrivate(v1Rep: ConstraintVar, v2Rep: ConstraintVar, id: ListenerID, listener: (t1: AllocationSiteToken, t2: FunctionToken) => void) { + const f = this.fragmentState; + const m1 = mapGetMap(f.pairListeners1, v1Rep); + if (!m1.has(id)) { + // run listener on all existing tokens + const funs: Array = []; + for (const t2 of f.getTokens(v2Rep)) + if (t2 instanceof FunctionToken) + funs.push(t2); + for (const t1 of f.getTokens(v1Rep)) + if (t1 instanceof AllocationSiteToken) + for (const t2 of funs) + this.callPairListener(id, listener, t1, t2); + // register listener for future tokens + m1.set(id, [v2Rep, listener]); + mapGetMap(f.pairListeners2, v2Rep).set(id, [v1Rep, listener]); + } + } + + /** + * Enqueues a call to a token pair listener if it hasn't been done before. + */ + private callPairListener(id: ListenerID, listener: (t1: AllocationSiteToken, t2: FunctionToken) => void, t1: AllocationSiteToken, t2: FunctionToken) { + const s = mapGetSet(mapGetMap(this.fragmentState.pairListenersProcessed, id), t1); + if (!s.has(t2)) { + s.add(t2); + this.enqueueListenerCall([listener, [t1, t2]]); + this.pairListenerNotifications++; + } + } + + /** + * Adds a quantified constraint for all neighbors of the given package. + * The PackageInfo and the node must together uniquely determine the function. + */ + addForAllPackageNeighborsConstraint(k: PackageInfo, n: Node, listener: (neighbor: PackageInfo) => void) { + if (logger.isDebugEnabled()) + logger.debug(`Adding package neighbor constraint to ${k}`); + this.addForAllPackageNeighborsConstraintPrivate(k, n, listener); + } + + private addForAllPackageNeighborsConstraintPrivate(k: PackageInfo, n: Node, listener: (neighbor: PackageInfo) => void) { + const f = this.fragmentState; + const m = mapGetMap(f.packageNeighborListeners, k); + if (!m.has(n)) { + // run listener on all existing neighbors + const ns = f.packageNeighbors.get(k); + if (ns) + for (const n of ns) { + this.enqueueListenerCall([listener, n]); + this.packageNeighborListenerNotifications++; + } + // register listener for future neighbors + m.set(n, listener); + } + } + + /** + * Adds a package neighbor relation. + * By default also notifies listeners. + */ + addPackageNeighbor(k1: PackageInfo, k2: PackageInfo, propagate: boolean = true) { + this.addPackageNeighborPrivate(k1, k2, propagate); + this.addPackageNeighborPrivate(k2, k1, propagate); + } + + private addPackageNeighborPrivate(k: PackageInfo, neighbor: PackageInfo, propagate: boolean = true) { + const f = this.fragmentState; + const s = mapGetSet(f.packageNeighbors, k); + if (!s.has(neighbor)) { + s.add(neighbor); + if (propagate) { + const ts = f.packageNeighborListeners.get(k); + if (ts) + for (const listener of ts.values()) { + this.enqueueListenerCall([listener, neighbor]); + this.packageNeighborListenerNotifications++; + } + } + } + } + + /** + * Adds a quantified constraint for all ancestors (reflexive and transitive) of the given token. + * The token and the node must together uniquely determine the function. + */ + addForAllAncestorsConstraint(t: Token, n: Node, listener: (ancestor: Token) => void) { + if (logger.isDebugEnabled()) + logger.debug(`Adding ancestors constraint to ${t}`); + this.addForAllAncestorsConstraintPrivate(t, n, listener); + } + + private addForAllAncestorsConstraintPrivate(t: Token, n: Node, listener: (ancestor: Token) => void) { + const f = this.fragmentState; + const m = mapGetMap(f.ancestorListeners, t); + if (!m.has(n)) { + // run listener on all existing ancestors + for (const a of f.getAncestors(t)) { + const p = mapGetSet(f.ancestorListenersProcessed, n); + if (!p.has(a)) { + this.enqueueListenerCall([listener, a]); + this.ancestorListenerNotifications++; + mapGetSet(f.ancestorListenersProcessed, n).add(a); + } + } + // register listener for future inheritance relations + m.set(n, listener); + } + } + + /** + * Adds an inheritance relation. + * By default also notifies listeners. + */ + addInherits(child: Token, parent: Token, propagate: boolean = true) { + if (child === parent) + return; + const f = this.fragmentState; + const st = mapGetSet(f.inherits, child); + if (!st.has(parent)) { + if (logger.isDebugEnabled()) + logger.debug(`Adding inheritance relation ${child} -> ${parent}`); + st.add(parent); + mapGetSet(f.reverseInherits, parent).add(child); + if (propagate) { + for (const des of f.getDescendants(child)) { + const ts = f.ancestorListeners.get(des); + if (ts) + for (const anc of f.getAncestors(parent)) + for (const [n, listener] of ts) { + const p = mapGetSet(f.ancestorListenersProcessed, n); + if (!p.has(anc)) { + this.enqueueListenerCall([listener, anc]); + this.ancestorListenerNotifications++; + p.add(anc); + } + } + } + } + } + } + + /** + * Adds a quantified constraint for all explicit numeric properties of the given array. + */ + addForAllArrayEntriesConstraint(t: ArrayToken, key: TokenListener, n: Node, listener: (prop: string) => void) { + if (logger.isDebugEnabled()) + logger.debug(`Adding array entries constraint #${TokenListener[key]} to ${t} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + this.addForAllArrayEntriesConstraintPrivate(t, this.getListenerID(key, n), listener); + } + + private addForAllArrayEntriesConstraintPrivate(t: ArrayToken, id: ListenerID, listener: (prop: string) => void) { + const f = this.fragmentState; + const m = mapGetMap(f.arrayEntriesListeners, t); + if (!m.has(id)) { + // run listener on all existing entries + const ps = f.arrayEntries.get(t); + if (ps) + for (const p of ps) { + this.enqueueListenerCall([listener, p]); + this.arrayEntriesListenerNotifications++; + } + // register listener for future entries + m.set(id, listener); + } + } + + /** + * Adds an array numeric property and notifies listeners. + * Non-numeric properties are ignored. + * By default also notifies listeners. + */ + addArrayEntry(a: ArrayToken, prop: string, propagate: boolean = true) { + if (!isArrayIndex(prop)) // TODO: treat large indices as "unknown"? + return; + const f = this.fragmentState; + const ps = mapGetSet(f.arrayEntries, a); + if (!ps.has(prop)) { + if (logger.isDebugEnabled()) + logger.debug(`Adding array entry ${a}[${prop}]`); + ps.add(prop); + if (propagate) { + const ts = f.arrayEntriesListeners.get(a); + if (ts) + for (const listener of ts.values()) { + this.enqueueListenerCall([listener, prop]); + this.arrayEntriesListenerNotifications++; + } + } + } + } + + /** + * Adds a quantified constraint for all properties of the given object. + */ + addForAllObjectPropertiesConstraint(t: ObjectPropertyVarObj, key: TokenListener, n: Node, listener: (prop: string) => void) { + if (logger.isDebugEnabled()) + logger.debug(`Adding object properties constraint #${TokenListener[key]} to ${t} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + this.addForAllObjectPropertiesConstraintPrivate(t, this.getListenerID(key, n), listener); + } + + private addForAllObjectPropertiesConstraintPrivate(t: ObjectPropertyVarObj, id: ListenerID, listener: (prop: string) => void) { + const f = this.fragmentState; + const m = mapGetMap(f.objectPropertiesListeners, t); + if (!m.has(id)) { + // run listener on all existing properties + const ps = f.objectProperties.get(t); + if (ps) + for (const p of ps) { + this.enqueueListenerCall([listener, p]); + this.objectPropertiesListenerNotifications++; + } + // register listener for future properties + m.set(id, listener); + } + } + + /** + * Adds an object property and notifies listeners. + * By default also notifies listeners. + */ + addObjectProperty(a: ObjectPropertyVarObj, prop: string, propagate: boolean = true) { + const f = this.fragmentState; + const ps = mapGetSet(f.objectProperties, a); + if (!ps.has(prop)) { + if (logger.isDebugEnabled()) + logger.debug(`Adding object property ${a}.${prop}`); + ps.add(prop); + if (propagate) { + const ts = f.objectPropertiesListeners.get(a); + if (ts) + for (const listener of ts.values()) { + this.enqueueListenerCall([listener, prop]); + this.objectPropertiesListenerNotifications++; + } + } + } + } + + /** + * Collects property read operations. + * @param result the constraint variable for the result of the property read operation + * @param base the constraint variable for the base expression + * @param pck the current package object token + */ + collectPropertyRead(result: ConstraintVar | undefined, base: ConstraintVar | undefined, pck: PackageObjectToken) { + if (result && base) + this.analysisState.maybeEmptyPropertyReads.push({result, base, pck}); + } + + /** + * Collects dynamic property write operations. + * @param base the constraint variable for the base expression + */ + collectDynamicPropertyWrite(base: ConstraintVar | undefined) { + if (base) + this.analysisState.dynamicPropertyWrites.add(base); + } + + /** + * Redirects constraint variable. + * Updates the subset edges and listeners, and propagates worklist tokens along redirected edges. + * Assumes that there is a subset path from v to rep. + * @param v constraint variable to redirect + * @param rep new representative (possibly v itself if v previously had another representative) + */ + redirect(v: ConstraintVar, rep: ConstraintVar) { + const f = this.fragmentState; + assert(f.vars.has(v)); + const oldRep = f.getRepresentative(v); + if (oldRep === rep) { + // v is already represented by rep + return; + } + if (logger.isDebugEnabled()) + logger.debug(`Redirecting ${v} to ${rep}`); + if (oldRep !== v) { + // v is already redirected, so it shouldn't have any subset edges, listeners or tokens + assert(!f.subsetEdges.has(v)); + assert(!f.reverseSubsetEdges.has(v)); + assert(!f.tokenListeners.has(v)); + assert(!f.pairListeners1.has(v)); + assert(!f.pairListeners2.has(v)); + assert(!f.hasVar(v)); + assert(!this.unprocessedTokens.has(v)); + assert(!this.unprocessedSubsetEdges.has(v)); // also holds for reverse unprocessedSubsetEdges but too costly to assert + } + if (v === rep) { + // v now becomes its own representative, and oldRep !== rep === v so the assertions above apply + f.redirections.delete(v); + f.vars.add(v); + } else { + // set rep as new representative for v + f.redirections.set(v, rep); + if (oldRep === v) { // if v was already redirected, then the assertions above apply + // process v's new outgoing subset edges + const ws = this.unprocessedSubsetEdges.get(v); + if (ws) { + for (const w of ws) + this.processEdge(v, w); + this.unprocessedSubsetEdges.delete(v); + } + // propagate worklist tokens (assuming there is a subset path from v to rep) + this.processTokens(v); + const [size, has] = this.fragmentState.getSizeAndHas(v); + this.fragmentState.deleteVar(v); + f.numberOfTokens -= size; + // redirect subset edges + const repOut = mapGetSet(f.subsetEdges, rep); + const repIn = mapGetSet(f.reverseSubsetEdges, rep); + const vOut = f.subsetEdges.get(v); + if (vOut) { + for (const w of vOut) { + if (w !== rep) { + const qs = f.reverseSubsetEdges.get(w); + assert(qs, "Subset edges empty"); + qs.delete(v); + if (!repOut.has(w)) { + repOut.add(w); + qs.add(rep); + f.numberOfSubsetEdges++; + } + } + } + f.numberOfSubsetEdges -= vOut.size; + f.subsetEdges.delete(v); + } + const vIn = f.reverseSubsetEdges.get(v); + if (vIn) { + for (const w of vIn) + if (w !== rep) { + const qs = f.subsetEdges.get(w); + assert(qs, "Subset edges empty"); + qs.delete(v); + if (!repIn.has(w)) { + repIn.add(w); + qs.add(rep); + f.numberOfSubsetEdges++; + } + } + f.numberOfSubsetEdges -= vIn.size; + f.reverseSubsetEdges.delete(v); + } + repOut.delete(v); + repIn.delete(v); + if (repOut.size === 0) + f.subsetEdges.delete(rep); + if (repIn.size === 0) + f.reverseSubsetEdges.delete(rep); + // redirect listeners, invoke on existing tokens in rep that are not in v + const rts: Set = new Set; + for (const t of f.getTokens(rep)) + if (!has(t)) + rts.add(t); + const tr = f.tokenListeners.get(v); + if (tr) { + const qr = mapGetMap(f.tokenListeners, rep); + for (const [k, listener] of tr) { + qr.set(k, listener); + for (const t of rts) { + this.enqueueListenerCall([listener, t]); + this.tokenListenerNotifications++; + } + } + f.tokenListeners.delete(v); + } + const tr1 = f.pairListeners1.get(v); + if (tr1) { + const bases: Array = []; + for (const t of rts) + if (t instanceof AllocationSiteToken) + bases.push(t); + const qr1 = mapGetMap(f.pairListeners1, rep) + for (const [k, v2l] of tr1) { + qr1.set(k, v2l); + const [v2, listener] = v2l; + for (const t2 of f.getTokens(v2)) + if (t2 instanceof FunctionToken) + for (const t of bases) + this.callPairListener(k, listener, t, t2); + } + f.pairListeners1.delete(v); + } + const tr2 = f.pairListeners2.get(v); + if (tr2) { + const funs: Array = []; + for (const t of rts) + if (t instanceof FunctionToken) + funs.push(t); + const qr2 = mapGetMap(f.pairListeners2, rep) + for (const [k, v1l] of tr2) { + qr2.set(k, v1l); + const [v1, listener] = v1l; + for (const t1 of f.getTokens(v1)) + if (t1 instanceof AllocationSiteToken) + for (const t of funs) + this.callPairListener(k, listener, t1, t); + } + f.pairListeners2.delete(v); + } + f.vars.delete(v); + f.vars.add(rep); + } + } + } + + /** + * Processes the items in the token worklist for the given constraint variable. + */ + processTokens(v: ConstraintVar) { + const ts = this.unprocessedTokens.get(v); + if (ts) { + if (logger.isDebugEnabled()) + logger.debug(`Worklist sizes: ${this.unprocessedTokensSize}+${this.unprocessedSubsetEdgesSize}, propagating ${ts.length} token${ts.length !== 1 ? "s" : ""} from ${v}`); + this.unprocessedTokens.delete(v); + this.unprocessedTokensSize -= ts.length; + // propagate new tokens to successors + const f = this.fragmentState; + assert(f.vars.has(v)); + let s = f.subsetEdges.get(v); + if (s) + for (const to of s) + this.addTokens(ts, to); + this.incrementFixpointIterations(); + } + } + + /** + * Propagates tokens along a new subset edge. + */ + processEdge(fromRep: ConstraintVar, to: ConstraintVar) { + const f = this.fragmentState; + const toRep = f.getRepresentative(to); + const [size, ts] = this.fragmentState.getTokensSize(fromRep); + if (size > 0) { + if (logger.isDebugEnabled()) + logger.debug(`Worklist sizes: ${this.unprocessedTokensSize}+${this.unprocessedSubsetEdgesSize}, propagating ${size} token${size !== 1 ? "s" : ""} from ${fromRep}`); + this.addTokens(ts, toRep); + this.incrementFixpointIterations(); + } + this.unprocessedSubsetEdgesSize--; + } + + incrementFixpointIterations() { + this.diagnostics.iterations++; + if (this.diagnostics.iterations % 100 === 0) { + this.analysisState.timeoutTimer.checkTimeout(); + this.printDiagnostics(); + } + } + + /** + * Processes all items in the worklist until a fixpoint is reached. + * This notifies listeners and propagates tokens along subset edges. + */ + async propagate() { + if (logger.isDebugEnabled()) + logger.debug("Processing constraints until fixpoint..."); + const a = this.analysisState; + const f = this.fragmentState; + a.timeoutTimer.checkTimeout(); + await this.checkAbort(); + let round = 0; + while (this.unprocessedTokens.size > 0 || this.unprocessedSubsetEdges.size > 0 || f.postponedListenerCalls.length > 0) { + round++; + this.fixpointRound = round; + if (logger.isVerboseEnabled()) + logger.verbose(`Fixpoint round: ${round} (call edges: ${a.numberOfFunctionToFunctionEdges}, vars: ${f.getNumberOfVarsWithTokens()}, tokens: ${f.numberOfTokens}, subsets: ${f.numberOfSubsetEdges})`); + if (options.maxRounds !== undefined && round > options.maxRounds) { + a.warn(`Fixpoint round limit reached, aborting propagation`); + this.roundLimitReached++; + this.unprocessedTokensSize = 0; + this.unprocessedTokens.clear(); + this.unprocessedSubsetEdgesSize = 0; + this.unprocessedSubsetEdges.clear(); + f.postponedListenerCalls.length = 0; + break; + } + if (this.unprocessedTokens.size > 0 || this.unprocessedSubsetEdges.size > 0) { + if (options.cycleElimination) { + // find vars that are end points of new subset edges + const nodes = new Set(); + for (const [v, ws] of this.unprocessedSubsetEdges) { + nodes.add(v); + for (const w of ws) + nodes.add(w); + } + // find strongly connected components + const timer1 = new Timer(); + const [reps, repmap] = nuutila(nodes, (v: ConstraintVar) => f.subsetEdges.get(v) || []); + if (logger.isVerboseEnabled()) + logger.verbose(`Cycle detection nodes: ${f.vars.size}, components: ${reps.length}`); + // cycle elimination + for (const [v, rep] of repmap) + this.redirect(v, rep); // TODO: this includes processing pending edges and tokens for v, which may be unnecessary? + this.totalCycleEliminationTime += timer1.elapsedCPU(); + this.totalCycleEliminationRuns++; + // TODO: (transitive reduction:) mark subset edges a->b as "ignored" if there is another path a==>c==>b? then skip those edges when new tokens appear in a (represent ignored edges separately, only look at ignored edges when adding new edges) - detect during cycle detection? + const timer2 = new Timer(); + // process new tokens and subset edges for the component representatives in topological order + for (let i = reps.length - 1; i >= 0; i--) { + const v = reps[i]; + const ws = this.unprocessedSubsetEdges.get(v); + if (ws) + for (const w of ws) + this.processEdge(v, w); + this.processTokens(v); + await this.checkAbort(true); + } + this.unprocessedSubsetEdges.clear(); + // process remaining tokens outside the sub-graph reachable via the new edges + for (const v of this.unprocessedTokens.keys()) + this.processTokens(v); + this.totalPropagationTime += timer2.elapsedCPU(); + } else { + // process all constraint variables and subset edges in worklists until empty + const timer = new Timer(); + for (const [v, ws] of this.unprocessedSubsetEdges) + for (const w of ws) { + this.processEdge(v, w); + await this.checkAbort(true); + } + this.unprocessedSubsetEdges.clear(); + for (const v of this.unprocessedTokens.keys()) { + this.processTokens(v); + await this.checkAbort(true); + } + this.totalPropagationTime += timer.elapsedCPU(); + } + } + if (this.unprocessedTokens.size !== 0 || this.unprocessedTokensSize !== 0 || this.unprocessedSubsetEdges.size !== 0 || this.unprocessedSubsetEdgesSize !== 0) + assert.fail(`worklist non-empty: unprocessedTokens.size: ${this.unprocessedTokens.size}, unprocessedTokensSize: ${this.unprocessedTokensSize}, unprocessedSubsetEdges.size: ${this.unprocessedSubsetEdges.size}, unprocessedSubsetEdgesSize: ${this.unprocessedSubsetEdgesSize}`); + // process all enqueued listener calls (excluding those created during the processing) + if (logger.isVerboseEnabled()) + logger.verbose(`Processing listener calls: ${f.postponedListenerCalls.length}`); + if (f.postponedListenerCalls.length > 0) { + const timer = new Timer(); + this.listenerNotificationRounds++; + const calls = Array.from(f.postponedListenerCalls); + f.postponedListenerCalls.length = 0; + let count = 0; + for (const [fun, args] of calls) { + (fun as Function).apply(undefined, Array.isArray(args) ? args : [args]); + if (++count % 100 === 0) { + a.timeoutTimer.checkTimeout(); + this.printDiagnostics(); + } + } + this.totalListenerCallTime += timer.elapsedCPU(); + } + } + if (this.unprocessedTokensSize !== 0) + assert.fail(`unprocessedTokensSize non-zero after propagate: ${this.unprocessedTokensSize}`); + if (this.unprocessedSubsetEdgesSize !== 0) + assert.fail(`unprocessedSubsetEdgesSize non-zero after propagate: ${this.unprocessedSubsetEdgesSize}`); + if (f.postponedListenerCalls.length > 0) + assert.fail(`postponedListenerCalls non-empty after propagate: ${f.postponedListenerCalls.length}`); + } + + async checkAbort(throttle: boolean = false) { + if (this.abort) { + if (throttle) { + if (this.diagnostics.iterations < this.fixpointIterationsThrottled + 1000) + return; + this.fixpointIterationsThrottled = this.diagnostics.iterations; + } + await setImmediate(); // gives the server a chance to process abort requests + if (this.abort()) { + logger.verbose("Abort signal received"); + throw new AbortedException(); + } + } + } + + /** + * Initializes a new fragment state. + */ + prepare() { + this.fragmentState = new FragmentState(); + } + + /** + * Stores the fragment state in the given module or package. + */ + store(mp: ModuleInfo | PackageInfo) { + if (logger.isDebugEnabled()) + logger.debug(`Storing state for ${mp}`); + mp.fragmentState = this.fragmentState; + } + + /** + * Restores the fragment state for the module or package. + */ + restore(mp: ModuleInfo | PackageInfo, propagate: boolean = true) { // TODO: reconsider use of 'propagate' flag + const s = mp.fragmentState; + if (s) { + if (logger.isDebugEnabled()) + logger.debug(`Restoring state for ${mp}`); + const f = this.fragmentState; + // represent redirections as two-way subset edges, but only if using cycle elimination + if (options.cycleElimination) + for (const [v, rep] of s.redirections) { // Note: because redirections are restored like this and no final cycle elimination is performed, listeners may re-add tokens and subset edges differently depending on choices of SCC representatives + mapGetSet(s.subsetEdges, v).add(rep); + mapGetSet(s.reverseSubsetEdges, rep).add(v); + mapGetSet(s.subsetEdges, rep).add(v); + mapGetSet(s.reverseSubsetEdges, v).add(rep); + } + // copy fragment state + addAll(s.vars, f.vars); + mapSetAddAll(s.ancestorListenersProcessed, f.ancestorListenersProcessed); + for (const [id, m] of s.pairListenersProcessed) + mapSetAddAll(m, mapGetMap(f.pairListenersProcessed, id)); + for (const [v, m] of s.tokenListeners) { + const vRep = f.getRepresentative(v); + for (const [id, listener] of m) + this.addForAllConstraintPrivate(vRep, id, listener); + } + for (const [v1, m] of s.pairListeners1) { + const v1Rep = f.getRepresentative(v1); + for (const [id, [v2, listener]] of m) + this.addForAllPairsConstraintPrivate(v1Rep, f.getRepresentative(v2), id, listener); + } + for (const [k, m] of s.packageNeighborListeners) + for (const [n, listener] of m) + this.addForAllPackageNeighborsConstraintPrivate(k, n, listener); + for (const [t, m] of s.ancestorListeners) + for (const [n, listener] of m) + this.addForAllAncestorsConstraintPrivate(t, n, listener); + for (const [t, m] of s.arrayEntriesListeners) + for (const [id, listener] of m) + this.addForAllArrayEntriesConstraintPrivate(t, id, listener); + for (const [t, m] of s.objectPropertiesListeners) + for (const [id, listener] of m) + this.addForAllObjectPropertiesConstraintPrivate(t, id, listener); + for (const [v, ts] of s.getAllVarsAndTokens()) + this.addTokens(ts, f.getRepresentative(v), propagate); + for (const [v, ws] of s.subsetEdges) { + const vRep = f.getRepresentative(v); + for (const w of ws) + this.addSubsetEdge(vRep, f.getRepresentative(w), propagate); + } + for (const [c, ps] of s.inherits) + for (const p of ps) + this.addInherits(c, p, propagate); + for (const [k, ns] of s.packageNeighbors) + for (const n of ns) + this.addPackageNeighbor(k, n, propagate); + this.printDiagnostics(); + } else if (logger.isVerboseEnabled()) + logger.verbose(`No state found for ${mp}`); + } +} \ No newline at end of file diff --git a/src/analysis/tokens.ts b/src/analysis/tokens.ts new file mode 100644 index 0000000..5b75ab0 --- /dev/null +++ b/src/analysis/tokens.ts @@ -0,0 +1,166 @@ +import {Function, Node} from "@babel/types"; +import {sourceLocationToStringWithFileAndEnd} from "../misc/util"; +import assert from "assert"; +import {ModuleInfo, PackageInfo} from "./infos"; +import {AccessPath} from "./accesspaths"; +import {NativeFunctionAnalyzer} from "../natives/nativebuilder"; + +/** + * Abstract value for constraint variables. + */ +export abstract class Token { + + abstract toString(): string; +} + +/** + * Token that represents function objects. + */ +export class FunctionToken extends Token { + + fun: Function; + + moduleInfo: ModuleInfo; + + constructor(fun: Function, moduleInfo: ModuleInfo) { + super(); + this.fun = fun; + this.moduleInfo = moduleInfo; + } + + toString() { + return `Function[${sourceLocationToStringWithFileAndEnd(this.fun.loc)}]`; + } +} + +/** + * Object kinds, used by AllocationSiteToken and PackageObjectToken. + * + * Iterator represents an abstraction of Iterator, Iterable, IterableIterator, IteratorResult, Generator and AsyncGenerator + * (thus conflating the different kinds of objects). + * It has a property 'value' holding the iterator values and a method 'next' that returns the abstract object itself. + * + * PromiseResolve and PromiseReject represent the resolve and reject function arguments of promise executors, + * using the same allocation site as the promise they belong to. + */ +export type ObjectKind = "Object" | "Array" | "Class" | "Map" | "Set" | "WeakMap" | "WeakSet" | "WeakRef" | "Iterator" | "RegExp" | "Date" | "Promise" | "PromiseResolve" | "PromiseReject"; + +/** + * Token that represents objects with a specific allocation site. + */ +export class AllocationSiteToken extends Token { + + kind: ObjectKind; + + allocSite: Node; + + packageInfo: PackageInfo; + + constructor(kind: ObjectKind, allocSite: Node, packageInfo: PackageInfo) { + super(); + this.kind = kind; + this.allocSite = allocSite; + this.packageInfo = packageInfo; + assert(this instanceof ArrayToken || kind !== "Array", "AllocationSiteTokens of kind Array must be created using ArrayToken"); + assert(this instanceof ObjectToken || kind !== "Object", "AllocationSiteTokens of kind Object must be created using ObjectToken"); + assert(this instanceof ClassToken || kind !== "Class", "AllocationSiteTokens of kind Class must be created using ClassToken"); + } + + toString() { + return `${this.kind}[${sourceLocationToStringWithFileAndEnd(this.allocSite.loc)}]`; + } +} + +/** + * Token that represents ordinary objects with a specific allocation site. + */ +export class ObjectToken extends AllocationSiteToken { + + constructor(allocSite: Node, packageInfo: PackageInfo) { + super("Object", allocSite, packageInfo); + } +} + +/** + * Token that represents arrays with a specific allocation site. + */ +export class ArrayToken extends AllocationSiteToken { + + constructor(allocSite: Node, packageInfo: PackageInfo) { + super("Array", allocSite, packageInfo); + } +} + +/** + * Token that represents classes with a specific allocation site. + */ +export class ClassToken extends AllocationSiteToken { + + constructor(allocSite: Node, packageInfo: PackageInfo) { + super("Class", allocSite, packageInfo); + } +} + +/** + * Token that represents a native object. + */ +export class NativeObjectToken extends Token { + + readonly name: string; + + readonly moduleInfo: ModuleInfo | undefined; + + readonly invoke: NativeFunctionAnalyzer | undefined; + + readonly constr: boolean; + + constructor(name: string, moduleInfo?: ModuleInfo, invoke?: NativeFunctionAnalyzer, constr: boolean = false) { + super(); + this.name = name; + this.moduleInfo = moduleInfo; + this.invoke = invoke; + this.constr = constr; + } + + toString() { + return `%${this.name}${this.moduleInfo ? `[${this.moduleInfo}]` : ""}`; + } +} + +/** + * Token that represents unknown values belonging to a specific package. + */ +export class PackageObjectToken extends Token { + + kind: ObjectKind; + + readonly packageInfo: PackageInfo; + + constructor(packageInfo: PackageInfo, kind: ObjectKind = "Object") { + super(); + this.kind = kind; + this.packageInfo = packageInfo; + } + + toString() { + return `*${this.kind === "Object" ? "" : `(${this.kind})`}[${this.packageInfo}]` + } +} + +/** + * Token that describes values that come from non-analyzed code (either libraries or clients). + * In vulnerability pattern matching mode, access path tokens are used for values from tracked modules. + */ +export class AccessPathToken extends Token { + + readonly ap: AccessPath; + + constructor(ap: AccessPath) { + super(); + this.ap = ap; + } + + toString(): string { + return `@${this.ap}`; + } +} diff --git a/src/analysis/widening.ts b/src/analysis/widening.ts new file mode 100644 index 0000000..933c36b --- /dev/null +++ b/src/analysis/widening.ts @@ -0,0 +1,172 @@ +import Solver from "./solver"; +import {ObjectToken, PackageObjectToken, Token} from "./tokens"; +import logger from "../misc/logger"; +import {ConstraintVar, ObjectPropertyVar} from "./constraintvars"; +import {addAll, getOrSet, mapGetSet} from "../misc/util"; +import {ModuleInfo} from "./infos"; +import Timer from "../misc/timer"; +import {CallResultAccessPath, ComponentAccessPath, PropertyAccessPath} from "./accesspaths"; +import assert from "assert"; + +// TODO: OK to assume that all tokens in widened belong to the current fragment? +// TODO: measure effect of widening... + +/** + * Widens the selected objects from allocation-site to package abstraction. + * This roughly takes time proportional to the size of the information stored for the constraint variables and tokens. + */ +export function widenObjects(m: ModuleInfo, widened: Set, solver: Solver) { + const a = solver.analysisState; + const f = solver.fragmentState; + if (logger.isVerboseEnabled()) + logger.verbose(`Widening (constraint vars: ${f.getNumberOfVarsWithTokens()}, widened tokens: ${widened.size})`); + if (logger.isDebugEnabled()) + for (const t of widened) + logger.debug(`Widening ${t}`); + if (widened.size === 0) + return; + addAll(widened, a.widened); + const timer = new Timer; + + const tokenMap: Map = new Map; + for (const t of widened) + tokenMap.set(t, a.canonicalizeToken(new PackageObjectToken(t.packageInfo))); + + /** + * Returns the widened version of the given token, or the given token itself if it is not being widened. + */ + function widenToken(t: T): T | PackageObjectToken { + return tokenMap.get(t as any) ?? t; + } + + function widenTokenSet(ts: Iterable): Set { + const res: Set = new Set; + for (const t of ts) + res.add(widenToken(t)); + return res; + } + + function widenTokenMapArrayValues(m: Map>): [Map>, number] { + const res: Map> = new Map; + let size = 0; + for (const [v, ts] of m) { + const s = widenTokenSet(ts); + res.set(v, Array.from(s)); + size += s.size; + } + return [res, size]; + } + + function widenTokenMapSetKeys(m: Map>): Map> { + const res: Map> = new Map; + for (const [t, vs] of m) + addAll(vs, mapGetSet(m, widenToken(t))); + return res; + } + + function widenTokenMapSetKeysValues(m: Map>): Map> { + const res: Map> = new Map; + for (const [v, qs] of m) { + const ws = mapGetSet(res, widenToken(v)); + for (const q of qs) + ws.add(widenToken(q)); + } + return res; + } + + function widenTokenMapKeys(m: Map): Map { + const res: Map = new Map; + for (const [t, v] of m) + res.set(widenToken(t), v); // possibly overriding existing different value, but should be a listener function with same behavior + return res; + } + + const varMap: Map = new Map; // cache for widenVar + + /** + * Returns the widened version of the given constraint variable, or the constraint variable itself if it is not being widened. + */ + function widenVar(v: ConstraintVar): ConstraintVar { + if (v instanceof ObjectPropertyVar && v.obj instanceof ObjectToken && widened.has(v.obj)) { + const vobj = v.obj; + return getOrSet(varMap, v, () => a.varProducer.packagePropVar(vobj.packageInfo, v.prop, v.accessor)); + } else + return v; + } + + function widenVarSet(s: Set): Set { + const res: Set = new Set; + for (const v of s) + res.add(widenVar(v)); + return res; + } + + function widenPropertyAccessPath(ap: PropertyAccessPath): PropertyAccessPath { + const w = widenVar(ap.base); + if (w === ap.base) + return ap; + return a.canonicalizeAccessPath(new PropertyAccessPath(w, ap.prop)); + } + + function widenCallResultAccessPath(ap: CallResultAccessPath): CallResultAccessPath { + const w = widenVar(ap.caller); + if (w === ap.caller) + return ap; + return a.canonicalizeAccessPath(new CallResultAccessPath(w)); + + } + + function widenComponentAccessPath(ap: ComponentAccessPath): ComponentAccessPath { + const w = widenVar(ap.component); + if (w === ap.component) + return ap; + return a.canonicalizeAccessPath(new ComponentAccessPath(w)); + + } + + // update the tokens + [solver.unprocessedTokens, solver.unprocessedTokensSize] = widenTokenMapArrayValues(solver.unprocessedTokens); + assert(solver.unprocessedSubsetEdges.size === 0 && solver.unprocessedSubsetEdgesSize === 0); + f.replaceTokens(widenTokenSet); + f.inherits = widenTokenMapSetKeysValues(f.inherits); + f.reverseInherits = widenTokenMapSetKeysValues(f.reverseInherits); + f.ancestorListeners = widenTokenMapKeys(f.ancestorListeners); // FIXME: if a token gets a new ancestor because of widening, ancestor listeners may need to be invoked? + f.objectPropertiesListeners = widenTokenMapKeys(f.objectPropertiesListeners); // FIXME: if an ObjectPropertyVarObj token gets a new property because of widening, object properties listeners may need to be invoked? + f.objectProperties = widenTokenMapSetKeys(f.objectProperties); + + // update the constraint variables + for (const v of f.vars) { + const rep = f.getRepresentative(widenVar(v)); + solver.addSubsetEdge(v, rep); // ensures that tokens get transferred + solver.redirect(v, rep); + } + a.dynamicPropertyWrites = widenVarSet(a.dynamicPropertyWrites); + for (const e of a.maybeEmptyPropertyReads) { + e.result = widenVar(e.result); + e.base = widenVar(e.base); + } + for (const e of a.unhandledDynamicPropertyWrites.values()) + e.src = widenVar(e.src); + for (const m of [a.propertyReadAccessPaths, a.propertyWriteAccessPaths]) + for (const m1 of m.values()) + for (const m2 of m1.values()) + for (const e of m2.values()) { + e.sub = widenVar(e.sub); + e.bp = widenPropertyAccessPath(e.bp); + } + for (const m of a.callResultAccessPaths.values()) + for (const e of m.values()) { + e.sub = widenVar(e.sub); + e.bp = widenCallResultAccessPath(e.bp); + } + for (const m of a.componentAccessPaths.values()) + for (const e of m.values()) { + e.sub = widenVar(e.sub); + e.bp = widenComponentAccessPath(e.bp); + } + + const ms = timer.elapsed(); + solver.totalWideningTime += ms; + if (logger.isVerboseEnabled()) + logger.verbose(`Widening completed in ${ms}ms`); +} diff --git a/src/dynamic/dyn.ts b/src/dynamic/dyn.ts new file mode 100644 index 0000000..2ebf937 --- /dev/null +++ b/src/dynamic/dyn.ts @@ -0,0 +1,342 @@ +/*! DO NOT INSTRUMENT */ + +/** + * Packages that are typically used for tests only and should be excluded in the collected call graphs. + */ +const TEST_PACKAGES = ["yarn", "mocha", "chai", "nyc", "sinon", "should", "@babel", "jest"]; // TODO: other test packages? + +/** + * Commands that do not need instrumentation, for example because the actual work is known to happen in child processes. + */ +const IGNORED_COMMANDS = ["npm", "npm-cli.js", "eslint", "grunt", "tsc", "tsd", "prettier", "rimraf", "xo"]; // TODO: other commands where instrumentation can be skipped? + +// override 'node' executable by overwriting process.execPath and prepending $JELLY_BIN to $PATH +const jellybin = process.env.JELLY_BIN; +const node = `${jellybin}/node`; +if (!jellybin) { + console.error('Error: Environment variable JELLY_BIN not set, aborting'); + process.exit(-1); +} +process.execPath = node; +const child_process = require('child_process'); +for (const fun of ["spawn", "spawnSync"]) { + const real = child_process[fun]; + child_process[fun] = function() { + const env = arguments[2]?.env || process.env; + const opts = + { ...arguments[2], + env: {...env, + PATH: `${jellybin}${env.PATH ? `:${env.PATH}` : ""}`, + NODE: node, + npm_node_execpath: node + }}; + // console.log("jelly:", fun, arguments[0], arguments[1].join(" ")/*, opts*/); // XXX + return real.call(this, arguments[0], arguments[1], opts); + }; +} + +// skip instrumentation of selected commands where actual work is known to happen in child processes +const cmd = process.argv[4]; +for (const s of IGNORED_COMMANDS) + if (cmd.endsWith(`/${s}`)) { + console.log(`jelly: Skipping instrumentation of ${cmd}`); + // @ts-ignore + return; + } + +const outfile = process.env.JELLY_OUT + "-" + process.pid; +if (!outfile) { + console.error('Error: Environment variable JELLY_OUT not set, aborting'); + process.exit(-1); +} + +console.log(`jelly: Running instrumented program: node ${process.argv.slice(4).join(" ")} (process ${process.pid})`); + +import {IID, Jalangi, SourceObject} from "jalangi"; +import fs from "fs"; +import path from "path"; + +declare const J$: Jalangi; + +const fileIds = new Map(); +const files: Array = []; +const fun2fun = new Map>(); +const call2fun = new Map>(); +const functionLocations = new Map(); +const callLocations = new Map(); +const funLocStack = Array(); // top-most is source location of callee function +const inAppStack = Array(); // top-most is true if in app code +let lastCallLoc: IID | null = null; // source location of most recent call site +let enterScriptLocation: SourceObject | undefined = undefined; // if not undefined, next functionEnter enters a module + +// TODO: add edges for implicit calls? calls to/from built-ins? to/from Node.js std.lib? calls to/from eval/Function? async/await? +/** + * Adds a call edge. + */ +function addCallEdge(call: IID | null, callerFun: IID, calleeFun: IID) { + let fs = fun2fun.get(callerFun); + if (!fs) { + fs = new Set; + fun2fun.set(callerFun, fs); + } + fs.add(calleeFun); + if (call) { // excluding call->function edges for implicit calls + let cs = call2fun.get(call); + if (!cs) { + cs = new Set; + call2fun.set(call, cs); + } + cs.add(calleeFun); + } +} + +/** + * Converts NodeProf SourceObject to string representation. + */ +function so2loc(s: SourceObject): string { + let fid = fileIds.get(s.name); + if (fid === undefined) { + fid = files.length; + files.push(s.name); + fileIds.set(s.name, fid); + } + return `${fid}:${s.loc.start.line}:${s.loc.start.column}:${s.loc.end.line}:${s.loc.end.column + 1}`; +} + +/** + * NodeProf instrumentation callbacks. + */ +J$.analysis = { // TODO: super calls not detected (see tests/micro/classes.js) + + /** + * Before function or constructor call. + */ + invokeFunPre: function(iid: IID, _f: Function, _base: Object, _args: any[], _isConstructor: boolean, _isMethod: boolean, _functionIid: IID, _functionSid: IID) { + lastCallLoc = iid; + }, + + /** + * Entering function. + */ + functionEnter: function(iid: IID, _func: Function, _receiver: object, _args: any[]) { + const so = J$.iidToSourceObject(iid); + const calleeEval = "eval" in so; + // determine whether the caller and/or the callee are in app code or in test packages + let callerInApp, calleeInApp; + if (inAppStack.length === 0) { // called from top-level + calleeInApp = !so.name.includes("node_modules/") && !so.name.startsWith("/") && !calleeEval; + callerInApp = false; + } else { + callerInApp = inAppStack.length > 0 && inAppStack[inAppStack.length - 1]; + if (callerInApp) {// called from app code + calleeInApp = !so.name.startsWith("/"); + if (calleeInApp) + for (const w of TEST_PACKAGES) + if (so.name.includes(`node_modules/${w}/`)) { + calleeInApp = false; + break; + } + } else // called from test package + calleeInApp = !so.name.includes("node_modules/") + } + inAppStack.push(calleeInApp); + // register call edge and call location if caller and callee are both in app code and it's an explicit call + if (callerInApp && calleeInApp && funLocStack.length > 0 && lastCallLoc && !calleeEval) { + const cso = J$.iidToSourceObject(lastCallLoc); + const callerEval = "eval" in cso; + if (!callerEval) { + addCallEdge(lastCallLoc, funLocStack[funLocStack.length - 1], iid); + if (!callLocations.has(lastCallLoc)) + callLocations.set(lastCallLoc, so2loc(cso)); + } + } + // register function location if callee is in app code + if (calleeInApp && !calleeEval && !functionLocations.has(iid)) + functionLocations.set(iid, so2loc(enterScriptLocation ?? so)); + // push function location and reset enterScriptLocation + funLocStack.push(iid); + enterScriptLocation = undefined; + lastCallLoc = null; + }, + + /** + * Exiting function. + */ + functionExit: function(_iid: IID, _returnVal, _wrappedExceptionVal) { + funLocStack.pop(); + lastCallLoc = null; + }, + + /** + * New source file loaded, apply workaround to find correct end source location. + */ + newSource: function(sourceInfo: { name: string; internal: boolean, eval?: string }, source: string) { + if ("eval" in sourceInfo) + return; // eval/Function call, not actually a new source file + // find correct end source location + let endLine = 1, last = 0; + for (let i = 0; i < source.length; i++) { + if (source[i] === '\n') { + endLine++; + last = i + 1; + } + } + const endColumn = source.length - last; + enterScriptLocation = { + name: sourceInfo.name, + loc: { + start: { line: 1, column: 1 }, + end: { line: endLine, column: endColumn } + } + }; + }, + + /** + * Before call to 'eval'. + */ + evalPre(iid: IID, str: string) { + lastCallLoc = null; + // TODO: record extra information about eval/Function calls? see --callgraph-native-calls + }, + + // evalPost(iid: IID, str: string) { + // // TODO? + // }, + + /** + * Before call to 'Function'. + */ + evalFunctionPre(iid: IID, func: Function, receiver: Object, args: any) { + lastCallLoc = null; + // TODO? + }, + + // evalFunctionPost(iid: IID, func: Function, receiver: Object, args: any, ret: any) { + // // TODO? + // }, + + /** + * Before call to built-in function. + */ + builtinEnter(name: string, f: Function, dis: any, args: any) { + lastCallLoc = null; + // TODO: record extra information about calls to built-ins? see --callgraph-native-calls and --callgraph-require-calls + }, + + // builtinExit(name: string, returnVal: any) { + // // TODO? + // }, + // + // asyncFunctionEnter(iid: IID) { + // // TODO: record extra information about async calls? see --callgraph-native-calls + // }, + // + // asyncFunctionExit(iid: IID, returnVal: any, wrappedException) { + // // TODO? + // }, + // + // awaitPre(iid: IID, valAwaited: any) { + // // TODO: record extra information about awaits? + // }, + // + // awaitPost(iid: IID, valAwaited: any, result: any, rejected: boolean) { + // // TODO? + // }, + + /** + * Before property read operation (which may trigger implicit call). + */ + getFieldPre(iid: IID, base: any, offset: any, isComputed: boolean, isOpAssign: boolean, isMethodCall: boolean): { base: any; offset: any; skip: boolean } | void { + lastCallLoc = null; + // TODO: detect implicit calls to getters? see --callgraph-implicit-calls + }, + + // getField(iid: IID, base: any, offset: any, val: any, isComputed: boolean, isOpAssign: boolean, isMethodCall: boolean): { result: any } | void { + // // TODO? + // }, + + /** + * Before property write operation (which may trigger implicit call). + */ + putFieldPre(iid: IID, base: any, offset: any, val: any, isComputed: boolean, isOpAssign: boolean): { base: any; offset: any; val: any; skip: boolean } | void { + lastCallLoc = null; + // TODO: detect implicit calls to setters? see --callgraph-implicit-calls + }, + + // putField(iid: IID, base: any, offset: any, val: any, isComputed: boolean, isOpAssign: boolean): { result: any } | void { + // // TODO? + // }, + + /** + * Before unary operator (which may trigger implicit call). + */ + unaryPre(iid: IID, op: string, left: any): { op: string; left: any; skip: boolean } | void { + lastCallLoc = null; + // TODO: detect implicit calls to valueOf? see --callgraph-implicit-calls + }, + + // unary(iid: IID, op: string, left: any, result: any): { result: any } | void { + // // TODO? + // }, + + /** + * Before binary operator (which may trigger implicit call). + */ + binaryPre(iid: IID, op: string, left: any, right: any, isOpAssign: boolean, isSwitchCaseComparison: boolean, isComputed: boolean): { op: string; left: any; right: any; skip: boolean } | void { + lastCallLoc = null; + // TODO: detect implicit calls to valueOf/toString? see --callgraph-implicit-calls + }, + + // binary(iid: IID, op: string, left: any, right: any, result: any, isOpAssign: boolean, isSwitchCaseComparison: boolean, isComputed: boolean): { result: any } | void { + // // TODO? + // } +}; + +/** + * Program exit, write call graph to JSON file. + * (Note, funLocStack is nonempty if exit happens because of process.exit.) + */ +process.on('exit', function() { + if (files.length === 0) { + console.log(`jelly: No relevant files detected for process ${process.pid}, skipping file write`); + return; + } + // console.log(`jelly: Writing ${outfile}`); + const fd = fs.openSync(outfile, "w"); + fs.writeSync(fd, `{\n "entries": [${JSON.stringify(path.relative(process.cwd(), process.argv[1]))}],\n`); + fs.writeSync(fd, ` "time": "${new Date().toUTCString()}",\n`); + fs.writeSync(fd, ` "files": [`); + let first = true; + for (const file of files) { + fs.writeSync(fd, `${first ? "" : ","}\n ${JSON.stringify(file)}`); + first = false; + } + fs.writeSync(fd, `\n ],\n "functions": {`); + first = true; + for (const [iid, loc] of functionLocations) { + fs.writeSync(fd, `${first ? "" : ","}\n "${iid}": ${JSON.stringify(loc)}`); + first = false; + } + fs.writeSync(fd, `\n },\n "calls": {`); + first = true; + for (const [iid, loc] of callLocations) { + fs.writeSync(fd, `${first ? "" : ","}\n "${iid}": ${JSON.stringify(loc)}`); + first = false; + } + fs.writeSync(fd, `\n },\n "fun2fun": [`); + first = true; + for (const [callerFun, callees] of fun2fun) + for (const callee of callees) { + fs.writeSync(fd, `${first ? "\n " : ", "}[${callerFun}, ${callee}]`); + first = false; + } + fs.writeSync(fd, `${first ? "" : "\n "}],\n "call2fun": [`); + first = true; + for (const [call, callees] of call2fun) + for (const callee of callees) { + fs.writeSync(fd, `${first ? "\n " : ", "}[${call}, ${callee}]`); + first = false; + } + fs.writeSync(fd, `${first ? "" : "\n "}]\n}\n`); + fs.closeSync(fd); +}); diff --git a/src/dynamic/soundnesstester.ts b/src/dynamic/soundnesstester.ts new file mode 100644 index 0000000..65e9201 --- /dev/null +++ b/src/dynamic/soundnesstester.ts @@ -0,0 +1,162 @@ +import {readFileSync} from "fs"; +import {DummyModuleInfo, FunctionInfo, ModuleInfo} from "../analysis/infos"; +import logger from "../misc/logger"; +import {arrayToString, percent, sourceLocationToStringWithFile, sourceLocationToStringWithFileAndEnd} from "../misc/util"; +import {CallGraph} from "callgraph"; +import {AnalysisState} from "../analysis/analysisstate"; +import path from "path"; +import {options} from "../options"; + +/** + * Performs soundness testing of the analysis result using the given dynamic call graph. + * @return [number of dynamic function->function call edges matched, + * total number of dynamic function->function call edges, + * number of dynamic call->function call edges matched, + * total number of dynamic call->function call edges] + */ +export function testSoundness(jsonfile: string, analysisState: AnalysisState): [number, number, number, number] { + + // collect all static functions including module top-level functions (excluding aliases) + const staticFunctions = new Map(); + for (const m of analysisState.moduleInfos.values()) + if (m.loc) // TODO: m.loc may be empty with --ignore-dependencies + staticFunctions.set(`${m.path}:${m.loc.start.line}:${m.loc.start.column + 1}:${m.loc.end.line}:${m.loc.end.column + 1}`, m); + for (const f of analysisState.functionInfos.values()) + if (!f.loc || "nodeIndex" in f.loc) + analysisState.warn(`Source location missing for function ${f.name || ""} in ${f.moduleInfo.path}`); + else + staticFunctions.set(`${f.moduleInfo.path}:${f.loc.start.line}:${f.loc.start.column + 1}:${f.loc.end.line}:${f.loc.end.column + 1}`, f); + + // log static locations + if (logger.isDebugEnabled()) { + logger.debug(`Static files: ${arrayToString(Array.from(analysisState.moduleInfos.keys()), "\n ")}`); + logger.debug(`Static functions: ${arrayToString(Array.from(staticFunctions.keys()), "\n ")}`); + logger.debug(`Static calls: ${arrayToString(Array.from(analysisState.callLocations), "\n ")}`); + logger.debug(`Ignored functions: ${arrayToString(analysisState.artificialFunctions.map(([, n]) => sourceLocationToStringWithFile(n.loc)), "\n ")}`); + // TODO: optionally ignore files that haven't been analyzed? (relevant with --ignore-dependencies) + } + + // load the dynamic call graph + const dyn = JSON.parse(readFileSync(jsonfile, "utf8")) as CallGraph; + + // collect dynamic files and check that they have been analyzed + const dynamicFiles = new Map(); + for (const file of dyn.files) { + const f = path.resolve(options.basedir, file); + dynamicFiles.set(dynamicFiles.size, f); + if (!analysisState.moduleInfos.has(f)) + logger.warn(`File ${file} not found in static call graph`); + // else + // logger.debug(`Found file ${file}`); + } + + // finds the representative for the given source location + function findRepresentativeLocation(loc: string): string { + const c = loc.indexOf(":"); + const file = dynamicFiles.get(Number(loc.substring(0, c)))!; + const rest = loc.substring(c + 1); + const m = analysisState.moduleInfos.get(file); + return `${m ? m.path : file}:${rest}`; + } + + // collect dynamic functions and check that they have been analyzed + const dynamicFunctionLocs = new Map(); + const dynamicFunctions = new Map(); + const ignoredFunctions = new Set(); + for (const [,n] of analysisState.artificialFunctions) + ignoredFunctions.add(sourceLocationToStringWithFile(n.loc) + ":"); + const comp = ([, loc1]: [string, string], [, loc2]: [string, string]) => loc1 < loc2 ? -1 : loc1 > loc2 ? 1 : 0; + for (const [f, loc] of Object.entries(dyn.functions).sort(comp)) { + const reploc = findRepresentativeLocation(loc); + dynamicFunctionLocs.set(Number(f), reploc); + const fun = staticFunctions.get(reploc); + if (!fun) { + if (!ignoredFunctions.has(reploc.substring(0, reploc.lastIndexOf(":", reploc.lastIndexOf(":") - 1) + 1))) // dyn.ts sometimes reports incorrect end locations, so we only consider start locations + logger.warn(`Function ${reploc} not found in static call graph`); + else + logger.debug(`Function ${reploc} from dynamic call graph ignored`); // filtering away artificial call edges reported by dyn.ts + } else { + dynamicFunctions.set(Number(f), fun); + // logger.debug(`Found function ${loc}`); + } + } + + // collect dynamic calls and check whether they have been analyzed + const callStrLocations = new Set(); + for (const n of analysisState.callLocations) + callStrLocations.add(sourceLocationToStringWithFileAndEnd(n.loc)); + const dynamicCallLocs = new Map(); + for (const [f, loc] of Object.entries(dyn.calls).sort(comp)) { + const reploc = findRepresentativeLocation(loc); + dynamicCallLocs.set(Number(f), reploc); + if (!callStrLocations.has(reploc)) + logger.warn(`Call ${reploc} not found in static call graph`); + } + + const warnings: Array = []; + + // check fun2fun edges + let found1 = 0, missed1 = 0; + for (const [from, to] of dyn.fun2fun) { + const callerFun = dynamicFunctions.get(from); + const calleeFun = dynamicFunctions.get(to); + let found = false; + if (callerFun && calleeFun) { + if (calleeFun instanceof FunctionInfo) { + const fs = analysisState.functionToFunction.get(callerFun); + if (fs && fs.has(calleeFun)) + found = true; + } else { + const ms = analysisState.requireGraph.get(callerFun); + if (ms && ms.has(calleeFun)) + found = true; + } + } + if (found) + found1++; + else { + warnings.push(`Call edge missing in static call graph: function ${dynamicFunctionLocs.get(from)} -> function ${dynamicFunctionLocs.get(to)}`); + missed1++; + } + } + const total1 = found1 + missed1; + + // check call2fun edges + const callStrToFunction = new Map>(); + for (const [n, s] of analysisState.callToFunction) + callStrToFunction.set(sourceLocationToStringWithFileAndEnd(n.loc), s); + const callStrToModule = new Map>(); + for (const [n, s] of analysisState.callToModule) + callStrToModule.set(sourceLocationToStringWithFileAndEnd(n.loc), s) + let found2 = 0, missed2 = 0; + for (const [from, to] of dyn.call2fun) { + const callLoc = dynamicCallLocs.get(from); + const calleeFun = dynamicFunctions.get(to); + let found = false; + if (callLoc && calleeFun) { + if (calleeFun instanceof FunctionInfo) { + const fs = callStrToFunction.get(callLoc); + if (fs && fs.has(calleeFun)) + found = true; + } else { + const ms = callStrToModule.get(callLoc); + if (ms && ms.has(calleeFun)) + found = true; + } + } + if (found) + found2++; + else { + warnings.push(`Call edge missing in static call graph: call ${dynamicCallLocs.get(from)} -> function ${dynamicFunctionLocs.get(to)}`); + missed2++; + } + } + const total2 = found2 + missed2; + + // report and return results + for (const m of warnings.sort()) + logger.warn(m); + logger.info(`Dynamic function->function call edges matched: ${found1}/${total1}${total1 > 0 ? ` (recall: ${percent(found1 / total1)})` : ""}`); + logger.info(`Dynamic call->function call edges matched: ${found2}/${total2}${total2 > 0 ? ` (recall: ${percent(found2 / total2)})` : ""}`); + return [found1, total1, found2, total2]; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..156c201 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,353 @@ +#!/usr/bin/env node + +import {analyzeFiles} from "./analysis/analyzer"; +import {closeSync, openSync, readdirSync, readFileSync, unlinkSync} from "fs"; +import {program} from "commander"; +import logger, {logToFile, setLogLevel} from "./misc/logger"; +import {testSoundness} from "./dynamic/soundnesstester"; +import {options, setDefaultTrackedModules, setOptions, setPatternProperties} from "./options"; +import {spawnSync} from "child_process"; +import path from "path"; +import {autoDetectBaseDir, expand, writeStreamedStringify} from "./misc/files"; +import {tapirPatternMatch} from "./patternmatching/tapirpatterns"; +import {toDot} from "./output/graphviz"; +import {AnalysisStateReporter} from "./output/analysisstatereporter"; +import {TypeScriptTypeInferrer} from "./typescript/typeinferrer"; +import {getAPIUsage, reportAPIUsage} from "./patternmatching/apiusage"; +import { + convertTapirPatterns, + getGlobs, + getProperties, + loadTapirDetectionPatternFiles, + removeObsoletePatterns +} from "./patternmatching/patternloader"; +import {compareCallGraphs} from "./output/compare"; +import {getMemoryLimit} from "./misc/memory"; +import Solver from "./analysis/solver"; +import {exportCallGraphHtml, exportDataFlowGraphHtml} from "./output/visualizer"; +import {VulnerabilityDetector, VulnerabilityResults} from "./patternmatching/vulnerabilitydetector"; +import {Vulnerability} from "vulnerabilities"; +import {addAll} from "./misc/util"; +import {getAPIExported, reportAccessPaths, reportAPIExportedFunctions} from "./patternmatching/apiexported"; +import {merge} from "./output/merge"; +import {CallGraph} from "callgraph"; + +const VERSION = require("../package.json").version; +const PKG = "pkg" in process; + +program + .name("jelly") + .version(VERSION) + .addHelpText("before", "Copyright (C) 2023 Anders Møller\n") + .option("-b, --basedir ", "base directory for files to analyze (default: auto-detect)") + .option("-f, --logfile ", "log to file (default: log to stdout)") + .option("-l, --loglevel ", "log level (debug/verbose/info/warn/error)", "info") + .option("-i, --timeout ", "limit analysis time") + .option("-a, --dataflow-html ", "save data-flow graph as HTML file") + .option("-m, --callgraph-html ", "save call graph as HTML file") + .option("-j, --callgraph-json ", "save call graph as JSON file") + .option("-s, --soundness ", "compare with dynamic call graph") + .option("-n, --graal-home ", "home of graal-nodejs (default: $GRAAL_HOME)") + .option("-d, --dynamic ", "generate call graph dynamically, no static analysis") + .option("-e, --exclude ", "files to exclude when specifying entry directories") + .option("-p, --patterns ", "files containing API usage patterns to detect") + .option("-v, --vulnerabilities ", "report vulnerability matches") + // .option("-g, --callgraph-graphviz ", "save call graph as Graphviz dot file") // TODO: graphviz output disabled for now + .option("--npm-test ", "run 'npm test' instead of 'node' (use with -d)") + // .option("--graphviz-packages ", "packages to include in Graphviz dot file (use with -g)") + // .option("--graphviz-elide-functions", "elide functions (use with -g)") + // .option("--graphviz-dependencies-only", "show module dependencies only (use with -g)") + .option("--tokens-json ", "save tokens for constraint variables as JSON file") + .option("--tokens", "report tokens for constraint variables") + .option("--largest", "report largest token sets and subset relations") + // .option("--bottom-up", "analyze bottom-up in package dependency structure") // TODO: bottom-up analysis disabled for now + .option("--no-alloc", "disable light-weight allocation site abstraction") + .option("--no-widening", "disable widening") + .option("--no-patch-dynamics", "disable patching of dynamic property accesses") + .option("--no-cycle-elimination", "disable cycle elimination") + .option("--no-natives", "disable nonessential models of native libraries") + .option("--skip-graal-test", "skip graal-nodejs test (use with -d)") + .option("--ignore-dependencies", "don't include dependencies in analysis") + .option("--ignore-unresolved", "don't report errors about unresolved modules") + .option("--no-print-progress", "don't print analysis progress information") + .option("--no-tty", "don't print solver progress for TTY") + .option("--warnings-unsupported", "print warnings about unsupported features") + .option("--gc", "enable garbage collection for more accurate memory usage reporting") + .option("--typescript", "enable TypeScript type inference (use with -p)") + .option("--api-usage", "report API usage of external packages (implies --ignore-dependencies)") + .option("--api-exported", "report API of modules") + .option("--find-access-paths ", "find access paths for source location (file:line)") + .option("--higher-order-functions", "report higher-order functions") + .option("--zeros", "report calls with zero callees and functions with zero callers") + .option("--tracked-modules ", "modules to track usage of (default: empty unless using -p, -v or --api-usage)") + .option("--no-callgraph-implicit", "omit implicit calls in call graph") // TODO: not yet including implicit valueOf/toString calls + .option("--no-callgraph-native", "omit native calls in call graph") // TODO: not yet including the native functions themselves, only callbacks from native functions + .option("--no-callgraph-require", "omit module loading in call graph") // TODO: currently works only for modules that are resolved successfully (and included even if --ignore-dependencies is used)? + .option("--no-callgraph-external", "omit heuristic external callbacks in call graph") + .option("--diagnostics", "report internal analysis diagnostics") + .option("--diagnostics-json ", "save analysis diagnostics in JSON file") + .option("--variable-kinds", "report constraint variable kinds") + .option("--max-rounds ", "limit number of fixpoint rounds for each module and package") + .option("--typescript-library-usage ", "save TypeScript library usage in JSON file, no analysis") + .option("--modules-only", "report reachable packages and modules only, no analysis") + .option("--compare-callgraphs", "compare two call graphs given as JSON files, no analysis") + .usage("[options] [files]") + .addHelpText("after", + "\nAll modules reachable by require/import from the given files are included in the analysis\n" + + "(except when using --ignore-dependencies). If specifying directories instead\n" + + "of files, the files in the directories and their subdirectories are used as entry points.\n" + + "The special argument -- can be used indicate end of options, typically after multi-argument options.\n" + + `Memory limit is ${getMemoryLimit()}MB.${PKG ? "" : " Change with, for example: NODE_OPTIONS=--max-old-space-size=4096"}`) + .action(main) + .showHelpAfterError() + .parse(); + +async function main() { + options.tty = true; + setOptions(program.opts()); + if (options.logfile) + logToFile(options.logfile); + setLogLevel(options.loglevel); + + if (PKG) + for (const opt of ["dynamic"] as const) + if (options[opt]) { + logger.error(`Error: Option --${opt} not available in binary executable`); + process.exitCode = -1; + return; + } + + if (options.compareCallgraphs) { + if (program.args.length !== 2) { + logger.error(`Error: Option --compare-callgraphs expects two files`); + process.exitCode = -1; + return; + } + compareCallGraphs(program.args[0], program.args[1]); + return; + } + + if (options.patterns && options.vulnerabilities) { // TODO: also check this in server.ts + logger.error(`Error: Options --patterns and --vulnerabilities cannot be used together`); // pattern match confidence computation requires relevant libraries to be external + process.exitCode = -1; + return; + } + + if (options.gc && typeof gc !== "function") { + // restart with node option --expose-gc if --gc is enabled + const args = process.argv.slice(1); + args.unshift("--expose-gc"); + const t = spawnSync(process.execPath, args, {stdio: "inherit"}); + if (t.status === null) { + logger.error("Error: Unable to restart with --expose-gc"); + process.exitCode = -1; + } else + process.exitCode = t.status; + return; + } + + if (options.dynamic) { + + const graalHome = options.graalHome || process.env.GRAAL_HOME; + const node = graalHome ? path.resolve(graalHome, "bin/node") : "node"; + if (!options.skipGraalTest) { + logger.info("Testing graal-nodejs") + const t = spawnSync(node, ["-e", "process.exit(typeof Graal === 'object' ? 0 : -1)"]); + if (t.status === null) { + logger.error(`Error: Unable to execute ${node}`); + process.exitCode = -1; + return; + } + if (t.status !== 0) { + logger.error("Error: 'node' is not graal-nodejs, try option --graal-home or environment variable GRAAL_HOME"); + process.exitCode = -1; + return; + } + } + logger.info("Generating dynamic call graph"); + let cmd, args, cwd; + if (options.npmTest) { + cmd = "npm"; + args = ["test", ...program.args]; + cwd = path.resolve(options.npmTest) + } else { + if (program.args.length === 0) { + logger.info("File missing, aborting"); + return; + } + cmd = `${__dirname}/bin/node`; + args = program.args; + cwd = process.cwd() + } + const dyn = path.resolve(options.dynamic); + const t = spawnSync(cmd, args, { + stdio: "inherit", + cwd, + env: { + ...process.env, + JELLY_OUT: dyn, + GRAAL_HOME: graalHome ? path.resolve(graalHome) : undefined, + PATH: `${__dirname}/bin${path.delimiter}${process.env.PATH}` + } + }); + if (t.status === null) { + logger.error(`Error: Unable to execute ${cmd}`); + process.exitCode = -1; + return; + } + + const dir = path.dirname(dyn); + const cgs: Array = []; + for (const f of readdirSync(dir, {withFileTypes: true})) + if (f.isFile()) { + const p = path.resolve(dir, f.name); + if (p.startsWith(`${dyn}-`)) { // instrumented execution has produced $JELLY-OUT- files + cgs.push(JSON.parse(readFileSync(p, 'utf-8')) as CallGraph); + unlinkSync(p); + } + } + const fd = openSync(dyn, "w"); + writeStreamedStringify(merge(cgs), fd); // TODO: alert if the call graph is empty? + closeSync(fd); + logger.info(`Dynamic call graph written to ${dyn}`); + + } else { + + if (program.args.length === 0) { + logger.info("No files to analyze (use --help to see usage)"); + return; + } + + if (!autoDetectBaseDir(program.args)) + return; + const files = expand(program.args); + if (logger.isVerboseEnabled()) { + logger.verbose("Entry files:"); + for (const file of files) + logger.verbose(` ${file}`); + } + + if (options.typescriptLibraryUsage) { + + const ts = new TypeScriptTypeInferrer(files); + const fd = openSync(options.typescriptLibraryUsage, "w"); + writeStreamedStringify(ts.libraryUsageToJSON(ts.getLibraryUsage()), fd); + closeSync(fd); + logger.info(`TypeScript library usage written to ${options.typescriptLibraryUsage}`); + + } else { + + let tapirPatterns, patterns, globs, props, vulnerabilityDetector; + if (options.patterns) { + tapirPatterns = removeObsoletePatterns(loadTapirDetectionPatternFiles(options.patterns)); + patterns = convertTapirPatterns(tapirPatterns); + globs = getGlobs(patterns); + props = getProperties(patterns); + } + if (options.vulnerabilities) { + logger.info(`Loading vulnerability patterns from ${options.vulnerabilities}`); + vulnerabilityDetector = new VulnerabilityDetector(JSON.parse(readFileSync(options.vulnerabilities, "utf8")) as Array); // TODO: use when setting globs and props? (see also server.ts) + const ps = vulnerabilityDetector.getPatterns(); + addAll(getGlobs(ps), (globs = (globs ?? new Set()))); + addAll(getProperties(ps), (props = (props ?? new Set()))); + } + + setDefaultTrackedModules(globs); + setPatternProperties(options.apiUsage ? undefined : (props || new Set())); + + const solver = new Solver(); + const a = solver.analysisState; + a.vulnerabilities = vulnerabilityDetector; + await analyzeFiles(files, solver); + const f = solver.fragmentState; + const out = new AnalysisStateReporter(a, f); + + let typer: TypeScriptTypeInferrer | undefined; + if (options.typescript) + typer = new TypeScriptTypeInferrer(files); + + let vr: VulnerabilityResults = {}; + if (vulnerabilityDetector) { + vr.package = vulnerabilityDetector.findPackagesThatMayDependOnVulnerablePackages(a); + vr.module = vulnerabilityDetector.findModulesThatMayDependOnVulnerableModules(a); + vr.function = vulnerabilityDetector.findFunctionsThatMayReachVulnerableFunctions(a); + vr.call = vulnerabilityDetector.findCallsThatMayReachVulnerableFunctions(a, vr.function); + vulnerabilityDetector.reportResults(a, vr); + vr.matches = vulnerabilityDetector.patternMatch(a, f, typer, solver.diagnostics); + // TODO: find functions that may reach functions in vulnerabilities.matches + } + + if (options.callgraphHtml) { + const file = options.callgraphHtml; + exportCallGraphHtml(a, file, vr); + logger.info(`Call graph written to ${file}`); + } + + if (options.dataflowHtml) { + const file = options.dataflowHtml; + exportDataFlowGraphHtml(a, f, file); // TODO: also show pattern matches and reachability + logger.info(`Data-flow graph written to ${file}`); + } + + if (options.callgraphGraphviz) { + const file = options.callgraphGraphviz; + const fd = openSync(file, "w"); + toDot(a, fd); + closeSync(fd); + logger.info(`Call graph written to ${file}`); + } + + if (options.tokens) + out.reportTokens(); + + if (options.tokensJson) + out.saveTokens(options.tokensJson); + + if (options.largest) { + out.reportLargestSubsetEdges(); + out.reportLargestTokenSets(); + } + + if (options.callgraphJson) + out.saveCallGraph(options.callgraphJson, files); + + if (options.diagnosticsJson) + out.saveDiagnostics(solver.diagnostics, options.diagnosticsJson); + + if (options.modulesOnly) + out.reportReachablePackagesAndModules(); + + if (options.soundness) + testSoundness(options.soundness, a); + + if (tapirPatterns && patterns) + tapirPatternMatch(tapirPatterns, patterns, a, f, typer, undefined, solver.diagnostics); + + if (options.apiUsage) { + const [r1, r2] = getAPIUsage(a); + reportAPIUsage(r1, r2); + } + + if (options.apiExported || options.findAccessPaths) { + const r = getAPIExported(a, f); + if (options.apiExported) + reportAPIExportedFunctions(r); + if (options.findAccessPaths) + reportAccessPaths(a, r, options.findAccessPaths); + } + + if (options.higherOrderFunctions) + out.reportHigherOrderFunctions(); + + if (options.zeros) { + const funs = out.getZeroCallerFunctions(); + out.reportZeroCallerFunctions(funs); + const calls = out.getZeroCalleeCalls(); + out.reportZeroCalleeCalls(calls); + } + + if (options.variableKinds) + out.reportVariableKinds(); + } + } +} \ No newline at end of file diff --git a/src/misc/asthelpers.ts b/src/misc/asthelpers.ts new file mode 100644 index 0000000..aed26cf --- /dev/null +++ b/src/misc/asthelpers.ts @@ -0,0 +1,131 @@ +import { + CallExpression, + Class, + ClassAccessorProperty, + ClassMethod, + ClassPrivateMethod, + ClassPrivateProperty, + ClassProperty, + Expression, + Identifier, + ImportDefaultSpecifier, + ImportSpecifier, + isCallExpression, + isClassPrivateProperty, + isExpression, + isExpressionStatement, + isIdentifier, + isImportSpecifier, + isJSXMemberExpression, + isMemberExpression, + isNumericLiteral, + isOptionalMemberExpression, + isParenthesizedExpression, + isStringLiteral, + JSXMemberExpression, + MemberExpression, + NewExpression, + ObjectMethod, + ObjectProperty, + OptionalCallExpression, + OptionalMemberExpression, + StringLiteral +} from "@babel/types"; +import {NodePath} from "@babel/traverse"; +import {CallNodePath} from "../natives/nativebuilder"; + +/** + * Finds the property name of a property access, returns undefined if dynamic and not literal string or number. + * (See also getKey below.) + */ +export function getProperty(node: MemberExpression | OptionalMemberExpression | JSXMemberExpression): string | undefined { + if (isJSXMemberExpression(node)) + return node.property.name; + else if (isIdentifier(node.property) && !node.computed) + return node.property.name; + else if (isStringLiteral(node.property)) + return node.property.value; + else if (isNumericLiteral(node.property)) + return node.property.value.toString(); + return undefined; +} + +/** + * Finds the property name of an object/class property/method definition, returns undefined if dynamic and not literal string or number. + * (See also getProperty above.) + */ +export function getKey(node: ObjectProperty | ClassProperty | ClassAccessorProperty | ClassPrivateProperty | ObjectMethod | ClassMethod | ClassPrivateMethod): string | undefined { + if (isClassPrivateProperty(node)) + return node.key.id.name; + if (isIdentifier(node.key) && !node.computed) + return node.key.name; + else if (isStringLiteral(node.key)) + return node.key.value; + else if (isNumericLiteral(node.key)) + return node.key.value.toString(); + return undefined; +} + +/** + * Checks whether the parent node (possibly in parentheses) is an expression statement. + */ +export function isParentExpressionStatement(path: NodePath): boolean { // TODO: also include nodes that are non-last in expression sequences? + let p: NodePath | null = path; + do { + p = p.parentPath; + } while (p && isParenthesizedExpression(p)); + return p !== null && isExpressionStatement(p.node); +} + +/** + * Returns the base expression and property of the given method call, or undefined if not applicable. + */ +export function getBaseAndProperty(path: CallNodePath): {base: Expression, property: MemberExpression["property"]} | undefined { + let p: NodePath | null = path.get("callee") as NodePath; + while (isParenthesizedExpression(p.node)) + p = p.get("expression") as NodePath; + if (!(isMemberExpression(p.node) || isOptionalMemberExpression(p.node))) + return undefined; + let base = p.node.object; + if (!isExpression(base)) // excluding Super + return undefined; + let property = p.node.property; + return {base, property}; +} + +/** + * Finds the exported property name for an export specifier. + */ +export function getExportName(exported: Identifier | StringLiteral): string { + return isIdentifier(exported) ? exported.name : exported.value; +} + +/** + * Finds the imported property name for an import specifier. + */ +export function getImportName(imp: ImportSpecifier | ImportDefaultSpecifier): string { + return isImportSpecifier(imp) ? isIdentifier(imp.imported) ? imp.imported.name : imp.imported.value : "default"; +} + +/** + * Finds the enclosing ClassDeclaration or ClassExpression of the given node path. + */ +export function getClass(path: NodePath): Class | undefined { + return (path.find((p) => p.isClass()) as NodePath)?.node; +} + +/** + * Returns true if the given node may be used as a Promise. + * If the node is a callee in a call node or the receiver in a property read that is not 'then' or 'catch', + * then false is returned, and otherwise true. + * From tapir.ts. + */ +export function isMaybeUsedAsPromise(path: NodePath): boolean { + return !isExpressionStatement(path.node) && + // The call is definitely not used as a Promise if the node is a callee in a call node + !(isCallExpression(path.parent) && path.parent.callee === path.node) && + // The call is definitely not used as a Promise if the receiver in a property read that is not then or catch + !(isMemberExpression(path.parent) && + isIdentifier(path.parent.property) && + !['then', 'catch'].includes(path.parent.property.name)); +} diff --git a/src/misc/files.ts b/src/misc/files.ts new file mode 100644 index 0000000..7dcc56d --- /dev/null +++ b/src/misc/files.ts @@ -0,0 +1,218 @@ +import {closeSync, existsSync, lstatSync, openSync, readdirSync, readFileSync, readSync, writeSync} from "fs"; +import {basename, relative, resolve, sep} from "path"; +import {options} from "../options"; +import micromatch from "micromatch"; +import {FilePath, sourceLocationToStringWithFile} from "./util"; +import logger from "./logger"; +import {SourceLocation} from "@babel/types"; +import {findPackageJson} from "./packagejson"; +import {AnalysisState} from "../analysis/analysisstate"; +import {tsResolveModuleName} from "../typescript/moduleresolver"; +import stringify from "stringify2stream"; + +/** + * Expands the given list of file paths. + * Each given file path is resolved relative to the current working directory. + * Directories are traversed recursively (except node_modules, .git, and .yarn, + * and also excluding out, build, dist, generated and sub-directories that contain package.json unless inside a node_modules directory within basedir), + * and all .js, .es, .mjs, .cjs, .ts, .tsx and Node.js shebang files are included + * (except .d.ts and paths matching options.exclude and also excluding .min.js, .bundle.js unless inside a node_modules directory within basedir). + * Symlinks are ignored. + * The resulting paths are relative to options.basedir. + */ +export function expand(paths: Array | string): Array { + if (typeof paths === "string") + paths = [paths]; + const res: Array = []; + for (const path of paths) + for (const e of expandRec(resolve(path), false)) + res.push(e); // TODO: complain if e starts with "."? (happens if path is outside basedir) + if (options.exclude) { + const excl = new Set(micromatch(res, options.exclude)); + const eres = []; + for (const r of res) + if (!excl.has(r)) + eres.push(r); + return eres; + } else + return res; +} + +function* expandRec(path: string, sub: boolean): Generator { + const stat = lstatSync(path); + const inNodeModules = relative(options.basedir, path).split(sep).includes("node_modules"); + if (stat.isDirectory()) { + const base = basename(path); + if (!sub || + !(["node_modules", ".git", ".yarn"].includes(base) || + (!inNodeModules && ["out", "build", "dist", "generated"].includes(base)))) { + const files = readdirSync(path); // TODO: use withFileTypes and dirent.isdirectory() + if (!sub || inNodeModules || !files.includes("package.json")) + for (const file of files.map(f => resolve(path, f)).sort((f1, f2) => { + // make sure files are ordered before directories + return (lstatSync(f1).isDirectory() ? 1 : 0) - (lstatSync(f2).isDirectory() ? 1 : 0) || f1.localeCompare(f2); + })) + yield* expandRec(file, true); + } else + logger.debug(`Skipping directory ${path}`); + } else if (stat.isFile() && !path.endsWith(".d.ts") && + (!inNodeModules || !(path.endsWith(".min.js") || path.endsWith(".bundle.js"))) && + (path.endsWith(".js") || path.endsWith(".jsx") || path.endsWith(".es") || path.endsWith(".mjs") || path.endsWith(".cjs") || path.endsWith(".ts") || path.endsWith(".tsx") + || isShebang(path))) + yield relative(options.basedir, path); + else + logger.debug(`Skipping file ${path}`); +} + +/** + * Attempts to detect whether the given file is a Node.js shebang file. + */ +function isShebang(path: string): boolean { // TODO: doesn't work with hacks like https://sambal.org/2014/02/passing-options-node-shebang-line/ + const fd = openSync(path, 'r'); + const buf = Buffer.alloc(256); + readSync(fd, buf, 0, buf.length, 0) + closeSync(fd); + const str = buf.toString('utf8'); + return str.startsWith("#!") && str.substring(0, str.indexOf("\n")).includes("node"); +} + +/** + * Resolves a 'require' string to a file path. + * @return resolved file path if successful, undefined if file type not analyzable + * @throws exception if the module is not found + */ +export function requireResolve(str: string, file: FilePath, loc: SourceLocation | null | undefined, a: AnalysisState): FilePath | undefined { + if (str.endsWith(".json")) { + logger.debug(`Skipping JSON file '${str}'`); // TODO: analyze JSON files? + return undefined; + } else if (str.endsWith(".node")) { + logger.debug(`Skipping binary addon file '${str}'`); + return undefined; + } else if (str.endsWith(".less") || str.endsWith(".svg") || str.endsWith(".png") || str.endsWith(".css") || str.endsWith(".scss")) { + logger.verbose(`Ignoring module '${str}' with special extension`); + return undefined; + } else if (str[0] === "/") { + a.warn(`Ignoring absolute module path '${str}'`); + return undefined; + } + let filepath; + try { + filepath = tsResolveModuleName(str, file); + // TypeScript prioritizes .ts over .js, overrule if coming from a .js file + if (file.endsWith(".js") && filepath.endsWith(".ts") && !str.endsWith(".ts")) { + const p = filepath.substring(0, filepath.length - 3) + ".js"; + if (existsSync(p)) + filepath = p; + } + } catch (e) { + // see if the string refers to a package that is among those analyzed (and not in node_modules) + for (const p of a.packageInfos.values()) + if (p.name === str) + if (filepath) { + a.warn(`Multiple packages named ${str} found, skipping module load`); + throw e; + } else { + filepath = resolve(p.dir, p.main || "index.js"); // https://nodejs.org/dist/latest-v8.x/docs/api/modules.html#modules_all_together + if (!existsSync(filepath)) + filepath = undefined; + } + if (!filepath) + throw e; + } + if (!filepath.startsWith(options.basedir)) { + const msg = `Found module at ${filepath}, but not in basedir`; + logger.debug(msg); + throw new Error(msg); + } + if (!filepath.endsWith(".js") && !filepath.endsWith(".jsx") && !filepath.endsWith(".es") && !filepath.endsWith(".mjs") && + !filepath.endsWith(".cjs") && !filepath.endsWith(".ts") && !filepath.endsWith(".tsx")) { + a.warn(`Module '${filepath}' at ${sourceLocationToStringWithFile(loc)} has unrecognized extension, skipping it`); + return undefined; + } + if (logger.isDebugEnabled()) + logger.debug(`Module '${str}' required from ${file} resolved to: ${filepath}`); + return filepath; +} + +/** + * Attempts to auto-detect basedir if not set explicitly. + * If not set explicitly and a single path is given, basedir is set to the nearest enclosing directory + * of paths[0] that contains a package.json file. + * @param paths paths to entry files or directories + * @return true if successful, false if failed + */ +export function autoDetectBaseDir(paths: Array): boolean { + if (options.basedir) { + const stat = lstatSync(options.basedir); + if (!stat.isDirectory()) { + logger.info(`Error: basedir ${options.basedir} is not a directory, aborting`); + return false; + } + return true; + } + if (paths.length === 0) + return true; + if (!existsSync(paths[0])) { + logger.info(`File or directory ${paths[0]} not found, aborting`); + return false; + } + const t = findPackageJson(paths[0]); + if (!t) { + logger.info("Can't auto-detect basedir, package.json not found (use option -b), aborting"); + return false; + } + options.basedir = resolve(process.cwd(), t.dir); + logger.verbose(`Basedir auto-detected: ${options.basedir}`); + return true; +} + +type SourceLocationStr = string; + +const codeCache: Map = new Map(); + +/** + * Reads the code for a source location. + * If cached, returns the cached value. If the code is too long, only returns the head and tail of the code. + */ +export function codeFromLocation(loc: SourceLocation): string { + let locStr = JSON.stringify(loc); + let content = codeCache.get(locStr); + if (!content) { + content = ""; + if (loc && "filename" in loc) { + let fileContent = readFileSync(loc.filename).toString().split(/\r?\n/); + let startRecord = false; + for (let i = loc.start.line; i <= loc.end.line; i++) { + let currLine = fileContent[i - 1]; + for (let j = 0; j < currLine.length; j++) { + if (i == loc.start.line && loc.start.column == j) + startRecord = true; + if (i == loc.end.line && j == loc.end.column) { + startRecord = false; + break; + } + if (startRecord) + content += currLine.charAt(j); + } + } + content = content.replaceAll(/\s+/g, " "); + if (content.length > 40) + content = `${content.substring(0, 20)}/*...*/$${content.substring(content.length - 10)}`; + } + codeCache.set(locStr, content); + } + return content; +} + +/** + * Writes a JSON structure to a file, with streaming to reduce memory usage. + */ +export function writeStreamedStringify(value: any, + fd: number, + replacer?: ((key: string, value: any) => any) | (number | string)[] | null, + space?: string | number) { + stringify(value, (chunk : string | undefined) => { + if (chunk) + writeSync(fd, chunk); + }, replacer, space); +} \ No newline at end of file diff --git a/src/misc/logger.ts b/src/misc/logger.ts new file mode 100644 index 0000000..99fbd33 --- /dev/null +++ b/src/misc/logger.ts @@ -0,0 +1,57 @@ +import {tmpdir} from "os"; +import winston from "winston"; +import * as Transport from 'winston-transport'; +import {options} from "../options"; + +const RED = "\x1b[31m"; +const YELLOW = "\x1b[33m"; +const WHITE = "\x1b[37m"; +const GREEN = "\x1b[32m"; +const CYAN = "\x1b[36m"; +const RESET = "\x1b[0m"; +const CLEAR = "\u001b[0K"; + +const colors: { + [key: string]: string +} = { + error: RED, + warn: YELLOW, + info: WHITE, + verbose: GREEN, + debug: CYAN, +} + +export const isTTY = process.stdout.isTTY; + +const logger = winston.createLogger({ + level: "info", + format: winston.format.printf(({level, message}) => + isTTY && options?.tty ? colors[level] + message + RESET + CLEAR : message), + transports: new winston.transports.Console({ + stderrLevels: [] // change to ["error"] to direct error messages to stderr + }) +}); + +export default logger; + +export function setLogLevel(level: string) { + logger.level = options.loglevel = level; +} + +export function logToFile(file?: string): Transport { + const t = new winston.transports.File({ + filename: file ?? `${tmpdir()}/jelly-${process.pid}.log` + }); + logger.remove(logger.transports[0]); + logger.add(t); + return t; +} + +export function writeStdOut(s: string) { + process.stdout.write(s + "\u001b[0K\r"); +} + +export function writeStdOutIfActive(s: string) { + if (options.printProgress && options.tty && isTTY && logger.level === "info") + writeStdOut(s); +} diff --git a/src/misc/memory.ts b/src/misc/memory.ts new file mode 100644 index 0000000..ca32cb3 --- /dev/null +++ b/src/misc/memory.ts @@ -0,0 +1,21 @@ +import {options} from "../options"; +import assert from "assert"; +import * as v8 from "v8"; + +/** + * Triggers garbage collection if option --gc is enabled and returns the current size of heap used in MB. + */ +export function getMemoryUsage(): number { + if (options.gc) { + assert(typeof gc === "function"); + gc(); + } + return Math.ceil(process.memoryUsage().heapUsed / 1048576); +} + +/** + * Returns the heap size limit in MB. + */ +export function getMemoryLimit(): number { + return Math.ceil(v8.getHeapStatistics().heap_size_limit / 1048576); +} diff --git a/src/misc/packagejson.ts b/src/misc/packagejson.ts new file mode 100644 index 0000000..e061607 --- /dev/null +++ b/src/misc/packagejson.ts @@ -0,0 +1,93 @@ +import {FilePath} from "./util"; +import {dirname, relative, resolve} from "path"; +import {existsSync, readFileSync} from "fs"; +import logger from "./logger"; +import {options} from "../options"; + +/** + * Information about a package.json file. + */ +export interface PackageJsonInfo { + + /** + * Package key generated from its name and version, or "" if package.json file is not found. + */ + packagekey: string; + + /** + * Package name, "
" if package.json file is not found, or "" if the main field is missing. + */ + name: string; + + /** + * Package version, undefined if not available. + */ + version: string | undefined; + + /** + * Normalized main file, undefined if not set or file is absent. + */ + main: string | undefined; + + /** + * Directory containing the package.json file, or "." if the file is not found. + */ + dir: string; +} + +/** + * Finds the enclosing package.json file if present. + */ +export function findPackageJson(file: FilePath): {packageJson: FilePath, dir: FilePath} | undefined { + let dir = file; + while (true) { + const packageJson = resolve(dir, "package.json"); + if (existsSync(packageJson)) + return {packageJson, dir}; + const d = dirname(dir); + if (d === dir || !d.startsWith(options.basedir)) + return undefined; + dir = d; + } +} + +/** + * Extracts PackageJsonInfo for the package containing the given file. + */ +export function getPackageJsonInfo(tofile: FilePath): PackageJsonInfo { + let packagekey, name, version, main, dir; + const p = findPackageJson(tofile); // TODO: add command-line option to skip search for package.json for entry files? + let f; + if (p) { + try { + f = JSON.parse(readFileSync(p.packageJson, {encoding: "utf8"})); + } catch { + logger.warn(`Unable to parse ${p.packageJson}`); + } + } + if (p && f) { + dir = p.dir; + if (!f.name) + logger.verbose(`Package name missing in ${p.packageJson}`); + name = f.name || ""; + if (!f.version) + logger.verbose(`Package version missing in ${p.packageJson}`); + version = f.version; + packagekey = `${name}${version ?? "?"}` + if (f.main) { + try { + // normalize main file path + main = relative(dir, require.resolve("./".includes(f.main[0]) ? f.main : "./" + f.main, {paths: [dir]})); + } catch { + logger.verbose(`Unable to locate package main file '${f.main}' at ${dir}`); + main = undefined; + } + } + } else { + name = "
"; + packagekey = ""; + version = undefined; + dir = "."; + } + return {packagekey, name, version, main, dir} +} diff --git a/src/misc/scc.ts b/src/misc/scc.ts new file mode 100644 index 0000000..97c8d30 --- /dev/null +++ b/src/misc/scc.ts @@ -0,0 +1,74 @@ +import assert from "assert"; +import {getOrSet} from "./util"; + +/** + * Nuutila and Soisalon-Soininen's strongly connected components algorithm. + * @param nodes nodes that must be visited (not necessarily all nodes in the graph!) + * @param succ successors for each node + * @returns [SCC representatives in reverse topological order, map from all visited nodes to their representatives] + */ +export function nuutila(nodes: Iterable, succ: (n: NodeType) => Iterable): [Array, Map] { + const d: Map = new Map; // visit order + const r: Map = new Map; // map from nodes to their representatives + const c: Set = new Set; // nodes in known components + const s: Array = []; // nodes in cycle but not yet inserted in c + const t: Array = []; // representatives, in topological order + let i = 1; // next visit order index + for (const v of nodes) + if (!d.has(v)) + visit(v); + return [t, r]; + + function visit(v: NodeType) { + d.set(v, i++); + r.set(v, v); + for (const w of succ(v)) { + if (!d.has(w)) + visit(w); // TODO: implement without recursion? + if (!c.has(w)) { + const rv = r.get(v); + assert(rv); + const rw = r.get(w); + assert(rw); + const drv = d.get(rv); + assert(drv); + const drw = d.get(rw); + assert(drw); + r.set(v, drv < drw ? rv : rw); + } + } + if (r.get(v) === v) { + c.add(v); + while (s.length > 0) { + const w = s[s.length - 1]; + const dv = d.get(v); + assert(dv); + const dw = d.get(w); + assert(dw); + if (dw <= dv) + break; + s.pop(); + c.add(w); + r.set(w, v); + } + t.push(v); + } else + s.push(v); + } +} + +/** + * Post-processing to extract the components, returned in reverse topological order. + */ +export function getComponents([t, r]: [Array, Map]): Array> { + const m: Map> = new Map; + for (const [v, w] of r.entries()) + getOrSet(m, w, () => []).push(v); + const sccs = []; + for (const v of t) { + const c = m.get(v); + assert(c); + sccs.push(c); + } + return sccs; +} diff --git a/src/misc/timer.ts b/src/misc/timer.ts new file mode 100644 index 0000000..151d572 --- /dev/null +++ b/src/misc/timer.ts @@ -0,0 +1,42 @@ +import {cpuUsage} from "process"; +import {options} from "../options"; + +export default class Timer { + + startTime: Date; + + startUsage: NodeJS.CpuUsage; + + constructor() { + this.startTime = new Date(); + this.startUsage = cpuUsage(); + } + + /** + * Returns the elapsed time in milliseconds since the timer was created. + */ + elapsed(): number { + return new Date().getTime() - this.startTime.getTime(); + } + + /** + * Returns the elapsed user+system CPU time in milliseconds since the timer was created. + * May be higher than the actual elapsed time if multiple CPU cores are performing work. + */ + elapsedCPU(): number { + const u = cpuUsage(this.startUsage); + return Math.round((u.user + u.system) / 1000); + } + + checkTimeout() { + if (options.timeout && this.elapsed() > options.timeout * 1000) + throw new TimeoutException(); + } +} + +export class TimeoutException extends Error { + + constructor() { + super("Analysis time limit exceeded"); + } +} diff --git a/src/misc/util.ts b/src/misc/util.ts new file mode 100644 index 0000000..3be3198 --- /dev/null +++ b/src/misc/util.ts @@ -0,0 +1,234 @@ +import {isIdentifier, Node, SourceLocation} from "@babel/types"; +import {globalLoc} from "../analysis/analysisstate"; +import assert from "assert"; + +export type SourceLocationWithFilename = SourceLocation & {filename: string}; + +export type PatchedSourceLocation = {nodeIndex: number, start?: {line: number, column: number }, end?: {line: number, column: number}}; + +export type SourceLocationJSON = string; // format: "::::" + +/** + * Normalized path to a file or directory. + */ +export type FilePath = string; + +/** + * True/False/Maybe. + */ +export enum Ternary { + True = 1, + False = 0, + Maybe = -1 +} + +/** + * Ternary "or" operator. + */ +export function ternaryOr(t1: Ternary, t2: Ternary): Ternary { + return t1 === Ternary.True || t2 === Ternary.True ? Ternary.True : + t1 === Ternary.Maybe || t2 === Ternary.Maybe ? Ternary.Maybe : + Ternary.False; +} + +export function ternaryToString(t: Ternary): string { + switch (t) { + case Ternary.True: + return "true"; + case Ternary.False: + return "false"; + case Ternary.Maybe: + return "maybe"; + } +} + +/** + * Returns a string representation of the given AST node. + */ +export function nodeToString(n: Node): string { + if (isIdentifier(n)) + return `'${n.name}'[${sourceLocationToStringWithFile(n.loc)}]`; + else + return `[${sourceLocationToStringWithFileAndEnd(n.loc)}]`; +} + +/** + * Returns a string representation of the given source location. + */ +export function sourceLocationToString(loc: SourceLocationWithFilename | SourceLocation | PatchedSourceLocation | null | undefined, withFilename: boolean = false, withEnd: boolean = false) { + if (!loc || loc === globalLoc) + return "?"; + const file = withFilename && "filename" in loc ? `${loc.filename}` : ""; + const start = loc.start && loc.start.line !== 0 ? `${loc.start.line}:${loc.start.column + 1}` : ""; + const end = withEnd && loc.end && loc.end.line !== 0 ? `:${loc.end.line}:${loc.end.column + 1}` : ""; + const extra = "nodeIndex" in loc ? `$${loc.nodeIndex}` : ""; // for uniquely naming AST nodes with missing source location + return file + (file && start ? ":" : "") + start + end + extra; +} + +/** + * Returns a string representation of the given source location, including filename. + */ +export function sourceLocationToStringWithFile(loc: SourceLocationWithFilename | SourceLocation | PatchedSourceLocation | null | undefined) { + return sourceLocationToString(loc, true); +} + +/** + * Returns a string representation of the given source location, including filename and end position. + */ +export function sourceLocationToStringWithFileAndEnd(loc: SourceLocationWithFilename | SourceLocation | PatchedSourceLocation | null | undefined) { + return sourceLocationToString(loc, true, true); +} + +/** + * Checks whether the given file and line belong to the source location range. + */ +export function sourceLocationContains(loc: SourceLocationWithFilename | SourceLocation | PatchedSourceLocation | null | undefined, file: string, line: number): boolean { + return Boolean(loc && loc.start && loc.end && "filename" in loc && loc.filename === file && loc.start.line <= line && line <= loc.end.line); +} + +/** + * Checks whether the first source location is within the second source location. + */ +export function sourceLocationIn(loc1: SourceLocation, loc2: SourceLocation | undefined | null): boolean { + if (!loc2) + return false; + let start = loc2.start.line < loc1.start.line || + (loc2.start.line === loc1.start.line && loc2.start.column <= loc1.start.column); + let end = loc1.end.line < loc2.end.line || + (loc1.end.line === loc2.end.line && loc2.end.column <= loc1.end.column); + return start && end; +} + +export function mapGetMap(m: Map>, k: K1): Map { + let mt = m.get(k); + if (!mt) { + mt = new Map(); + m.set(k, mt); + } + return mt; +} + +export function mapGetSet(m: Map>, k: K): Set { + let mt = m.get(k); + if (!mt) { + mt = new Set; + m.set(k, mt); + } + return mt; +} + +export function mapGetArray(m: Map>, k: K): Array { + let mt = m.get(k); + if (!mt) { + mt = []; + m.set(k, mt); + } + return mt; +} + +export function getOrSet(m: Map, k: K, v: () => V): V { + let r = m.get(k); + if (!r) { + r = v(); + m.set(k, r); + } + return r; +} + +export function arrayToString(a: Array, sep: string): string { + return a.length === 0 ? "-" : sep + a.join(sep); +} + +export function mapMapSize(m: Map>): number { + let c = 0; + for (const n of m.values()) + c += n.size + return c; +} + +export function mapSetAddAll(from: Map>, to: Map>) { + for (const [k, vs] of from) + addAll(vs, mapGetSet(to, k)); +} + +export function addAll(from: Iterable | Set | Array | undefined, to: Set): number { + if (!from) + return 0; + const before = to.size; + for (const x of from) + to.add(x); + return to.size - before; +} + +export function mapArrayAdd(k: K, v: V, m: Map>) { + let a = m.get(k); + if (!a) { + a = []; + m.set(k, a); + } + a.push(v); +} + +export function deleteAll(xs: Iterable, s: Set) { + for (const x of xs) + s.delete(x); +} + +export function deleteMapSetAll(m: Map>, k: K, vs: Set) { + const s = m.get(k); + if (s) { + deleteAll(vs.values(), s); + if (s.size === 0) + m.delete(k); + } +} + +/** + * Computes a hashcode for the given string. + */ +export function strHash(s: string): number { + let h = 0; + for (let i = 0; i < s.length; i++) + h = Math.imul(31, h) + s.charCodeAt(i) | 0; + return h; +} + +/** + * Checks whether the given property name is an array index. + */ +export function isArrayIndex(prop: string): boolean { + return Number.isSafeInteger(parseFloat(prop)) && parseInt(prop) >= 0; // TODO: more precise check for isArrayIndex? +} + +/** + * Converts the given number to a percentage string. + */ +export function percent(x: number): string { + return `${(100 * x).toFixed(2)}%`; +} + +export class SourceLocationsToJSON { + + private readonly fileIndex = new Map(); + + private readonly files: Array; + + constructor(files: Array) { + this.files = files; + } + + private getFileIndex(file: FilePath): number { + let n = this.fileIndex.get(file); + if (n === undefined) { + n = this.files.length; + this.fileIndex.set(file, n); + this.files.push(file); + } + return n; + } + + makeLocString(loc: SourceLocationWithFilename | SourceLocation | undefined | null): SourceLocationJSON { + assert(loc && "filename" in loc); // TODO: assertion may fail? + return `${this.getFileIndex(loc.filename)}:${loc.start.line}:${loc.start.column + 1}:${loc.end.line}:${loc.end.column + 1}`; + } +} \ No newline at end of file diff --git a/src/natives/ecmascript.ts b/src/natives/ecmascript.ts new file mode 100644 index 0000000..c8685c2 --- /dev/null +++ b/src/natives/ecmascript.ts @@ -0,0 +1,1654 @@ +// noinspection JSUnusedLocalSymbols + +import { + assignBaseArrayArrayValueToArray, + assignBaseArrayValueToArray, + assignIteratorMapValuePairs, + assignIteratorValuesToArrayValue, + assignIteratorValuesToProperty, + assignParameterToArrayValue, + assignParameterToThisArrayValue, + assignParameterToThisProperty, + callPromiseExecutor, + invokeCallback, + newArray, + newObject, + newPackageObject, + returnArgument, + returnArrayValue, + returnIterator, + returnPackageObject, + returnPromiseIterator, + returnResolvedPromise, + returnShuffledArray, + returnShuffledInplace, + returnThis, + returnThisInPromise, + returnThisProperty, + returnToken, + warnNativeUsed, + widenArgument +} from "./nativehelpers"; +import {PackageObjectToken} from "../analysis/tokens"; +import {isExpression, isNewExpression} from "@babel/types"; +import {NativeFunctionParams, NativeModel, NativeModelParams} from "./nativebuilder"; +import {getBaseAndProperty} from "../misc/asthelpers"; +import {TokenListener} from "../analysis/listeners"; + +export const OBJECT_PROTOTYPE = "Object.prototype"; +export const ARRAY_PROTOTYPE = "Array.prototype"; +export const FUNCTION_PROTOTYPE = "Function.prototype"; +export const REGEXP_PROTOTYPE = "RegExp.prototype"; +export const DATE_PROTOTYPE = "Date.prototype"; +export const MAP_PROTOTYPE = "Map.prototype"; +export const SET_PROTOTYPE = "Set.prototype"; +export const WEAKMAP_PROTOTYPE = "WeakMap.prototype"; +export const WEAKSET_PROTOTYPE = "WeakSet.prototype"; +export const WEAKREF_PROTOTYPE = "WeakRef.prototype"; +export const GENERATOR_PROTOTYPE_NEXT = "Generator.prototype.next"; +export const ASYNC_GENERATOR_PROTOTYPE_NEXT = "AsyncGenerator.prototype.next"; +export const PROMISE_PROTOTYPE = "Promise.prototype"; + +export const MAP_KEYS = "%MAP_KEYS"; +export const MAP_VALUES = "%MAP_VALUES"; +export const SET_VALUES = "%SET_VALUES"; +export const WEAKMAP_VALUES = "%WEAKMAP_VALUES"; +export const WEAKREF_VALUES = "%WEAKREF_VALUES"; +export const PROMISE_FULFILLED_VALUES = "%PROMISE_FULFILLED_VALUES"; +export const PROMISE_REJECTED_VALUES = "%PROMISE_REJECTED_VALUES"; + +/* + * Models of ECMAScript standard built-in objects. + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects + * and https://github.com/microsoft/TypeScript/tree/main/lib. + * Primitive values and accessor properties are ignored; only functions and objects are modeled. + */ +export const ecmascriptModels: NativeModel = { + name: "ecmascript", + init: (p: NativeModelParams) => { + // establish essential inheritance relations + const thePackageObjectToken = p.solver.analysisState.canonicalizeToken(new PackageObjectToken(p.moduleInfo.packageInfo)); + const theArrayPackageObjectToken = p.solver.analysisState.canonicalizeToken(new PackageObjectToken(p.moduleInfo.packageInfo, "Array")); + const theDatePackageObjectToken = p.solver.analysisState.canonicalizeToken(new PackageObjectToken(p.moduleInfo.packageInfo, "Date")); + const theRegExpPackageObjectToken = p.solver.analysisState.canonicalizeToken(new PackageObjectToken(p.moduleInfo.packageInfo, "RegExp")); + p.solver.addInherits(thePackageObjectToken, p.natives.get(OBJECT_PROTOTYPE)!); + p.solver.addInherits(theArrayPackageObjectToken, p.natives.get(ARRAY_PROTOTYPE)!); + p.solver.addInherits(theDatePackageObjectToken, p.natives.get(DATE_PROTOTYPE)!); + p.solver.addInherits(theRegExpPackageObjectToken, p.natives.get(REGEXP_PROTOTYPE)!); + p.solver.addInherits(p.natives.get(ARRAY_PROTOTYPE)!, p.natives.get(OBJECT_PROTOTYPE)!); + p.solver.addInherits(p.natives.get(DATE_PROTOTYPE)!, p.natives.get(OBJECT_PROTOTYPE)!); + p.solver.addInherits(p.natives.get(REGEXP_PROTOTYPE)!, p.natives.get(OBJECT_PROTOTYPE)!); + p.solver.addInherits(p.natives.get(FUNCTION_PROTOTYPE)!, p.natives.get(OBJECT_PROTOTYPE)!); + p.solver.addInherits(p.natives.get(PROMISE_PROTOTYPE)!, p.natives.get(OBJECT_PROTOTYPE)!); + // TODO: all ObjectToken objects should also inherit from Object.prototype and Array.prototype? + // TODO: all FunctionToken objects should also inherit from Function.prototype? + // TODO: all NativeObjectToken objects and AccessPathToken objects should also inherit from Object.prototype, Array.prototype and Function.prototype? + }, + variables: [ + { + name: "globalThis" + }, + { + name: "Infinity" + }, + { + name: "NaN" + } + ], + functions: [ + { + name: "decodeURI" + }, + { + name: "decodeURIComponent" + }, + { + name: "encodeURI" + }, + { + name: "encodeURIComponent" + }, + { + name: "escape" + }, + { + name: "eval", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("eval", p); + } + }, + { + name: "isFinite" + }, + { + name: "isNaN" + }, + { + name: "parseFloat" + }, + { + name: "parseInt" + }, + { + name: "unescape" + } + ], + classes: [ + { + name: "AggregateError", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("AggregateError", p); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + }, + { + name: "Array", + fields: [ + { + name: "length" + } + ], + invoke: (p: NativeFunctionParams) => { + const t = newArray(p); + for (let i = 0; i < p.path.node.arguments.length; i++) + assignParameterToArrayValue(i, t, p); + returnToken(t, p); + }, + staticMethods: [ + { + name: "from", + invoke: (p: NativeFunctionParams) => { + const t = newArray(p); + if (!p.path.node.arguments.every(arg => isExpression(arg))) + warnNativeUsed("Array.from", p, "with SpreadElement"); // TODO: SpreadElement + else if (p.path.node.arguments.length > 0) + assignIteratorValuesToArrayValue(0, t, p); + if (p.path.node.arguments.length > 1) { + // TODO: connect p.path.node.arguments[0] iterable/arrayLike values to mapFn + // TODO: connect returnVar of mapFn to array value of t + // TODO: if p.path.node.arguments.length > 2, connect thisArg to thisVar of mapFn + warnNativeUsed("Array.from", p, "with map function argument"); // TODO + } + returnToken(t, p); + } + }, + { + name: "isArray" + }, + { + name: "of", + invoke: (p: NativeFunctionParams) => { + const t = newArray(p); + for (let i = 0; i < p.path.node.arguments.length; i++) + assignParameterToArrayValue(i, t, p); + returnToken(t, p); + } + } + ], + methods: [ + { + name: "at", + invoke: (p: NativeFunctionParams) => { + returnArrayValue(p); + } + }, + { + name: "concat", + invoke: (p: NativeFunctionParams) => { + const t = newArray(p); + assignBaseArrayValueToArray(t, p); + for (let i = 0; i < p.path.node.arguments.length; i++) { + assignIteratorValuesToArrayValue(i, t, p); + assignParameterToArrayValue(i, t, p); // TODO: could omit arrays among the arguments (see also 'flat' below) + } + returnToken(t, p); + } + }, + { + name: "copyWithin", + invoke: (p: NativeFunctionParams) => { + returnShuffledInplace(p); + } + }, + { + name: "entries", + invoke: (p: NativeFunctionParams) => { + returnIterator("ArrayEntries", p); + } + }, + { + name: "every", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.every", p); + } + }, + { + name: "fill", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisArrayValue(0, p); + } + }, + { + name: "filter", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.filter", p); + returnShuffledArray(p); + } + }, + { // TODO: see also findLast (proposal) + name: "find", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.find", p); + returnArrayValue(p); + } + }, + { // TODO: see also findLastIndex (proposal) + name: "findIndex", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.findIndex", p); + } + }, + { + name: "flat", + invoke: (p: NativeFunctionParams) => { + const t = newArray(p); + assignBaseArrayValueToArray(t, p); // TODO: could omit arrays among the arguments (see also 'concat' above) + assignBaseArrayArrayValueToArray(t, p); + if (p.path.node.arguments.length > 0) + warnNativeUsed("Array.prototype.flat", p, "with unknown depth"); // TODO: connect elements of elements of base recursively + returnToken(t, p); + } + }, + { + name: "flatMap", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.flatMap", p); + } + }, + { + name: "forEach", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.forEach", p); + } + }, + { + name: "group", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Array.prototype.group", p); // TODO (experimental) + } + }, + { + name: "groupToMap", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Array.prototype.groupToMap", p); // TODO (experimental) + } + }, + { + name: "includes" + }, + { + name: "indexOf" + }, + { + name: "join" + }, + { + name: "keys", + invoke: (p: NativeFunctionParams) => { + returnIterator("ArrayKeys", p); + } + }, + { + name: "lastIndexOf" + }, + { + name: "map", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.map", p); + } + }, + { + name: "pop", + invoke: (p: NativeFunctionParams) => { + returnArrayValue(p); + } + }, + { + name: "push", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisArrayValue(0, p); + } + }, + { + name: "reduce", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.reduce", p); + } + }, + { + name: "reduceRight", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.reduceRight", p); + } + }, + { + name: "reverse" + }, + { + name: "shift", + invoke: (p: NativeFunctionParams) => { + returnArrayValue(p); + } + }, + { + name: "slice", + invoke: (p: NativeFunctionParams) => { + returnShuffledArray(p); + } + }, + { + name: "some", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.some", p); + } + }, + { + name: "sort", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Array.prototype.sort", p); + } + }, + { + name: "splice", + invoke: (p: NativeFunctionParams) => { + const t = returnShuffledArray(p); + if (t) + for (let i = 2; i < p.path.node.arguments.length; i++) + assignParameterToArrayValue(i, t, p); + } + }, + { + name: "toLocaleString", + }, + { + name: "toString", + }, + { + name: "unshift", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisArrayValue(0, p); + } + }, + { + name: "values", + invoke: (p: NativeFunctionParams) => { + returnIterator("ArrayValues", p); + } + } + ] + }, + { + name: "ArrayBuffer", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("ArrayBuffer", p); // TODO + }, + staticMethods: [ + { + name: "isView" + }], + methods: [ + { + name: "slice", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("ArrayBuffer.prototype.slice", p); // TODO + } + }] + }, + // TODO: AsyncFunction, AsyncGeneratorFunction, GeneratorFunction + { + name: "Atomics" + // TODO + }, + { + name: "BigInt", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("BigInt", p); // TODO + }, + // TODO + }, + { + name: "BigInt64Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("BigInt64Array", p); // TODO + }, + // TODO + }, + { + name: "BigUint64Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("BigUint64Array", p); // TODO + }, + // TODO + }, + { + name: "Boolean", + methods: [ + { + name: "toString" + }, + { + name: "valueOf" + } + ] + }, + { + name: "DataView", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("DataView", p); // TODO + } + // TODO + }, + { + name: "Date", + invoke: (p: NativeFunctionParams) => { + if (isNewExpression(p.path.node)) + returnToken(newPackageObject("Date", p.natives.get(DATE_PROTOTYPE)!, p), p); + }, + staticMethods: [ + { + name: "now" + }, + { + name: "parse" + }, + { + name: "UTC" + } + ], + methods: [ + { + name: "getDate" + }, + { + name: "getDay" + }, + { + name: "getFullYear" + }, + { + name: "getHours" + }, + { + name: "getMilliseconds" + }, + { + name: "getMinutes" + }, + { + name: "getMonths" + }, + { + name: "getSeconds" + }, + { + name: "getTime" + }, + { + name: "getTimezoneOffset" + }, + { + name: "getUTCDate" + }, + { + name: "getUTCDay" + }, + { + name: "getUTCFullYear" + }, + { + name: "getUTCMilliseconds" + }, + { + name: "getUTCMinutes" + }, + { + name: "getUTCMonth" + }, + { + name: "getUTCSeconds" + }, + { + name: "getYear" + }, + { + name: "setDate" + }, + { + name: "setFullYear" + }, + { + name: "setHours" + }, + { + name: "setMilliseconds" + }, + { + name: "setMinutes" + }, + { + name: "setMonth" + }, + { + name: "setSeconds" + }, + { + name: "setTime" + }, + { + name: "setUTCDate" + }, + { + name: "setUTCFullYear" + }, + { + name: "setUTCHours" + }, + { + name: "setUTCMilliseconds" + }, + { + name: "setUTCMinutes" + }, + { + name: "setUTCMonth" + }, + { + name: "setUTCSeconds" + }, + { + name: "setYear" + }, + { + name: "toDateString" + }, + { + name: "toGMTString" + }, + { + name: "toISOString" + }, + { + name: "toJSON" + }, + { + name: "toLocaleDateString" + }, + { + name: "toLocaleString" + }, + { + name: "toLocaleTimeString" + }, + { + name: "toString" + }, + { + name: "toTimeString" + }, + { + name: "toUTCString" + }, + { + name: "valueOf" + }, + + ] + }, + { + name: "Error", + fields: [ + { + name: "cause" + }, + { + name: "message" + }, + { + name: "name" + } + ], + methods: [ + { + name: "toString" + } + ], + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("Error", p, "with multiple arguments"); // TODO + returnPackageObject(p); + } + }, + { + name: "EvalError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("EvalError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "FinalizationRegistry", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("FinalizationRegistry", p); // TODO + } + // TODO + }, + { + name: "Float32Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Float32Array", p); // TODO + } + // TODO + }, + { + name: "Float64Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Float64Array", p); // TODO + } + // TODO + }, + { + name: "Function", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Function", p); + }, + methods: [ + { + name: "apply", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Function.prototype.apply", p); // TODO + } + }, + { + name: "bind", + invoke: (p: NativeFunctionParams) => { + const bp = getBaseAndProperty(p.path); + if (bp) + p.solver.addSubsetConstraint(p.solver.analysisState.varProducer.expVar(bp.base, p.path), p.solver.analysisState.varProducer.expVar(p.path.node, p.path)); // TODO: move to nativehelpers + if (!p.path.node.arguments.every(arg => isExpression(arg))) + warnNativeUsed("Function.prototype.bind", p, "with SpreadElement"); // TODO: SpreadElement + else if (p.path.node.arguments.length === 1) + warnNativeUsed("Function.prototype.bind", p, "with one argument"); // TODO: bind 'this' + else if (p.path.node.arguments.length > 1) + warnNativeUsed("Function.prototype.bind", p, "with multiple arguments"); // TODO: bind 'this' and partial arguments + } + }, + { + name: "call", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Function.prototype.call", p); // TODO + } + }, + { + name: "toString" + } + ] + }, + { + name: "Generator", // also used for Iterator, Iterable, IterableIterator, IteratorResult + hidden: true, + methods: [ + { + name: "next", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisProperty(0, "value", p); // assuming the iterator/iterable uses the same abstract object as the iterator result + returnThis(p); + } + }, + { + name: "return", + invoke: (p: NativeFunctionParams) => { + returnThis(p); + } + }, + { + name: "throw", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length >= 1) + widenArgument(p.path.node.arguments[0], p); + } + }, + ] + }, + { + name: "AsyncGenerator", // async variant of Generator + hidden: true, + methods: [ + { + name: "next", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisProperty(0, "value", p); + returnThisInPromise(p); + } + } + ] + }, + { + name: "Int16Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Int16Array", p); // TODO + } + // TODO + }, + { + name: "Int32Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Int32Array", p); // TODO + } + // TODO + }, + { + name: "Int8Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Int8Array", p); // TODO + } + // TODO + }, + { + name: "InternalError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("InternalError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "Intl", + staticMethods: [ + // TODO + ] + }, + { + name: "JSON", + staticMethods: [ + { + name: "parse", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("JSON.parse", p, "with reviver"); // TODO + returnPackageObject(p); + } + }, + { + name: "stringify", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("JSON.stringify", p, "with replacer"); // TODO (only warn if second argument may be a function) + } + }, + ] + }, + { + name: "Map", + invoke: (p: NativeFunctionParams) => { + if (isNewExpression(p.path.node)) { + const t = newObject("Map", p.natives.get(MAP_PROTOTYPE)!, p); + if (p.path.node.arguments.length > 0) + assignIteratorMapValuePairs(0, t, MAP_KEYS, MAP_VALUES, p); + returnToken(t, p); + } + }, + methods: [ + { + name: "clear" + }, + { + name: "delete" + }, + { + name: "entries", + invoke: (p: NativeFunctionParams) => { + returnIterator("MapEntries", p); + } + }, + { + name: "forEach", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Map.prototype.forEach", p); + } + }, + { + name: "get", + invoke: (p: NativeFunctionParams) => { + returnThisProperty(MAP_VALUES, p); + } + }, + { + name: "has" + }, + { + name: "keys", + invoke: (p: NativeFunctionParams) => { + returnIterator("MapKeys", p); + } + }, + { + name: "set", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisProperty(0, MAP_KEYS, p); + assignParameterToThisProperty(1, MAP_VALUES, p); + } + }, + { + name: "values", + invoke: (p: NativeFunctionParams) => { + returnIterator("MapValues", p); + } + } + ] + }, + { + name: "Math", + fields: [ + { + name: "E" + }, + { + name: "LN10" + }, + { + name: "LN2" + }, + { + name: "LOG10E" + }, + { + name: "LOG2E" + }, + { + name: "PI" + }, + { + name: "SQRT1_2" + }, + { + name: "SQRT2" + } + ], + staticMethods: [ + { + name: "abs" + }, + { + name: "acos" + }, + { + name: "acosh" + }, + { + name: "asin" + }, + { + name: "asinh" + }, + { + name: "atan" + }, + { + name: "atan2" + }, + { + name: "atanh" + }, + { + name: "cbrt" + }, + { + name: "ceil" + }, + { + name: "clz32" + }, + { + name: "cos" + }, + { + name: "cosh" + }, + { + name: "exp" + }, + { + name: "expm1" + }, + { + name: "floor" + }, + { + name: "fround" + }, + { + name: "hypot" + }, + { + name: "imul" + }, + { + name: "log" + }, + { + name: "log10" + }, + { + name: "log1p" + }, + { + name: "log2" + }, + { + name: "max" + }, + { + name: "min" + }, + { + name: "pow" + }, + { + name: "random" + }, + { + name: "round" + }, + { + name: "sign" + }, + { + name: "sin" + }, + { + name: "sinh" + }, + { + name: "sqrt" + }, + { + name: "tan" + }, + { + name: "tanh" + }, + { + name: "trunc" + } + ] + }, + { + name: "Number", + fields: [ + { + name: "EPSILON" + }, + { + name: "MAX_SAFE_INTEGER" + }, + { + name: "MAX_VALUE" + }, + { + name: "MIN_SAFE_INTEGER" + }, + { + name: "MIN_VALUE" + }, + { + name: "NaN" + }, + { + name: "NEGATIVE_INFINITY" + }, + { + name: "POSITIVE_INFINITY" + } + ], + staticMethods: [ + { + name: "isFinite" + }, + { + name: "isInteger" + }, + { + name: "isNaN" + }, + { + name: "isSafeInteger" + }, + { + name: "parseFloat" + }, + { + name: "parseInt" + } + ], + methods: [ + { + name: "toExponential" + }, + { + name: "toFixed" + }, + { + name: "toLocaleString" + }, + { + name: "toPrecision" + }, + { + name: "toString" + }, + { + name: "valueOf" + } + ] + }, + { + name: "Object", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 0) + warnNativeUsed("Object", p, "with arguments"); // TODO + returnPackageObject(p); + }, + staticMethods: [ + { + name: "assign", + invoke: (p: NativeFunctionParams) => { + for (let i = 1; i < p.path.node.arguments.length; i++) + widenArgument(p.path.node.arguments[i], p); + if (p.path.node.arguments.length >= 1) + returnArgument(p.path.node.arguments[0], p); + returnPackageObject(p); + } + }, + { + name: "create", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length >= 1) + widenArgument(p.path.node.arguments[0], p); + if (p.path.node.arguments.length >= 2) + warnNativeUsed("Object.create", p, "with properties object"); // TODO + returnPackageObject(p); + } + }, + { + name: "defineProperties", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.defineProperties", p); // TODO + } + }, + { + name: "defineProperty", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.defineProperty", p); // TODO + } + }, + { + name: "entries", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.entries", p); // TODO + } + }, + { + name: "freeze" + }, + { + name: "fromEntries", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.fromEntries", p); // TODO + } + }, + { + name: "getOwnPropertyDescriptor", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.getOwnPropertyDescriptor", p); // TODO + } + }, + { + name: "getOwnPropertyDescriptors", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.getOwnPropertyDescriptors", p); // TODO + } + }, + { + name: "getOwnPropertyNames", + invoke: (p: NativeFunctionParams) => { + returnToken(newArray(p), p); + } + }, + { + name: "getOwnPropertySymbols", + invoke: (p: NativeFunctionParams) => { + returnToken(newArray(p), p); + } + }, + { + name: "getPrototypeOf", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.getPrototypeOf", p); // TODO + } + }, + { + name: "is" + }, + { + name: "isExtensible" + }, + { + name: "isFrozen" + }, + { + name: "isSealed" + }, + { + name: "keys", + invoke: (p: NativeFunctionParams) => { + returnToken(newArray(p), p); + } + }, + { + name: "preventExtensions" + }, + { + name: "seal" + }, + { + name: "setPrototypeOf", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.setPrototypeOf", p); // TODO + } + }, + { + name: "values", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Object.values", p); // TODO + } + }, + ], + methods: [ + { + name: "hasOwnProperty" + }, + { + name: "isPrototypeOf" + }, + { + name: "propertyIsEnumerable" + }, + { + name: "toLocaleString" + }, + { + name: "toString" + }, + { + name: "valueOf" + } + ] + }, + { + name: "Promise", + invoke: (p: NativeFunctionParams) => { + if (isNewExpression(p.path.node)) { + const promise = newObject("Promise", p.natives.get(PROMISE_PROTOTYPE)!, p); + const resolveFunction = newObject("PromiseResolve", p.natives.get(FUNCTION_PROTOTYPE)!, p); + const rejectFunction = newObject("PromiseReject", p.natives.get(FUNCTION_PROTOTYPE)!, p); + callPromiseExecutor(promise, resolveFunction, rejectFunction, p); + returnToken(promise, p); + } + }, + staticMethods: [ + { + name: "all", + invoke: (p: NativeFunctionParams) => { + returnPromiseIterator("all", p); + } + }, + { + name: "allSettled", + invoke: (p: NativeFunctionParams) => { + returnPromiseIterator("allSettled", p); + } + }, + { + name: "any", + invoke: (p: NativeFunctionParams) => { + returnPromiseIterator("any", p); + } + }, + { + name: "race", + invoke: (p: NativeFunctionParams) => { + returnPromiseIterator("race", p); + } + }, + { + name: "reject", + invoke: (p: NativeFunctionParams) => { + returnResolvedPromise("reject", p); + } + }, + { + name: "resolve", + invoke: (p: NativeFunctionParams) => { + returnResolvedPromise("resolve", p); + } + }, + ], + methods: [ + { + name: "catch", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Promise.prototype.catch$onRejected", p, 0, TokenListener.NATIVE_INVOKE_CALLBACK); + } + }, + { + name: "finally", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Promise.prototype.finally$onFinally", p, 0, TokenListener.NATIVE_INVOKE_CALLBACK); + } + }, + { + name: "then", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Promise.prototype.then$onFulfilled", p, 0, TokenListener.NATIVE_INVOKE_CALLBACK); + invokeCallback("Promise.prototype.then$onRejected", p, 1, TokenListener.NATIVE_INVOKE_CALLBACK2); + } + } + ] + }, + { + name: "Proxy", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Proxy", p); // TODO + }, + staticMethods: [ + { + name: "revocable", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Proxy.revocable", p); // TODO + }, + } + ] + }, + { + name: "RangeError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("RangeError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "ReferenceError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("ReferenceError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "Reflect" + // TODO + }, + { + name: "RegExp", + invoke: (p: NativeFunctionParams) => { + returnToken(newPackageObject("RegExp", p.natives.get(REGEXP_PROTOTYPE)!, p), p); + }, + methods: [ + { + name: "exec", + invoke: (p: NativeFunctionParams) => { + returnToken(newArray(p), p); + } + }, + { + name: "test" + }, + { + name: "toString" + } + ] + }, + { + name: "Set", + invoke: (p: NativeFunctionParams) => { + if (isNewExpression(p.path.node)) { + const t = newObject("Set", p.natives.get(SET_PROTOTYPE)!, p); + if (p.path.node.arguments.length > 0) + assignIteratorValuesToProperty(0, t, SET_VALUES, p); + returnToken(t, p); + } + }, + methods: [ + { + name: "add", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisProperty(0, SET_VALUES, p); + } + }, + { + name: "clear" + }, + { + name: "delete" + }, + { + name: "entries", + invoke: (p: NativeFunctionParams) => { + returnIterator("SetEntries", p); + } + }, + { + name: "forEach", + invoke: (p: NativeFunctionParams) => { + invokeCallback("Set.prototype.forEach", p); + } + }, + { + name: "has" + }, + { + name: "values", + invoke: (p: NativeFunctionParams) => { + returnIterator("SetValues", p); + } + } + ] + }, + { + name: "SharedArrayBuffer", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("SharedArrayBuffer", p); // TODO + } + // TODO + }, + { + name: "String", + staticMethods: [ + { + name: "fromCharCode" + }, + { + name: "fromCodePoint" + }, + { + name: "raw" + } + ], + methods: [ + { + name: "charAt" + }, + { + name: "charCodeAt" + }, + { + name: "codePointAt" + }, + { + name: "concat" + }, + { + name: "endsWith" + }, + { + name: "includes" + }, + { + name: "indexOf" + }, + { + name: "lastIndexOf" + }, + { + name: "localeCompare" + }, + { + name: "match" + }, + { + name: "matchAll" + }, + { + name: "normalize" + }, + { + name: "padEnd" + }, + { + name: "padStart" + }, + { + name: "repeat" + }, + { + name: "replace" + }, + { + name: "replaceAll" + }, + { + name: "search" + }, + { + name: "slice" + }, + { + name: "split" + }, + { + name: "startsWith" + }, + { + name: "substring" + }, + { + name: "toLocaleLowerCase" + }, + { + name: "toLocaleUpperCase" + }, + { + name: "toLowerCase" + }, + { + name: "toString" + }, + { + name: "toUpperCase" + }, + { + name: "trim" + }, + { + name: "trimEnd" + }, + { + name: "trimStart" + }, + { + name: "valueOf" + } + ] + }, + { + name: "Symbol", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Symbol", p); // TODO + } + // TODO + }, + { + name: "SyntaxError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("SyntaxError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "TypedArray", + hidden: true + // TODO + }, + { + name: "TypeError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("TypeError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "Uint16Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Uint16Array", p); // TODO + } + // TODO + }, + { + name: "Uint32Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Uint32Array", p); // TODO + } + // TODO + }, + { + name: "Uint8Array", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Uint8Array", p); // TODO + } + // TODO + }, + { + name: "Uint8ClampedArray", + invoke: (p: NativeFunctionParams) => { + warnNativeUsed("Uint8ClampedArray", p); // TODO + } + // TODO + }, + { + name: "URIError", + invoke: (p: NativeFunctionParams) => { + if (p.path.node.arguments.length > 1) + warnNativeUsed("URIError", p, "with multiple arguments"); // TODO + returnPackageObject(p); // TODO: should inherit toString from Error + } + // TODO + }, + { + name: "WeakMap", + invoke: (p: NativeFunctionParams) => { + const t = newObject("WeakMap", p.natives.get(WEAKMAP_PROTOTYPE)!, p); + if (p.path.node.arguments.length > 0) + assignIteratorMapValuePairs(0, t, null, WEAKMAP_VALUES, p); + returnToken(t, p); + }, + methods: [ + { + name: "clear" + }, + { + name: "delete" + }, + { + name: "get", + invoke: (p: NativeFunctionParams) => { + returnThisProperty(WEAKMAP_VALUES, p); + } + }, + { + name: "has" + }, + { + name: "set", + invoke: (p: NativeFunctionParams) => { + assignParameterToThisProperty(1, WEAKMAP_VALUES, p); + } + } + ] + }, + { + name: "WeakRef", + invoke: (p: NativeFunctionParams) => { + if (isNewExpression(p.path.node)) { + assignParameterToThisProperty(0, WEAKREF_VALUES, p); + returnToken(newObject("WeakRef", p.natives.get(WEAKREF_PROTOTYPE)!, p), p); + } + }, + methods: [ + { + name: "deref", + invoke: (p: NativeFunctionParams) => { + returnThisProperty(WEAKREF_VALUES, p); + } + } + ] + }, + { + name: "WeakSet", + invoke: (p: NativeFunctionParams) => { + if (isNewExpression(p.path.node)) + returnToken(newObject("WeakSet", p.natives.get(WEAKSET_PROTOTYPE)!, p), p); + }, + methods: [ + { + name: "add" + }, + { + name: "delete" + }, + { + name: "has" + } + ] + }, + { + name: "WebAssembly", + staticMethods: [ + { + name: "compile" + }, + { + name: "compileStreaming" + }, + { + name: "instantiate" + }, + { + name: "instantiateStreaming" + }, + { + name: "validate" + } + ] + // TODO: WebAssembly.Module + // TODO: WebAssembly.Global + // TODO: WebAssembly.Instance + // TODO: WebAssembly.Memory + // TODO: WebAssembly.Table + // TODO: WebAssembly.CompileError + // TODO: WebAssembly.LinkError + // TODO: WebAssembly.RuntimeError + // TODO: WebAssembly.Tag + // TODO: WebAssembly.Exception + } + ] +}; diff --git a/src/natives/nativebuilder.ts b/src/natives/nativebuilder.ts new file mode 100644 index 0000000..a5d816c --- /dev/null +++ b/src/natives/nativebuilder.ts @@ -0,0 +1,197 @@ +import {CallExpression, identifier, Identifier, NewExpression, OptionalCallExpression} from "@babel/types"; +import Solver from "../analysis/solver"; +import {NativeObjectToken, PackageObjectToken} from "../analysis/tokens"; +import {ModuleInfo} from "../analysis/infos"; +import {ecmascriptModels} from "./ecmascript"; +import {NodePath} from "@babel/traverse"; +import {nodejsModels} from "./nodejs"; +import {options} from "../options"; +import logger from "../misc/logger"; +import {Operations} from "../analysis/operations"; + +export type CallNodePath = NodePath; + +export type NativeModelParams = {solver: Solver, moduleInfo: ModuleInfo, natives: SpecialNativeObjects}; + +export type NativeFunctionParams = NativeModelParams & {op: Operations, path: CallNodePath}; + +export type NativeVariableParams = NativeModelParams & {id: Identifier}; + +export type NativeFunctionAnalyzer = (p: NativeFunctionParams) => void; + +export type NativeModelInitializer = (p: NativeModelParams) => void; + +export type NativeVariableInitializer = (p: NativeVariableParams) => NativeObjectToken | PackageObjectToken; + +export type NativeModel = { + name: string, + init?: NativeModelInitializer, + variables?: Array, + params?: Array, + functions: Array, + classes: Array +} + +export type NativeVariableModel = { + name: string, + init?: NativeVariableInitializer +}; + +export type NativeFunctionModel = { + name: string, + invoke?: NativeFunctionAnalyzer +}; + +export type NativeFieldModel = { + name: string +}; + +export type NativeClassModel = { + name: string, + hidden?: boolean, + invoke?: NativeFunctionAnalyzer, + fields?: Array, // TODO: NativeObjectModel.fields is current unused + staticMethods?: Array, + methods?: Array +}; + +export type SpecialNativeObjects = Map; + +/** + * Prepares models for the ECMAScript and Node.js native declarations. + * Returns the global identifiers and the tokens for special native objects. + */ +export function buildNatives(solver: Solver, moduleInfo: ModuleInfo): [Array, SpecialNativeObjects] { + const globals: Array = []; + const natives: SpecialNativeObjects = new Map; + const a = solver.analysisState; + + const models = [ecmascriptModels, nodejsModels]; + + for (const m of models) { + const loc = {start: {line: 0, column: 0}, end: {line: 0, column: 0}, filename: `%${m.name}`}; // dummy location for global identifiers + + /** + * Adds an identifier to the global scope. + * @param name identifier name + * @param moduleSpecific if true, the token will belong to the current module + * @param invoke optional model of calls if a function + * @param constr if true, the function is a constructor (default: false) + * @param hidden if true, the identifier is not added to globals + * @param init if provided, execute this initializer instead of using a fresh NativeObjectToken + */ + function defineGlobal(name: string, moduleSpecific: boolean = false, invoke?: NativeFunctionAnalyzer, constr: boolean = false, hidden: boolean = false, init?: NativeVariableInitializer) { + if (options.natives || m.name === "ecmascript" || (m.name === "nodejs" && ["exports", "module"].includes(name))) { + const id = identifier(name); + id.loc = loc; + if (!hidden) + globals.push(id); + const t = init + ? init({solver, moduleInfo, natives, id}) + : a.canonicalizeToken(new NativeObjectToken(name, moduleSpecific ? moduleInfo : undefined, invoke, constr)); + solver.addTokenConstraint(t, a.varProducer.nodeVar(id)); + natives.set(name, t); + } + } + + /** + * Adds a global function. + */ + function defineGlobalFunction(name: string, invoke: NativeFunctionAnalyzer | undefined, constr: boolean = false, hidden: boolean = false) { + defineGlobal(name, undefined, invoke, constr, hidden); + } + + /** + * Adds a prototype object for a class. + */ + function definePrototypeObject(name: string): NativeObjectToken { + const t = a.canonicalizeToken(new NativeObjectToken(`${name}.prototype`)); + if (m.name === "ecmascript") + natives.set(t.name, t); + if (options.natives || m.name === "ecmascript") + solver.addTokenConstraint(t, a.varProducer.objPropVar(natives.get(name)!, "prototype")); + return t; + } + + /** + * Adds a field to an object. + */ + function defineField(x: NativeClassModel, f: NativeFieldModel) { + // TODO: defineField (ignored for now...) + } + + /** + * Adds a static method to an object. + */ + function defineStaticMethod(x: NativeClassModel, f: NativeFunctionModel) { + if (options.natives) { + const t = a.canonicalizeToken(new NativeObjectToken(`${x.name}.${f.name}`, undefined, f.invoke)); + if (m.name === "ecmascript") + natives.set(t.name, t); + solver.addTokenConstraint(t, a.varProducer.objPropVar(natives.get(x.name)!, f.name)); + } + } + + /** + * Adds a (non-static) method to an object. + */ + function defineMethod(x: NativeClassModel, f: NativeFunctionModel, pro: NativeObjectToken) { + if (options.natives) { + const t = a.canonicalizeToken(new NativeObjectToken(`${x.name}.prototype.${f.name}`, undefined, f.invoke)); + if (m.name === "ecmascript") + natives.set(t.name, t); + solver.addTokenConstraint(t, a.varProducer.objPropVar(pro, f.name)); + } + } + + if (logger.isVerboseEnabled()) + logger.verbose(`Adding ${m.name}`); + + // implicit parameters + if (m.params) + for (const v of m.params) + defineGlobal(v.name, true); + + // global variables + if (m.variables) + for (const v of m.variables) + defineGlobal(v.name, undefined, undefined, undefined, undefined, v.init); + + // global functions + for (const f of m.functions) + defineGlobalFunction(f.name, f.invoke); + + // global classes + for (const x of m.classes) { + defineGlobalFunction(x.name, x.invoke, true, x.hidden); + const pro = definePrototypeObject(x.name); + + // fields + if (x.fields) + for (const v of x.fields) + defineField(x, v); + + // static methods + if (x.staticMethods) + for (const f of x.staticMethods) + defineStaticMethod(x, f); + + // non-static methods + if (x.methods) + for (const f of x.methods) + defineMethod(x, f, pro); + } + } + + // specialized initialization + for (const m of models) + if (m.init) { + if (logger.isVerboseEnabled()) + logger.verbose(`Running initialization for ${m.name}`); + m.init({solver, moduleInfo, natives}); + } + if (logger.isVerboseEnabled()) + logger.verbose("Adding natives completed"); + + return [globals, natives]; +} diff --git a/src/natives/nativehelpers.ts b/src/natives/nativehelpers.ts new file mode 100644 index 0000000..b5eb370 --- /dev/null +++ b/src/natives/nativehelpers.ts @@ -0,0 +1,802 @@ +import {CallExpression, Expression, isExpression, isIdentifier} from "@babel/types"; +import { + AllocationSiteToken, + ArrayToken, + FunctionToken, + NativeObjectToken, + ObjectKind, + ObjectToken, + PackageObjectToken, + Token +} from "../analysis/tokens"; +import {getBaseAndProperty, isParentExpressionStatement} from "../misc/asthelpers"; +import {Node} from "@babel/core"; +import { + ARRAY_PROTOTYPE, + GENERATOR_PROTOTYPE_NEXT, + MAP_KEYS, + MAP_VALUES, + OBJECT_PROTOTYPE, + PROMISE_FULFILLED_VALUES, + PROMISE_PROTOTYPE, + PROMISE_REJECTED_VALUES, + SET_VALUES +} from "./ecmascript"; +import {NativeFunctionParams} from "./nativebuilder"; +import {TokenListener} from "../analysis/listeners"; +import assert from "assert"; +import {NodePath} from "@babel/traverse"; +import {Operations} from "../analysis/operations"; + +/** + * Models an assignment from a function parameter (0-based indexing) to a property of the base object. + */ +export function assignParameterToThisProperty(param: number, prop: string, p: NativeFunctionParams) { + if (p.path.node.arguments.length > param) { + const bp = getBaseAndProperty(p.path); + const arg = p.path.node.arguments[param]; + if (isExpression(arg) && bp) { // TODO: non-expression arguments? + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_1, arg, (t: Token) => { + if (t instanceof NativeObjectToken || t instanceof AllocationSiteToken || t instanceof FunctionToken || t instanceof PackageObjectToken) { + const argVar = a.varProducer.expVar(arg, p.path); + p.solver.addSubsetConstraint(argVar, a.varProducer.objPropVar(t, prop)); + } + }); + } + } +} + +/** + * Assigns from the given expression to an unknown entry of the given array object. + */ +function assignExpressionToArrayValue(from: Expression, t: ArrayToken, p: NativeFunctionParams) { + const a = p.solver.analysisState; + const argVar = a.varProducer.expVar(from, p.path); + p.solver.addSubsetConstraint(argVar, a.varProducer.arrayValueVar(t)); + p.solver.addForAllArrayEntriesConstraint(t, TokenListener.NATIVE_13, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(argVar, a.varProducer.objPropVar(t, prop)); + }); +} + +/** + * Models an assignment from a function parameter (0-based indexing) to an unknown entry of the base array object. + */ +export function assignParameterToThisArrayValue(param: number, p: NativeFunctionParams) { + if (p.path.node.arguments.length > param) { + const bp = getBaseAndProperty(p.path); + const arg = p.path.node.arguments[param]; + if (isExpression(arg) && bp) { // TODO: non-expression arguments? + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_2, arg, (t: Token) => { + if (t instanceof ArrayToken) + assignExpressionToArrayValue(arg, t, p); + }); + } + } +} + +/** + * Models an assignment from a function parameter (0-based indexing) to an unknown entry of the given array object. + */ +export function assignParameterToArrayValue(param: number, t: ArrayToken, p: NativeFunctionParams) { + if (p.path.node.arguments.length > param) { + const arg = p.path.node.arguments[param]; + if (isExpression(arg)) // TODO: non-expression arguments? + assignExpressionToArrayValue(arg, t, p); + } +} + +/** + * Models a property of the base object being returned from a function. + */ +export function returnThisProperty(prop: string, p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_3, p.path.node, (t: Token) => { + if (t instanceof NativeObjectToken || t instanceof AllocationSiteToken || t instanceof FunctionToken || t instanceof PackageObjectToken) + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), a.varProducer.nodeVar(p.path.node)); + }); + } +} + +/** + * Models the base object being returned from a function. + */ +export function returnThis(p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(p.path.node)); + } +} + +/** + * Models the base object wrapped into a promise being returned from a function. + */ +export function returnThisInPromise(p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + const promise = newObject("Promise", p.natives.get(PROMISE_PROTOTYPE)!, p); + p.solver.addSubsetConstraint(baseVar, a.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + p.solver.addTokenConstraint(promise, a.varProducer.nodeVar(p.path.node)); + } +} + +/** + * Models an unknown entry of the base array object being returned from a function. + */ +export function returnArrayValue(p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_4, p.path.node, (t: Token) => { + if (t instanceof ArrayToken) { + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t), a.varProducer.nodeVar(p.path.node)); + p.solver.addForAllArrayEntriesConstraint(t, TokenListener.NATIVE_14, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), a.varProducer.nodeVar(p.path.node)); + }); + } + }); + } +} + +/** + * Models creation of an array that contains the same values as in the base array but in unknown order. + */ +export function returnShuffledArray(p: NativeFunctionParams): ArrayToken | undefined { + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const res = newArray(p); + returnToken(res, p); + const resVar = a.varProducer.arrayValueVar(res); + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_5, p.path.node, (t: Token) => { + if (t instanceof ArrayToken) { + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t), resVar); + p.solver.addForAllArrayEntriesConstraint(t, TokenListener.NATIVE_15, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), resVar); + }); + } + }); + return res; + } else + return undefined; +} + +/** + * Models an unknown permutation of the values in the base array, returning the base array. + */ +export function returnShuffledInplace(p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_6, p.path.node, (t: Token) => { + if (t instanceof ArrayToken) { + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t), baseVar); + p.solver.addForAllArrayEntriesConstraint(t, TokenListener.NATIVE_16, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), baseVar); + }); + } + }); + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(p.path.node)); + } +} + +/** + * Warns about use of a native function. + */ +export function warnNativeUsed(name: string, p: NativeFunctionParams, extra?: string) { + p.solver.analysisState.warnUnsupported(p.path.node, `Call to '${name}'${extra ? ` ${extra}` : ""}`, true); +} + +/** + * Models that an object represented by the current PackageObjectToken is returned. + */ +export function returnPackageObject(p: NativeFunctionParams) { + const a = p.solver.analysisState; + p.solver.addTokenConstraint(a.canonicalizeToken(new PackageObjectToken(p.moduleInfo.packageInfo)), a.varProducer.expVar(p.path.node, p.path)); +} + +/** + * Ensures that objects at the given expression are widened to field-based analysis. + */ +export function widenArgument(arg: Node, p: NativeFunctionParams) { + if (isExpression(arg)) { // TODO: non-Expression arguments? + const a = p.solver.analysisState; + a.registerEscaping(a.varProducer.expVar(arg, p.path)); // triggers widening to field-based + } +} + +/** + * Models flow from the given expression to the function return. + */ +export function returnArgument(arg: Node, p: NativeFunctionParams) { + if (isExpression(arg)) { // TODO: non-Expression arguments? + const a = p.solver.analysisState; + p.solver.addSubsetConstraint(a.varProducer.expVar(arg, p.path), a.varProducer.expVar(p.path.node, p.path)); + } +} + +/** + * Creates a new AllocationSiteToken with the given kind and prototype. + */ +export function newObject(kind: ObjectKind, proto: NativeObjectToken | PackageObjectToken, p: NativeFunctionParams): AllocationSiteToken { + const t = p.solver.analysisState.canonicalizeToken( + kind ==="Object" ? new ObjectToken(p.path.node, p.moduleInfo.packageInfo) : + kind === "Array" ? new ArrayToken(p.path.node, p.moduleInfo.packageInfo) : + new AllocationSiteToken(kind, p.path.node, p.moduleInfo.packageInfo)); + p.solver.addInherits(t, proto); + return t; +} + +/** + * Creates a new PackageObjectToken with the given kind and prototype. + */ +export function newPackageObject(kind: ObjectKind, proto: NativeObjectToken | PackageObjectToken, p: NativeFunctionParams): PackageObjectToken { + const t = p.solver.analysisState.canonicalizeToken(new PackageObjectToken(p.moduleInfo.packageInfo, kind)); + p.solver.addInherits(t, proto); + return t; +} + +/** + * Creates a new array represented by an ArrayToken. + */ +export function newArray(p: NativeFunctionParams): ArrayToken { + const a = p.solver.analysisState; + const t = a.canonicalizeToken(new ArrayToken(p.path.node, p.moduleInfo.packageInfo)); + p.solver.addInherits(t, p.natives.get(ARRAY_PROTOTYPE)!); + return t; +} + +/** + * Models flow of the given token to the function return. + */ +export function returnToken(t: Token, p: NativeFunctionParams) { + const a = p.solver.analysisState; + p.solver.addTokenConstraint(t, a.varProducer.expVar(p.path.node, p.path)); +} + +type IteratorKind = + "ArrayKeys" | + "ArrayValues" | + "ArrayEntries" | + "SetValues" | + "SetEntries" | + "MapKeys" | + "MapValues" | + "MapEntries"; + +/** + * Models returning an Iterator object for the given kind of base object. + */ +export function returnIterator(kind: IteratorKind, p: NativeFunctionParams) { // TODO: see also astvisitor.ts:ForOfStatement + const bp = getBaseAndProperty(p.path); + if (!isParentExpressionStatement(p.path) && bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_7, p.path.node, (t: Token) => { + if (t instanceof AllocationSiteToken) { + const iter = a.canonicalizeToken(new AllocationSiteToken("Iterator", t.allocSite, p.moduleInfo.packageInfo)); + p.solver.addTokenConstraint(iter, a.varProducer.expVar(p.path.node, p.path)); + const iterNext = a.varProducer.objPropVar(iter, "next"); + p.solver.addTokenConstraint(p.natives.get(GENERATOR_PROTOTYPE_NEXT)!, iterNext); + const iterValue = a.varProducer.objPropVar(iter, "value"); + switch (kind) { + case "ArrayKeys": { + if (t.kind !== "Array") + break; + // do nothing, the iterator values are just primitives + break; + } + case "ArrayValues": { + if (t.kind !== "Array") + break; + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t), iterValue); + p.solver.addForAllArrayEntriesConstraint(t, TokenListener.NATIVE_17, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), iterValue); + }); + break; + } + case "ArrayEntries": { + if (t.kind !== "Array") + break; + const pair = a.canonicalizeToken(new ArrayToken(p.path.node, p.moduleInfo.packageInfo)); // TODO: see newArrayToken + p.solver.addInherits(t, p.natives.get(ARRAY_PROTOTYPE)!); + p.solver.addTokenConstraint(pair, iterValue); + const oneVar = a.varProducer.objPropVar(pair, "1"); + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t), oneVar); + p.solver.addForAllArrayEntriesConstraint(t, TokenListener.NATIVE_18, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, prop), oneVar); + }); + break; + } + case "SetValues": { + if (t.kind !== "Set") + break; + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, SET_VALUES), iterValue); + break; + } + case "SetEntries": { + if (t.kind !== "Set") + break; + const pair = a.canonicalizeToken(new ArrayToken(p.path.node, p.moduleInfo.packageInfo)); // TODO: see newArrayToken + p.solver.addInherits(t, p.natives.get(ARRAY_PROTOTYPE)!); + p.solver.addTokenConstraint(pair, iterValue); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, SET_VALUES), a.varProducer.objPropVar(pair, "0")); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, SET_VALUES), a.varProducer.objPropVar(pair, "1")); + break; + } + case "MapKeys": { + if (t.kind !== "Map") + break; + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, MAP_KEYS), iterValue); + break; + } + case "MapValues": { + if (t.kind !== "Map") + break; + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, MAP_VALUES), iterValue); + break; + } + case "MapEntries": { + if (t.kind !== "Map") + break; + const pair = a.canonicalizeToken(new ArrayToken(p.path.node, p.moduleInfo.packageInfo)); // TODO: see newArrayToken + p.solver.addInherits(t, p.natives.get(ARRAY_PROTOTYPE)!); + p.solver.addTokenConstraint(pair, iterValue); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, MAP_KEYS), a.varProducer.objPropVar(pair, "0")); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, MAP_VALUES), a.varProducer.objPropVar(pair, "1")); + break; + } + } // TODO: also handle TypedArray + } + // TODO: also handle arguments, user-defined... + }); + } +} + +type CallbackKind = + "Array.prototype.forEach" | + "Array.prototype.every" | + "Array.prototype.filter" | + "Array.prototype.find" | + "Array.prototype.findIndex" | + "Array.prototype.flatMap" | + "Array.prototype.map" | + "Array.prototype.reduce" | + "Array.prototype.reduceRight" | + "Array.prototype.some" | + "Array.prototype.sort" | + "Map.prototype.forEach" | + "Set.prototype.forEach" | + "Promise.prototype.then$onFulfilled" | + "Promise.prototype.then$onRejected" | + "Promise.prototype.catch$onRejected" | + "Promise.prototype.finally$onFinally"; + +/** + * Models call to a callback. + */ +export function invokeCallback(kind: CallbackKind, p: NativeFunctionParams, arg: number = 0, key: TokenListener = TokenListener.NATIVE_INVOKE_CALLBACK) { + const args = p.path.node.arguments; + if (args.length > arg) { + const funarg = args[arg]; + const bp = getBaseAndProperty(p.path); + if (isExpression(funarg) && bp) { // TODO: SpreadElement? non-MemberExpression? + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + const funVar = a.varProducer.expVar(funarg, p.path); + const caller = a.getEnclosingFunctionOrModule(p.path, p.moduleInfo); + p.solver.addForAllPairsConstraint(baseVar, funVar, key, p.path.node, (bt: AllocationSiteToken, ft: FunctionToken) => { // TODO: ignoring native functions etc. + a.registerCall(p.path.node, p.moduleInfo, {native: true}); + a.registerCallEdge(p.path.node, caller, a.functionInfos.get(ft.fun)!, {native: true}); // TODO: call graph edges for promise-related calls? + const param1 = ft.fun.params.length > 0 && isIdentifier(ft.fun.params[0]) ? ft.fun.params[0] : undefined; // TODO: non-Identifier parameters? + const param2 = ft.fun.params.length > 1 && isIdentifier(ft.fun.params[1]) ? ft.fun.params[1] : undefined; + const param3 = ft.fun.params.length > 2 && isIdentifier(ft.fun.params[2]) ? ft.fun.params[2] : undefined; + const param4 = ft.fun.params.length > 3 && isIdentifier(ft.fun.params[3]) ? ft.fun.params[3] : undefined; + const connectThis = () => { // TODO: only bind 'this' if the callback is a proper function (not a lambda?) + if (args.length > 1 && isExpression(args[1])) { // TODO: SpreadElement + // bind thisArg to 'this' of the callback + const thisArgVar = a.varProducer.expVar(args[1], p.path); + p.solver.addSubsetConstraint(thisArgVar, a.varProducer.thisVar(ft.fun)); + } + } + switch (kind) { + case "Array.prototype.forEach": + case "Array.prototype.every": + case "Array.prototype.filter": + case "Array.prototype.find": + case "Array.prototype.findIndex": + case "Array.prototype.flatMap": + case "Array.prototype.map": + case "Array.prototype.some": + // write array elements to param1 + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(bt), a.varProducer.nodeVar(param1)); + p.solver.addForAllArrayEntriesConstraint(bt, TokenListener.NATIVE_19, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, prop), a.varProducer.nodeVar(param1)); + }); + switch (kind) { + case "Array.prototype.map": + // return new array with elements from the callback return values + const t = newArray(p); + p.solver.addSubsetConstraint(a.varProducer.returnVar(ft.fun), a.varProducer.arrayValueVar(t)); + returnToken(t, p); + break; + case "Array.prototype.flatMap": + warnNativeUsed("Array.prototype.flatMap", p, "(return value ignored)"); // TODO: flatMap return value... + break; + } + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(param3)); + connectThis(); + // TODO: array functions are generic (can be applied to any array-like object, including strings), can also be sub-classed + break; + case "Array.prototype.reduce": + case "Array.prototype.reduceRight": + // write array elements to param2 + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(bt), a.varProducer.nodeVar(param2)); + p.solver.addForAllArrayEntriesConstraint(bt, TokenListener.NATIVE_20, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, prop), a.varProducer.nodeVar(param2)); + }); + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(param4)); + // bind initialValue to previousValue + if (args.length > 1 && isExpression(args[1])) { // TODO: SpreadElement + const thisArgVar = a.varProducer.expVar(args[1], p.path); + p.solver.addSubsetConstraint(thisArgVar, a.varProducer.nodeVar(param1)); + } + // connect callback return value to previousValue and to result + const retVar = a.varProducer.returnVar(ft.fun); + p.solver.addSubsetConstraint(retVar, a.varProducer.nodeVar(param1)); + p.solver.addSubsetConstraint(retVar, a.varProducer.expVar(p.path.node, p.path)); + break; + case "Array.prototype.sort": + // write array elements to param1 and param2 and to the array + const btVar = a.varProducer.arrayValueVar(bt); + p.solver.addSubsetConstraint(btVar, a.varProducer.nodeVar(param1)); + p.solver.addSubsetConstraint(btVar, a.varProducer.nodeVar(param2)); + p.solver.addForAllArrayEntriesConstraint(bt, TokenListener.NATIVE_21, p.path.node, (prop: string) => { + const btPropVar = a.varProducer.objPropVar(bt, prop) + p.solver.addSubsetConstraint(btPropVar, a.varProducer.nodeVar(param1)); + p.solver.addSubsetConstraint(btPropVar, a.varProducer.nodeVar(param2)); + p.solver.addSubsetConstraint(btPropVar, btVar); + }); + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(p.path.node)); + break; + case "Map.prototype.forEach": + if (bt.kind !== "Map") + break; + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, MAP_VALUES), a.varProducer.nodeVar(param1)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, MAP_KEYS), a.varProducer.nodeVar(param2)); + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(param3)); + connectThis(); + break; + case "Set.prototype.forEach": + if (bt.kind !== "Set") + break; + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, SET_VALUES), a.varProducer.nodeVar(param1)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, SET_VALUES), a.varProducer.nodeVar(param2)); + p.solver.addSubsetConstraint(baseVar, a.varProducer.nodeVar(param3)); // TODO: what if called via e.g. bind? (same for other baseVar constraints above) + connectThis(); + break; + case "Promise.prototype.then$onFulfilled": + case "Promise.prototype.then$onRejected": + case "Promise.prototype.catch$onRejected": + case "Promise.prototype.finally$onFinally": + if (bt.kind !== "Promise") + break; + let prop, key; + switch (kind) { + case "Promise.prototype.then$onFulfilled": + prop = PROMISE_FULFILLED_VALUES; + key = TokenListener.CALL_PROMISE_ONFULFILLED; + break; + case "Promise.prototype.then$onRejected": + case "Promise.prototype.catch$onRejected": + prop = PROMISE_REJECTED_VALUES; + key = TokenListener.CALL_PROMISE_ONREJECTED; + break; + case "Promise.prototype.finally$onFinally": + prop = undefined; + key = TokenListener.CALL_PROMISE_ONFINALLY; + break; + } + // create a new promise + const thenPromise = a.canonicalizeToken(new AllocationSiteToken("Promise", p.path.node, p.moduleInfo.packageInfo)); + p.solver.addInherits(thenPromise, p.natives.get(PROMISE_PROTOTYPE)!); + if (prop) { + // assign promise fulfilled/rejected value to the callback parameter + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, prop), a.varProducer.nodeVar(param1)); + } + // for all return values of the callback... + p.solver.addForAllConstraint(a.varProducer.returnVar(ft.fun), key, p.path.node, (t: Token) => { + if (t instanceof AllocationSiteToken && t.kind === "Promise") { + // callback return value is a promise, transfer its values to the new promise + if (kind !== "Promise.prototype.finally$onFinally") + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_FULFILLED_VALUES), a.varProducer.objPropVar(thenPromise, PROMISE_FULFILLED_VALUES)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_REJECTED_VALUES), a.varProducer.objPropVar(thenPromise, PROMISE_REJECTED_VALUES)); + } else if (kind !== "Promise.prototype.finally$onFinally") { + // callback return value is a non-promise value, assign it to the fulfilled value of the new promise + p.solver.addTokenConstraint(t, a.varProducer.objPropVar(thenPromise, PROMISE_FULFILLED_VALUES)); + } + }); + // use identity function as onFulfilled handler at catch + if (kind === "Promise.prototype.catch$onRejected") + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, PROMISE_FULFILLED_VALUES), a.varProducer.objPropVar(thenPromise, PROMISE_FULFILLED_VALUES)); + // pipe through fulfilled/rejected values at finally + else if (kind === "Promise.prototype.finally$onFinally") { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, PROMISE_FULFILLED_VALUES), a.varProducer.objPropVar(thenPromise, PROMISE_FULFILLED_VALUES)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(bt, PROMISE_REJECTED_VALUES), a.varProducer.objPropVar(thenPromise, PROMISE_REJECTED_VALUES)); + } + // TODO: should use identity function for onFulfilled/onRejected in general if funVar is a non-function value + // return the new promise + p.solver.addTokenConstraint(thenPromise, a.varProducer.expVar(p.path.node, p.path)); + break; + } + }); + } + } +} + +/** + * Models flow from values of the given iterator's values to the given object property. + */ +export function assignIteratorValuesToProperty(param: number, t: AllocationSiteToken, prop: string, p: NativeFunctionParams) { + const arg = p.path.node.arguments[param]; + if (isExpression(arg)) { // TODO: non-Expression argument + const src = p.op.expVar(arg, p.path); + const dst = p.solver.analysisState.varProducer.objPropVar(t, prop); + p.op.readIteratorValue(src, dst, p.path.node); + } +} + +/** + * Models flow from values of the given iterator's values to unknown values of the given array. + */ +export function assignIteratorValuesToArrayValue(param: number, t: ArrayToken, p: NativeFunctionParams) { + const arg = p.path.node.arguments[param]; + if (isExpression(arg)) { // TODO: non-Expression argument + const src = p.op.expVar(arg, p.path); + const dst = p.solver.analysisState.varProducer.arrayValueVar(t); + p.op.readIteratorValue(src, dst, p.path.node); + } +} + +/** + * Models flow from key-value pairs of the given iterator's values to the given object key and value properties. + */ +export function assignIteratorMapValuePairs(param: number, t: AllocationSiteToken, keys: string | null, values: string, p: NativeFunctionParams) { + const arg = p.path.node.arguments[param]; + if (isExpression(arg)) { // TODO: non-Expression argument + const a = p.solver.analysisState; + const src = p.op.expVar(arg, p.path); + const dst = a.varProducer.intermediateVar(p.path.node, "assignIteratorValuePairsToProperties"); + p.op.readIteratorValue(src, dst, p.path.node); + p.solver.addForAllConstraint(dst, TokenListener.NATIVE_8, p.path.node, (t2: Token) => { + if (t2 instanceof ArrayToken) { + if (keys) + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t2, "0"), a.varProducer.objPropVar(t, keys)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t2, "1"), a.varProducer.objPropVar(t, values)); + } + }); + } +} + +/** + * Models flow from values of the base array to the given array, in unknown order. + */ +export function assignBaseArrayValueToArray(t: ArrayToken, p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + const dst = p.solver.analysisState.varProducer.arrayValueVar(t); + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_9, p.path.node, (t2: Token) => { + if (t2 instanceof ArrayToken) { + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t2), dst); + p.solver.addForAllArrayEntriesConstraint(t2, TokenListener.NATIVE_22, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t2, prop), dst); + }); + } + }); + } +} + +/** + * Models flow from values of array values of the base array to the given array, in unknown order. + */ +export function assignBaseArrayArrayValueToArray(t: ArrayToken, p: NativeFunctionParams) { + const bp = getBaseAndProperty(p.path); + if (bp) { + const a = p.solver.analysisState; + const baseVar = a.varProducer.expVar(bp.base, p.path); + const dst = p.solver.analysisState.varProducer.arrayValueVar(t); + const f = (t3: Token) => { + if (t3 instanceof ArrayToken) { + p.solver.addSubsetConstraint(a.varProducer.arrayValueVar(t3), dst); + p.solver.addForAllArrayEntriesConstraint(t3, TokenListener.NATIVE_23, p.path.node, (prop: string) => { + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t3, prop), dst); + }); + } + }; + p.solver.addForAllConstraint(baseVar, TokenListener.NATIVE_10, p.path.node, (t2: Token) => { + if (t2 instanceof ArrayToken) { + p.solver.addForAllConstraint(a.varProducer.arrayValueVar(t2), TokenListener.NATIVE_11, p.path.node, f); + p.solver.addForAllArrayEntriesConstraint(t2, TokenListener.NATIVE_24, p.path.node, (prop: string) => { + p.solver.addForAllConstraint(a.varProducer.objPropVar(t2, prop), TokenListener.NATIVE_12, p.path.node, f); + }); + } + }); + } +} + +/** + * Models call to a promise executor with resolveFunction and rejectFunction as arguments. + */ +export function callPromiseExecutor(promise: AllocationSiteToken, resolveFunction: AllocationSiteToken, rejectFunction: AllocationSiteToken, p: NativeFunctionParams) { + const args = p.path.node.arguments; + if (args.length >= 1 && isExpression(args[0])) { // TODO: SpreadElement? non-MemberExpression? + const a = p.solver.analysisState; + const funVar = a.varProducer.expVar(args[0], p.path); + p.solver.addForAllConstraint(funVar, TokenListener.CALL_PROMISE_EXECUTOR, p.path.node, (t: Token) => { + if (t instanceof FunctionToken) { + // TODO: register call and call edge for implicit call to promise executor? + const param1 = t.fun.params.length > 0 && isIdentifier(t.fun.params[0]) ? t.fun.params[0] : undefined; // TODO: non-Identifier parameters? + const param2 = t.fun.params.length > 1 && isIdentifier(t.fun.params[1]) ? t.fun.params[1] : undefined; + p.solver.addTokenConstraint(resolveFunction, a.varProducer.nodeVar(param1)); + p.solver.addTokenConstraint(rejectFunction, a.varProducer.nodeVar(param2)); + } + }); + } +} + +/** + * Models call to a promise resolve or reject function. + */ +export function callPromiseResolve(t: ObjectToken, args: CallExpression["arguments"], path: NodePath, op: Operations) { + if (args.length >= 1 && isExpression(args[0])) { // TODO: non-Expression? + const arg = op.expVar(args[0], path); + if (arg) { + // find the current promise + const promise = op.a.canonicalizeToken(new AllocationSiteToken("Promise", t.allocSite, t.packageInfo)); + switch (t.kind) { + case "PromiseResolve": + // for all argument values... + op.solver.addForAllConstraint(arg, TokenListener.CALL_PROMISE_RESOLVE, path.node, (vt: Token) => { + if (vt instanceof AllocationSiteToken && vt.kind === "Promise") { + // argument is a promise, transfer its values to the current promise + op.solver.addSubsetConstraint(op.varProducer.objPropVar(vt, PROMISE_FULFILLED_VALUES), op.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + op.solver.addSubsetConstraint(op.varProducer.objPropVar(vt, PROMISE_REJECTED_VALUES), op.varProducer.objPropVar(promise, PROMISE_REJECTED_VALUES)); + } else { + // argument is a non-promise value, assign it to the fulfilled value of the current promise + op.solver.addTokenConstraint(vt, op.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + } + }); + break; + case "PromiseReject": + // assign the argument value to the rejected value of the current promise + op.solver.addSubsetConstraint(arg, op.varProducer.objPropVar(promise, PROMISE_REJECTED_VALUES)); + break; + default: + assert.fail(); + } + } + } +} + +/** + * Models a call to Promise.resolve or Promise.reject. + */ +export function returnResolvedPromise(kind: "resolve" | "reject", p: NativeFunctionParams) { + const a = p.solver.analysisState; + const args = p.path.node.arguments; + // make a new promise and return it + const promise = newObject("Promise", p.natives.get(PROMISE_PROTOTYPE)!, p); + p.solver.addTokenConstraint(promise, a.varProducer.expVar(p.path.node, p.path)); + if (args.length >= 1 && isExpression(args[0])) { // TODO: non-Expression? + const arg = p.op.expVar(args[0], p.path); + if (arg) { + let prop: string, key; + switch (kind) { + case "resolve": + prop = PROMISE_FULFILLED_VALUES; + key = TokenListener.MAKE_PROMISE_RESOLVE; + break; + case "reject": + prop = PROMISE_REJECTED_VALUES; + key = TokenListener.MAKE_PROMISE_REJECT; + break; + } + p.solver.addForAllConstraint(arg, key, p.path.node, (vt: Token) => { + if (vt instanceof AllocationSiteToken && vt.kind === "Promise") { + // argument is a promise, return it + p.solver.addTokenConstraint(vt, a.varProducer.expVar(p.path.node, p.path)); + } else { + // argument is a non-promise value, assign it to the fulfilled value of the new promise + p.solver.addTokenConstraint(vt, a.varProducer.objPropVar(promise, prop)); + } + }); + } + } +} + +/** + * Models a call to Promise.all, Promise.allSettled, Promise.any or Promise.race. + */ +export function returnPromiseIterator(kind: "all" | "allSettled" | "any" | "race", p: NativeFunctionParams) { + const a = p.solver.analysisState; + const args = p.path.node.arguments; + if (args.length >= 1 && isExpression(args[0])) { // TODO: non-Expression? + const arg = p.op.expVar(args[0], p.path); + if (arg) { + // make a new promise and return it + const promise = newObject("Promise", p.natives.get(PROMISE_PROTOTYPE)!, p); + p.solver.addTokenConstraint(promise, a.varProducer.expVar(p.path.node, p.path)); + let array: ArrayToken | undefined; + if (kind === "all" || kind === "allSettled") { + // add a new array as fulfilled value + array = newArray(p); + p.solver.addTokenConstraint(array, a.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + } + let allSettledObjects: AllocationSiteToken | undefined; + if (kind === "allSettled") { + // add a new object to the array + allSettledObjects = newObject("Object", p.natives.get(OBJECT_PROTOTYPE)!, p); + p.solver.addTokenConstraint(allSettledObjects, a.varProducer.arrayValueVar(array!)); + } + // read the iterator values + const tmp = a.varProducer.intermediateVar(p.path.node, "returnPromiseIterator"); + p.op.readIteratorValue(arg, tmp, p.path.node); + let key; + switch (kind) { + case "all": + key = TokenListener.MAKE_PROMISE_ALL; + break; + case "allSettled": + key = TokenListener.MAKE_PROMISE_ALLSETTLED; + break; + case "any": + key = TokenListener.MAKE_PROMISE_ANY; + break; + case "race": + key = TokenListener.MAKE_PROMISE_RACE; + break; + } + p.solver.addForAllConstraint(tmp, key, p.path.node, (t: Token) => { + if (t instanceof AllocationSiteToken && t.kind === "Promise") { + switch (kind) { + case "all": + // assign fulfilled values to the array and rejected values to the new promise + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_FULFILLED_VALUES), a.varProducer.arrayValueVar(array!)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_REJECTED_VALUES), a.varProducer.objPropVar(promise, PROMISE_REJECTED_VALUES)); + break; + case "allSettled": + // assign fulfilled and rejected values to the 'value' and 'reason' properties, respectively + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_FULFILLED_VALUES), a.varProducer.objPropVar(allSettledObjects!, "value")); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_REJECTED_VALUES), a.varProducer.objPropVar(allSettledObjects!, "reason")); + break; + case "any": + // assign fulfilled values to the new promise + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_FULFILLED_VALUES), a.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + // TODO: assign rejected values to an AggregateError object and assign that object to the rejected value of the new promise + break; + case "race": + // assign fulfilled and rejected values to the new promise + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_FULFILLED_VALUES), a.varProducer.objPropVar(promise, PROMISE_FULFILLED_VALUES)); + p.solver.addSubsetConstraint(a.varProducer.objPropVar(t, PROMISE_REJECTED_VALUES), a.varProducer.objPropVar(promise, PROMISE_REJECTED_VALUES)); + break; + } + } + }); + } + } +} diff --git a/src/natives/nodejs.ts b/src/natives/nodejs.ts new file mode 100644 index 0000000..ea95a2c --- /dev/null +++ b/src/natives/nodejs.ts @@ -0,0 +1,157 @@ +import module from "module"; +import {NativeModelParams, NativeFunctionParams, NativeModel, NativeVariableParams} from "./nativebuilder"; + +/** + * Names of Node.js built-in modules. + */ +export const builtinModules = new Set(module.builtinModules); + +/* + * Models of Node.js standard built-in objects. + * See https://nodejs.org/api/ and https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node. + * Primitive values and accessor properties are ignored; only functions and objects are modeled. + */ +export const nodejsModels: NativeModel = { + name: "nodejs", + init: (p: NativeModelParams) => { + // module.exports = exports + p.solver.addTokenConstraint(p.natives.get("exports")!, p.solver.analysisState.varProducer.objPropVar(p.natives.get("module")!, "exports")); + }, + params: [ + { + name: "require" + }, + { + name: "module" + }, + { + name: "exports" + } + ], + variables: [ + { + name: "console" // TODO: see https://nodejs.org/api/console.html + }, + { + name: "global", + init: (p: NativeVariableParams) => { + return p.natives.get("globalThis")!; // TODO: 'global' is actually a property on globalThis + } + }, + { + name: "performance"// TODO: 'performance' is actually a property on globalThis + }, + { + name: "process" // TODO: see https://nodejs.org/api/process.html#process + } + ], + functions: [ // TODO: these are actually properties on globalThis, see also 'fetch' + { + name: "atob" + }, + { + name: "btoa" + }, + { + name: "clearImmediate" + }, + { + name: "clearInterval" + }, + { + name: "clearTimeout" + }, + { + name: "queueMicrotask", + invoke: (p: NativeFunctionParams) => { + // TODO: model callback + } + }, + { + name: "setImmediate", + invoke: (p: NativeFunctionParams) => { + // TODO: model callback + } + }, + { + name: "setInterval", + invoke: (p: NativeFunctionParams) => { + // TODO: model callback + } + }, + { + name: "setTimeout", + invoke: (p: NativeFunctionParams) => { + // TODO: model callback + } + }, + { + name: "structuredClone", + invoke: (p: NativeFunctionParams) => { + // TODO: structuredClone + } + }, + ], + classes: [ + { + name: "AbortController" + // TODO + }, + { + name: "AbortSignal" + // TODO + }, + { + name: "BroadcastChannel" + // TODO + }, + { + name: "Buffer" + // TODO + }, + { + name: "DOMException" + // TODO + }, + { + name: "Event" + // TODO + }, + { + name: "EventTarget" + // TODO + }, + { + name: "FinalizationRegistry" + // TODO + }, + { + name: "MessageChannel" + // TODO + }, + { + name: "MessageEvent" + // TODO + }, + { + name: "MessagePort" + // TODO + }, + { + name: "TextDecoder" + // TODO + }, + { + name: "TextEncoder" + // TODO + }, + { + name: "URL" + // TODO + }, + { + name: "URLSearchParams" + // TODO + } + ] +} diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..18f9971 --- /dev/null +++ b/src/options.ts @@ -0,0 +1,164 @@ +import {OptionValues} from "commander"; +import {resolve} from "path"; +import logger from "./misc/logger"; + +/** + * Command line options (see usage in main.ts). + */ +export const options: { + callgraphHtml: string | undefined, + dataflowHtml: string | undefined, + callgraphGraphviz: string | undefined, + graphvizElideFunctions: boolean, + graphvizPackages: Array | undefined, + dependenciesOnly: boolean, + logfile: string | undefined, + loglevel: string, + loglevelServer: string, + tokens: boolean, + largest: boolean, + soundness: string | undefined, + basedir: string, + callgraphJson: string | undefined, + tokensJson: string | undefined, + bottomUp: boolean, + alloc: boolean, + widening: boolean, + cycleElimination: boolean, + modulesOnly: boolean, + printProgress: boolean, + tty: boolean | undefined, + dynamic: string | undefined, + npmTest: string | undefined, + graalHome: string | undefined, + skipGraalTest: boolean, + ignoreDependencies: boolean, + ignoreUnresolved: boolean, + exclude: Array | undefined, + patterns: Array | undefined, + natives: boolean, + warningsUnsupported: boolean, + timeout: number | undefined, + gc: boolean, + typescript: boolean, + apiUsage: boolean, + apiExported: boolean, + findAccessPaths: string | undefined, + trackedModules: Array | undefined, + compareCallgraphs: boolean, + callgraphImplicit: boolean, + callgraphNative: boolean, + callgraphRequire: boolean, + callgraphExternal: boolean, + diagnosticsJson: string | undefined, + maxRounds: number | undefined, + diagnostics: boolean, + patchDynamics: boolean, + typescriptLibraryUsage: string | undefined, + higherOrderFunctions: boolean, + zeros: boolean, + variableKinds: boolean, + vulnerabilities: string | undefined +} = { + callgraphHtml: undefined, + dataflowHtml: undefined, + callgraphGraphviz: undefined, + graphvizElideFunctions: false, + graphvizPackages: undefined, + dependenciesOnly: false, + logfile: undefined, + loglevel: "info", + loglevelServer: "info", + tokens: false, + largest: false, + soundness: undefined, + basedir: "", + callgraphJson: undefined, + tokensJson: undefined, + bottomUp: false, + alloc: true, + widening: true, + cycleElimination: true, + modulesOnly: false, + printProgress: true, + tty: false, + dynamic: undefined, + npmTest: undefined, + graalHome: undefined, + skipGraalTest: false, + ignoreDependencies: false, + ignoreUnresolved: false, + exclude: undefined, + patterns: undefined, + natives: true, + warningsUnsupported: false, + timeout: undefined, + gc: false, + typescript: false, + apiUsage: false, + apiExported: false, + findAccessPaths: undefined, + trackedModules: undefined, + compareCallgraphs: false, + callgraphImplicit: true, + callgraphNative: true, + callgraphRequire: true, + callgraphExternal: true, + diagnosticsJson: undefined, + maxRounds: undefined, + diagnostics: false, + patchDynamics: true, + typescriptLibraryUsage: undefined, + higherOrderFunctions: false, + zeros: false, + variableKinds: false, + vulnerabilities: undefined +}; + +export function setOptions(opts: OptionValues & Partial) { + for (const opt of Object.getOwnPropertyNames(options)) { + const v = opts[opt]; + if (v !== undefined) + (options as any)[opt] = v; + } + if (options.apiUsage) + options.ignoreDependencies = true; +} + +/** + * Ensures that options.basedir is an absolute path. + * Relative paths are resolved relative to the current working directory. + */ +export function resolveBaseDir() { + options.basedir = resolve(process.cwd(), options.basedir); +} + +const original = Object.assign({}, options); + +/** + * Reset options to defaults. + */ +export function resetOptions() { + Object.assign(options, original); +} + +/** + * Sets default options.trackedModules according to options.patterns and options.apiUsage. + */ +export function setDefaultTrackedModules(globs: Set | undefined) { + options.trackedModules ??= globs !== undefined ? Array.from(globs) : options.apiUsage ? ["**"] : undefined; + if (logger.isVerboseEnabled() && options.trackedModules) { + logger.verbose(`Tracked modules:${options.trackedModules.length > 0 ? "" : " (none)"}`); + for (const g of options.trackedModules) + logger.verbose(` ${g}`); + } +} + +export let patternProperties: Set | undefined = undefined; + +/** + * Sets pattern properties. If defined, object properties not in this set are not tracked in access paths. + */ +export function setPatternProperties(props: Set | undefined) { + patternProperties = props; +} diff --git a/src/output/analysisstatereporter.ts b/src/output/analysisstatereporter.ts new file mode 100644 index 0000000..406f7a8 --- /dev/null +++ b/src/output/analysisstatereporter.ts @@ -0,0 +1,498 @@ +import logger from "../misc/logger"; +import {deleteAll, getOrSet, SourceLocationJSON, sourceLocationToStringWithFile, sourceLocationToStringWithFileAndEnd} from "../misc/util"; +import {AnalysisState} from "../analysis/analysisstate"; +import {FunctionToken, NativeObjectToken, Token} from "../analysis/tokens"; +import fs from "fs"; +import {ConstraintVar, FunctionReturnVar, NodeVar, ObjectPropertyVar} from "../analysis/constraintvars"; +import {FragmentState} from "../analysis/fragmentstate"; +import {relative, resolve} from "path"; +import {options} from "../options"; +import {DummyModuleInfo, FunctionInfo, ModuleInfo} from "../analysis/infos"; +import {Function, isIdentifier, Node, SourceLocation} from "@babel/types"; +import assert from "assert"; +import {AnalysisDiagnostics} from "diagnostics"; +import {CallGraph} from "callgraph"; + +/** + * Functions for reporting information about the analysis state. + */ +export class AnalysisStateReporter { + + readonly a: AnalysisState; + + readonly f: FragmentState; + + constructor(a: AnalysisState, f: FragmentState) { + this.a = a; + this.f = f; + } + + /** + * Saves the constraint variables and their tokens to a JSON file. + */ + saveTokens(outfile: string) { + const fd = fs.openSync(outfile, "w"); + fs.writeSync(fd, "["); + let firstvar = true; + for (const [v, ts] of this.f.getAllVarsAndTokens()) { + if (firstvar) + firstvar = false; + else + fs.writeSync(fd, ","); + fs.writeSync(fd, `\n { "var": ${JSON.stringify(v.toString)}, "tokens": [`); + let firsttoken = true; + for (const t of ts) { + if (firsttoken) + firsttoken = false; + else + fs.writeSync(fd, ","); + fs.writeSync(fd, `\n ${JSON.stringify(t.toString())}`); + } + fs.writeSync(fd, "\n ] }"); + } + fs.writeSync(fd, "\n]"); + fs.closeSync(fd); + logger.info(`Analysis tokens written to ${outfile}`); + } + + /** + * Saves the call graph to a JSON file using the format defined in callgraph.d.ts. + */ + saveCallGraph(outfile: string, files: Array) { + const fd = fs.openSync(outfile, "w"); + fs.writeSync(fd, `{\n "time": "${new Date().toUTCString()}",\n`); + fs.writeSync(fd, ` "entries": [`); + let first = true; + for (const file of files) { + fs.writeSync(fd, `${first ? "" : ","}\n ${JSON.stringify(relative(options.basedir,resolve(options.basedir, file)))}`); + first = false; + } + fs.writeSync(fd, `\n ],\n "files": [`); + const fileIndices = new Map(); + first = true; + for (const m of this.a.moduleInfos.values()) { + fileIndices.set(m, fileIndices.size); + fs.writeSync(fd, `${first ? "" : ","}\n ${JSON.stringify(relative(options.basedir, m.path))}`); + first = false; + } + fs.writeSync(fd, `\n ],\n "functions": {`); + function makeLocStr(fileIndex: number, loc: SourceLocation | undefined | null): string { + return `${fileIndex}:${loc ? `${loc.start.line}:${loc.start.column + 1}:${loc.end.line}:${loc.end.column + 1}` : "?:?:?:?"}`; + } + const functionIndices = new Map(); + first = true; + for (const fun of [...this.a.functionInfos.values(), ...this.a.moduleInfos.values()]) { + const funIndex = functionIndices.size; + functionIndices.set(fun, funIndex); + const fileIndex = fileIndices.get(fun instanceof ModuleInfo ? fun : fun.moduleInfo); + if (fileIndex === undefined) + assert.fail(`File index not found for ${fun}`); + fs.writeSync(fd, `${first ? "" : ","}\n "${funIndex}": ${JSON.stringify(makeLocStr(fileIndex, fun.loc))}`); + first = false; + } + fs.writeSync(fd, `\n },\n "calls": {`); + const callIndices = new Map(); + first = true; + for (const [m, calls] of this.a.calls) + for (const call of calls) { + const callIndex = callIndices.size + functionIndices.size; + callIndices.set(call, callIndex); + const fileIndex = fileIndices.get(m); + if (fileIndex === undefined) + assert.fail(`File index not found for ${m}`); + fs.writeSync(fd, `${first ? "" : ","}\n "${callIndex}": ${JSON.stringify(makeLocStr(fileIndex, call.loc))}`); + first = false; + } + fs.writeSync(fd, `\n },\n "fun2fun": [`); + first = true; + for (const [caller, callees] of [...this.a.functionToFunction, ...(options.callgraphRequire ? this.a.requireGraph : [])]) + for (const callee of callees) { + const callerIndex = functionIndices.get(caller); + if (callerIndex === undefined) + assert.fail(`Function index not found for ${caller}`); + const calleeIndex = functionIndices.get(callee); + if (calleeIndex === undefined) + assert.fail(`Function index not found for ${callee}`); + fs.writeSync(fd, `${first ? "\n " : ", "}[${callerIndex}, ${calleeIndex}]`); + first = false; + } + fs.writeSync(fd, `${first ? "" : "\n "}],\n "call2fun": [`); + first = true; + for (const [call, callIndex] of callIndices) { + const funs = this.a.callToFunction.get(call) || []; + const mods = this.a.callToModule.get(call) || []; + for (const callee of [...funs, ...mods]) { + if (callee instanceof DummyModuleInfo) + continue; // skipping require/import edges to modules that haven't been analyzed + const calleeIndex = functionIndices.get(callee); + if (calleeIndex === undefined) + assert.fail(`Function index not found for ${callee}`); + fs.writeSync(fd, `${first ? "\n " : ", "}[${callIndex}, ${calleeIndex}]`); + first = false; + } + } + fs.writeSync(fd, `${first ? "" : "\n "}],\n "ignore": [`); + first = true; + for (const [m, n] of this.a.artificialFunctions) { + const fileIndex = fileIndices.get(m); + if (fileIndex === undefined) + assert.fail(`File index not found for ${m}`); + fs.writeSync(fd, `${first ? "" : ","}\n ${JSON.stringify(makeLocStr(fileIndex, n.loc))}`); + first = false; + } + fs.writeSync(fd, `${first ? "" : "\n "}]\n}\n`); + fs.closeSync(fd); + logger.info(`Call graph written to ${outfile}`); + } + + /** + * Creates a JSON representation of the call graph using the format defined in callgraph.d.ts. + */ + callGraphToJSON(files: Array): CallGraph { + const cg: CallGraph & {entries: Array, ignore: Array} = { + time: new Date().toUTCString(), + entries: [], + files: [], + functions: [], + calls: [], + fun2fun: [], + call2fun: [], + ignore: [] + }; + for (const file of files) + cg.entries.push(relative(options.basedir,resolve(options.basedir, file))); + const fileIndices = new Map(); + for (const m of this.a.moduleInfos.values()) { + fileIndices.set(m, fileIndices.size); + cg.files.push(relative(options.basedir, m.path)); + } + function makeLocStr(fileIndex: number, loc: SourceLocation | undefined | null): string { + return `${fileIndex}:${loc ? `${loc.start.line}:${loc.start.column + 1}:${loc.end.line}:${loc.end.column + 1}` : "?:?:?:?"}`; + } + const functionIndices = new Map(); + for (const fun of [...this.a.functionInfos.values(), ...this.a.moduleInfos.values()]) { + const funIndex = functionIndices.size; + functionIndices.set(fun, funIndex); + const fileIndex = fileIndices.get(fun instanceof ModuleInfo ? fun : fun.moduleInfo); + if (fileIndex === undefined) + assert.fail(`File index not found for ${fun}`); + cg.functions[funIndex] = makeLocStr(fileIndex, fun.loc); + } + const callIndices = new Map(); + for (const [m, calls] of this.a.calls) + for (const call of calls) { + const callIndex = callIndices.size + functionIndices.size; + callIndices.set(call, callIndex); + const fileIndex = fileIndices.get(m); + if (fileIndex === undefined) + assert.fail(`File index not found for ${m}`); + cg.calls[callIndex] = makeLocStr(fileIndex, call.loc); + } + for (const [caller, callees] of [...this.a.functionToFunction, ...(options.callgraphRequire ? this.a.requireGraph : [])]) + for (const callee of callees) { + const callerIndex = functionIndices.get(caller); + if (callerIndex === undefined) + assert.fail(`Function index not found for ${caller}`); + const calleeIndex = functionIndices.get(callee); + if (calleeIndex === undefined) + assert.fail(`Function index not found for ${callee}`); + cg.fun2fun.push([callerIndex, calleeIndex]); + } + for (const [call, callIndex] of callIndices) { + const funs = this.a.callToFunction.get(call) || []; + const mods = this.a.callToModule.get(call) || []; + for (const callee of [...funs, ...mods]) { + if (callee instanceof DummyModuleInfo) + continue; // skipping require/import edges to modules that haven't been analyzed + const calleeIndex = functionIndices.get(callee); + if (calleeIndex === undefined) + assert.fail(`Function index not found for ${callee}`); + cg.fun2fun.push([callIndex, calleeIndex]); + } + } + for (const [m, n] of this.a.artificialFunctions) { + const fileIndex = fileIndices.get(m); + if (fileIndex === undefined) + assert.fail(`File index not found for ${m}`); + cg.ignore.push(makeLocStr(fileIndex, n.loc)); + } + return cg; + } + + /** + * Reports the token sets for all constraint variables. + * Native variables and object properties with one value are omitted. + */ + reportTokens() { + logger.info("Tokens:"); + for (const [v, ts, size] of this.f.getAllVarsAndTokens()) + if (size > 0 && + !(((v instanceof ObjectPropertyVar && v.obj instanceof NativeObjectToken) || // TODO: don't omit native variables that contain non-native values? + (v instanceof NodeVar && isIdentifier(v.node) && v.node.loc?.start.line === 0)) && size === 1)) { + logger.info(` ${v}: (size ${size})`); + for (const t of ts) + logger.info(` ${t}`); + } + } + + /** + * Reports warnings about unhandled dynamic property write operations with nonempty token sets. + * The source code and the tokens are included in the output if loglevel is verbose or higher. + */ + reportNonemptyUnhandledDynamicPropertyWrites() { + for (const [node, {src, source}] of this.a.unhandledDynamicPropertyWrites.entries()) { + const [size, ts] = this.f.getTokensSize(this.f.getRepresentative(src)); + if (size > 0) { + let funs = 0; + for (const t of ts) + if (t instanceof FunctionToken) + funs++; + this.a.warn(`Nonempty dynamic property write (${funs} function${funs === 1 ? "" : "s"}) at ${sourceLocationToStringWithFile(node.loc)}`); // TODO: may report duplicate warnings + if (logger.isVerboseEnabled() && source !== undefined) { + logger.warn(source); + for (const t of ts) + logger.warn(` ${t}`); + } + } + } + } + + /** + * Reports warnings about unhandled dynamic property read operations. + */ + reportNonemptyUnhandledDynamicPropertyReads() { + for (const node of this.a.unhandledDynamicPropertyReads) + this.a.warn(`Dynamic property read at ${sourceLocationToStringWithFile(node.loc)}`); // TODO: may report duplicate warnings + } + + /** + * Returns the call sites that have zero callees, + * excluding call sites to known native functions or external functions. + */ + getZeroCalleeCalls(): Set { + const calls = new Set(); + for (const c of this.a.callLocations) { + if (!this.a.nativeCallLocations.has(c) && !this.a.externalCallLocations.has(c)) { + const cs = this.a.callToFunction.get(c); + if (!cs || cs.size === 0) + calls.add(c); + } + } + return calls; + } + + /** + * Reports the given set of call sites that have zero callees. + */ + reportZeroCalleeCalls(calls: Set) { + for (const c of calls) + logger.info(`Call with zero callees at ${sourceLocationToStringWithFileAndEnd(c.loc)}`); + } + + /** + * Returns the number of call sites that have zero callees but may call a known native functions (and not an external function). + */ + getZeroButNativeCalleeCalls(): number { + let r = 0; + for (const c of this.a.callLocations) { + if (this.a.nativeCallLocations.has(c) && !this.a.externalCallLocations.has(c)) { + const cs = this.a.callToFunction.get(c); + if (!cs || cs.size === 0) { + r++; + if (logger.isDebugEnabled()) + logger.debug(`Call with native-only callees at ${sourceLocationToStringWithFile(c.loc)}`); + } + } + } + return r; + } + + /** + * Returns the number of call sites that have zero callees but may call an external function (and not a known native function). + */ + getZeroButExternalCalleeCalls(): number { + let r = 0; + for (const c of this.a.callLocations) { + if (this.a.externalCallLocations.has(c) && !this.a.nativeCallLocations.has(c)) { + const cs = this.a.callToFunction.get(c); + if (!cs || cs.size === 0) { + r++; + if (logger.isDebugEnabled()) + logger.debug(`Call with external-only callees at ${sourceLocationToStringWithFile(c.loc)}`); + } + } + } + return r; + } + + /** + * Returns the number of call sites that have zero callees but may call a known native function or an external function. + */ + getZeroButNativeOrExternalCalleeCalls(): number { + let r = 0; + for (const c of this.a.callLocations) { + if (this.a.nativeCallLocations.has(c) && this.a.externalCallLocations.has(c)) { + const cs = this.a.callToFunction.get(c); + if (!cs || cs.size === 0) { + r++; + if (logger.isDebugEnabled()) + logger.debug(`Call with native-or-external-only callees at ${sourceLocationToStringWithFile(c.loc)}`); + } + } + } + return r; + } + + /** + * Returns the number of call sites that have exactly one callee. + */ + getOneCalleeCalls() { + let r = 0; + for (const c of this.a.callLocations) { + const cs = this.a.callToFunction.get(c); + if (cs) + if (cs.size === 1) + r++; + else if (cs.size > 1) + if (logger.isDebugEnabled()) + logger.debug(`Call with multiple callees at ${sourceLocationToStringWithFile(c.loc)}: ${cs.size}`); + } + return r; + } + + /** + * Returns the functions that have zero callers. + */ + getZeroCallerFunctions(): Set { + let funs = new Set(this.a.functionInfos.values()); + for (const fs of this.a.functionToFunction.values()) + deleteAll(fs.values(), funs); + return funs; + } + + /** + * Reports the given set of functions that have zero callers. + */ + reportZeroCallerFunctions(funs: Set) { + for (const f of funs) + logger.info(`Function with zero callers at ${f}`); + } + + /** + * Saves analysis diagnostics in JSON file. + */ + saveDiagnostics(stats: AnalysisDiagnostics, file: string) { + const fd = fs.openSync(file, "w"); + fs.writeSync(fd, JSON.stringify(stats, undefined, 2)); + fs.closeSync(fd); + logger.info(`Analysis diagnostics written to ${file}`); + } + + /** + * Reports the reachable packages and modules. + */ + reportReachablePackagesAndModules() { + logger.info("Packages and modules:"); + for (const p of this.a.packageInfos.values()) { + logger.info(`${p} (${p.dir})`); + for (const m of p.modules.values()) + logger.info(` ${m.getOfficialName()} (${m.relativePath})`); + } + } + + /** + * Reports the largest token sets. + */ + reportLargestTokenSets() { + const a: Array<{v: ConstraintVar, ts: Iterable, size: number}> = []; + for (const [v, ts, size] of this.f.getAllVarsAndTokens()) + a.push({v, ts, size}); + a.sort((x, y) => y.size - x.size); + logger.info("Largest token sets:"); + // for (let i = Math.min(9, a.length - 1); i >= 0; i--) { + for (let i = 0; i < 10 && i < a.length; i++) { + logger.info(` ${a[i].v}: ${a[i].size}`); + if (logger.isVerboseEnabled()) + for (const t of a[i].ts) + logger.info(` ${t}`); + } + } + + /** + * Reports the largest number of outgoing subset edges. + */ + reportLargestSubsetEdges() { + const a: Array<{v: ConstraintVar, vs: Set}> = []; + for (const [v, vs] of this.f.subsetEdges) + a.push({v, vs}); + a.sort((x, y) => y.vs.size - x.vs.size); + logger.info("Largest subset outs:"); + // for (let i = Math.min(9, a.length - 1); i >= 0; i--) { + for (let i = 0; i < 10 && i < a.length; i++) { + logger.info(` ${a[i].v}: ${a[i].vs.size}`); + if (logger.isVerboseEnabled()) + for (const v of a[i].vs) + logger.info(` ${v}`); + } + } + + /** + * Reports higher-order functions. + * Shows the number of function arguments and function return values for each higher-order function. + */ + reportHigherOrderFunctions() { + const funargs = new Map(); + for (const [f, vs] of this.a.functionParameters) { + for (const v of vs) + for (const t of this.f.getTokens(v)) + if (t instanceof FunctionToken) + funargs.set(f, (funargs.get(f) || 0) + 1); + } + const funreturns = new Map(); + for (const v of this.f.vars) + if (v instanceof FunctionReturnVar) + for (const t of this.f.getTokens(v)) + if (t instanceof FunctionToken) + funreturns.set(v.fun, (funreturns.get(v.fun) || 0) + 1); + logger.info("Higher-order functions (function arguments + function return values):"); + const a = []; + for (const f of [...funargs.keys(), ...funreturns.keys()]) + a.push(`${sourceLocationToStringWithFileAndEnd(f.loc)} (${funargs.get(f) ?? 0}+${funreturns.get(f) ?? 0})`); + a.sort(); + for (const f of a) + logger.info(f); + + } + + /** + * Reports the kinds of constraint variables and the number of occurrences for each kind. + */ + reportVariableKinds() { + const varsWithListeners = new Set(); + for (const v of this.f.tokenListeners.keys()) + varsWithListeners.add(v); + for (const [v1, m] of this.f.pairListeners1) { + varsWithListeners.add(v1); + for (const [v2,] of m.values()) + varsWithListeners.add(v2); + } + const counts = new Map(); + const withListenersCounts = new Map(); + const srcCounts = new Map(); + const dstCounts = new Map(); + for (const v of this.f.vars) { + const k = v.getKind(); + counts.set(k, getOrSet(counts, k, () => 0) + 1); + if (varsWithListeners.has(v)) + withListenersCounts.set(k, getOrSet(withListenersCounts, k, () => 0) + 1); + if (this.f.subsetEdges.has(v)) + srcCounts.set(k, getOrSet(srcCounts, k, () => 0) + 1); + if (this.f.reverseSubsetEdges.has(v)) + dstCounts.set(k, getOrSet(dstCounts, k, () => 0) + 1); + } + logger.info("Constraint variable kinds (with listeners, sources, targets):"); + for (const [k, n] of Array.from(counts.entries()).sort(([, n1], [, n2]) => n2 - n1)) + logger.info(`${k}: ${n} (${withListenersCounts.get(k) ?? 0}, ${srcCounts.get(k) ?? 0}, ${dstCounts.get(k) ?? 0})`); + logger.info(`Total: ${this.f.vars.size} (${varsWithListeners.size})`); + } +} diff --git a/src/output/compare.ts b/src/output/compare.ts new file mode 100644 index 0000000..a75f87d --- /dev/null +++ b/src/output/compare.ts @@ -0,0 +1,160 @@ +import {readFileSync} from "fs"; +import {CallGraph} from "callgraph"; +import {addAll, mapGetSet, percent} from "../misc/util"; +import logger from "../misc/logger"; +import assert from "assert"; + +function compareStringArrays(as1: Array | undefined, as2: Array | undefined, file1: string, file2: string, kind: string): Set { + const s = new Set(); + if (as2) + addAll(as2, s); + if (as1) + for (const e of as1) + if (!s.has(e)) + logger.info(`${kind} ${e} found in ${file1}, missing in ${file2}`); + return s; +} + +function loc(str: string, cg: CallGraph, kind: "Function" | "Call"): {str: string, file: string} { + const c1 = str.indexOf(":"); + const c2 = str.indexOf(":", c1 + 1); + const c3 = str.indexOf(":", c2 + 1); + const file = cg.files[Number(str.substring(0, c1))]; + const rest = str.substring(c1 + 1, kind === "Function" ? c3 : undefined); // stripping end locations for functions (workaround like in soundnesstester) + return {str: `${file}:${rest}`, file}; +} + +function compareLocationObjects(o1: {[index: number]: string}, o2: {[index: number]: string}, file1: string, file2: string, cg1: CallGraph, cg2: CallGraph, file2files: Set, kind: "Function" | "Call", ignores: Set) { + const s = new Set(); + for (const loc2 of Object.values(o2)) + s.add(loc(loc2, cg2, kind).str); + for (const loc1 of Object.values(o1)) { + const {str: q, file: f} = loc(loc1, cg1, kind); + if (ignores.has(q)) { + logger.debug(`Ignoring ${q}`); + continue; + } + if (!s.has(q)) { + const extra = !file2files.has(f) ? ` (file ${f} missing)` : ""; + logger.info(`${kind} ${q} found in ${file1}, missing in ${file2}${extra}`); + } + } +} + +function edge(from: string, to: string): string { + return `${from} -> ${to}`; +} + +function compareEdges(es1: Array<[number, number]>, es2: Array<[number, number]>, file1: string, file2: string, cg1: CallGraph, cg2: CallGraph, file2files: Set, kind: "Function" | "Call", prop: "functions" | "calls", ignores: Set): [number, number] { + const s = new Set(); + for (const [i, j] of es2) { + if (!cg2[prop][i]) + assert.fail(`cg2["${prop}"][${i}] is undefined`); + if (!(cg2.functions)[j]) + assert.fail(`cg2.functions[${j}] is undefined`); + const from = loc(cg2[prop][i], cg2, kind).str; + const to = loc((cg2.functions)[j], cg2, "Function").str; + s.add(edge(from, to)); + } + let found = 0, missed = 0; + for (const [i, j] of es1) { // TODO: assuming no duplicate pairs + if (!cg1[prop][i]) + assert.fail(`cg1["${prop}"][${i}] is undefined`); + if (!(cg1.functions)[j]) + assert.fail(`cg1.functions[${j}] is undefined`); + const {str: from, file: ff} = loc(cg1[prop][i], cg1, kind); + const {str: to, file: ft} = loc((cg1.functions)[j], cg1, "Function"); + if (ignores.has(from) || ignores.has(to)) { + logger.debug(`Ignoring ${from} -> ${to}`); + continue; + } + const e = edge(from, to); + if (!s.has(e)) { + const extra = !file2files.has(ff) ? ` (file ${ff} missing)` : !file2files.has(ft) ? ` (file ${ft} missing)` : ""; + logger.info(`${kind}->function edge ${e} found in ${file1}, missing in ${file2}${extra}`); + missed++; + } else + found++; + } + return [found, found + missed]; +} + +// https://manu.sridharan.net/files/ICSE-2013-Approximate.pdf +// https://github.com/asgerf/callgraphjs/blob/master/evaluate.js +function compareCallSiteEdges(cg1: CallGraph, cg2: CallGraph, ignores1: Set, ignores2: Set): {precision: number, recall: number} { + const e1 = new Map>(); + for (const [c, f] of cg1.call2fun) { + const from = loc(cg1.calls[c], cg1, "Call").str; + const to = loc(cg1.functions[f], cg1, "Function").str; + if (ignores2.has(to)) { + logger.debug(`Ignoring ${from} -> ${to}`); + continue; + } + mapGetSet(e1, from).add(to); + } + const e2 = new Map>(); + for (const [c, f] of cg2.call2fun) { + const from = loc(cg2.calls[c], cg2, "Call").str; + const to = loc(cg2.functions[f], cg2, "Function").str; + if (ignores1.has(to)) { + logger.debug(`Ignoring ${from} -> ${to}`); + continue; + } + mapGetSet(e2, from).add(to); + } + const ps = []; + const rs = []; + for (const c of Object.values(cg1.calls)) { + const from = loc(c, cg1, "Call").str; + const s1 = e1.get(from); + if (s1) { + const s2 = e2.get(from) ?? new Set; + const pos = Array.from(s1).reduce((acc, f) => acc + (s2.has(f) ? 1 : 0), 0); + ps.push(s2.size > 0 ? pos / s2.size : 1); + rs.push(pos / s1.size); + } + } + function avg(xs: Array): number { + return xs.reduce((acc, x) => acc + x, 0) / (xs.length || 1); + } + return {precision: avg(ps), recall: avg(rs)}; +} + +function getIgnores(cg: CallGraph): Set { + const s = new Set(); + if (cg.ignore) + for (const p of cg.ignore) + s.add(loc(p, cg, "Function").str); + return s; +} + +/** + * Compares two call graphs, reports missing files, function and edges and precision/recall. + * @param file1 "actual" call graph + * @param file2 "predicted" call graph + */ +export function compareCallGraphs(file1: string, file2: string) { + logger.info(`Comparing ${file1} and ${file2}`); + const cg1 = JSON.parse(readFileSync(file1, "utf8")) as CallGraph; + const cg2 = JSON.parse(readFileSync(file2, "utf8")) as CallGraph; + const ignores1 = getIgnores(cg1); + const ignores2 = getIgnores(cg2); + compareStringArrays(cg1.entries, cg2.entries, file1, file2, "Entry"); + compareStringArrays(cg2.entries, cg1.entries, file2, file1, "Entry"); + const file2files = compareStringArrays(cg1.files, cg2.files, file1, file2, "File"); + const file1files = compareStringArrays(cg2.files, cg1.files, file2, file1, "File"); + compareLocationObjects(cg1.functions, cg2.functions, file1, file2, cg1, cg2, file2files, "Function", ignores2); + compareLocationObjects(cg2.functions, cg1.functions, file2, file1, cg2, cg1, file1files, "Function", ignores1); + compareLocationObjects(cg1.calls, cg2.calls, file1, file2, cg1, cg2, file2files, "Call", ignores2); + compareLocationObjects(cg2.calls, cg1.calls, file2, file1, cg2, cg1, file1files, "Call", ignores1); + const [foundFun1, totalFun1] = compareEdges(cg1.fun2fun, cg2.fun2fun, file1, file2, cg1, cg2, file2files, "Function", "functions", ignores2); + const [foundFun2, totalFun2] = compareEdges(cg2.fun2fun, cg1.fun2fun, file2, file1, cg2, cg1, file1files, "Function", "functions", ignores1); + const [foundCall1, totalCall1] = compareEdges(cg1.call2fun, cg2.call2fun, file1, file2, cg1, cg2, file2files, "Call", "calls", ignores2); + const [foundCall2, totalCall2] = compareEdges(cg2.call2fun, cg1.call2fun, file2, file1, cg2, cg1, file1files, "Call", "calls", ignores1); + logger.info(`Function->function edges in ${file2} that are also in ${file1}: ${foundFun2}/${totalFun2} (${percent(foundFun2 / totalFun2)})`); + logger.info(`Function->function edges in ${file1} that are also in ${file2}: ${foundFun1}/${totalFun1} (${percent(foundFun1 / totalFun1)})`); + logger.info(`Call->function edges in ${file2} that are also in ${file1}: ${foundCall2}/${totalCall2} (${percent(foundCall2 / totalCall2)})`); + logger.info(`Call->function edges in ${file1} that are also in ${file2}: ${foundCall1}/${totalCall1} (${percent(foundCall1 / totalCall1)})`); + const {precision, recall} = compareCallSiteEdges(cg1, cg2, ignores1, ignores2); + logger.info(`Per-call average precision: ${percent(precision)}, recall: ${percent(recall)}`); +} \ No newline at end of file diff --git a/src/output/graphviz.ts b/src/output/graphviz.ts new file mode 100644 index 0000000..b8a1fb0 --- /dev/null +++ b/src/output/graphviz.ts @@ -0,0 +1,104 @@ +import {FunctionInfo, ModuleInfo, PackageInfo} from "../analysis/infos"; +import logger from "../misc/logger"; +import {options} from "../options"; +import {writeSync} from "fs"; +import {sourceLocationToString} from "../misc/util"; +import {AnalysisState} from "../analysis/analysisstate"; + +// TODO: optionally mark reachable packages/modules/functions +// TODO: optionally highlight call edges where the target package is not in the transitive dependencies of the source package (or vice versa) +// TODO: optionally highlight paths to selected API points +// TODO: when using option packages: show excluded packages as dummy nodes? + +/** + * Write a Graphviz dot representation of the result to the given file. + */ +export function toDot(a: AnalysisState, fd: number = process.stdout.fd) { + const ids = new Map(); + let next = 1; + + function id(x: PackageInfo | ModuleInfo | FunctionInfo): number { + let t = ids.get(x); + if (!t) { + t = next++; + ids.set(x, t); + if (logger.isDebugEnabled()) + logger.debug(`Assigning ID ${t} to ${x}`); + } + return t; + } + + function esc(s: string): string { + return s.replace("\\", "\\\\").replace("\"", "\\\""); + } + + function ind(i: number): string { + return " ".repeat(i); + } + + function writeFunction(f: FunctionInfo, i: number, moduleId: number) { + if (options.graphvizElideFunctions || options.dependenciesOnly) + ids.set(f, moduleId); + else + writeSync(fd, `${ind(i)}subgraph cluster${id(f)} {\n` + + `${ind(i)} label=\"${f.name ?? ""}@${sourceLocationToString(f.loc)}\";\n` + + `${ind(i)} bgcolor=\"#ffffff\";\n` + + `${ind(i)}node${id(f)}[style=invis,shape=point];\n`); + for (const fs of f.functions.values()) + writeFunction(fs, i + 1, moduleId); + if (!options.graphvizElideFunctions && !options.dependenciesOnly) + writeSync(fd, `${ind(i)}}\n`); + } + + function writeModule(km: string, m: ModuleInfo, i: number) { + writeSync(fd, `${ind(i)}subgraph cluster${id(m)} {\n` + + `${ind(i)} label=\"${esc(km)}\";\n` + + `${ind(i)} bgcolor=\"#ffffff\";\n` + + `${ind(i)}node${id(m)}[style=invis,shape=point];\n`); + for (const f of m.functions.values()) + writeFunction(f, i + 1, id(m)); + writeSync(fd, `${ind(i)}}\n`); + } + + function isPackageIncluded(p: PackageInfo): boolean { + return !options.graphvizPackages || options.graphvizPackages.indexOf(p.name) !== -1; + } + + writeSync(fd, "digraph G {\n" + + " graph [ranksep=1];\n" + + " compound=true;\n" + + " node [shape=box,fillcolor=\"#ffffff\",style=filled]\n"); + for (const [kp, p] of a.packageInfos) + if (isPackageIncluded(p)) { + writeSync(fd, ` subgraph cluster${id(p)} {\n` + + ` label=\"${esc(kp)}\";\n` + + " bgcolor=\"#f0f0f0\";\n"); + for (const [km, m] of p.modules) + writeModule(km, m, 2); + writeSync(fd, " }\n"); + } + writeSync(fd, ` edge ${options?.dependenciesOnly ? "[color=\"#000000\",style=solid]" : "[color=\"#888888\",style=dashed]"};\n`); + for (const [from, tos] of a.requireGraph) + if (isPackageIncluded(from.packageInfo)) + for (const to of tos) + if (isPackageIncluded(to.packageInfo)) + writeSync(fd, ` node${id(from)}->node${id(to)} [lhead=cluster${id(to)}];\n`); + if (!options?.dependenciesOnly) { + writeSync(fd, ` edge [color=\"#000000\",style=solid];\n`); + const es = new Set(); + for (const [from, tos] of a.functionToFunction) + if (isPackageIncluded(from.packageInfo)) + for (const to of tos) + if (isPackageIncluded(to.packageInfo)) { + const m = from instanceof FunctionInfo ? from.moduleInfo : from; + const str = ` node${id(from)}->node${id(to)}${m !== to.moduleInfo ? ` [lhead=cluster${id(to)}]` : ""};\n`; + if (options.graphvizElideFunctions) + if (es.has(str)) + continue; // don't produce duplicate edges + else // TODO: also omit self-loops when elideFunctions enabled + es.add(str); + writeSync(fd, str); + } + } + writeSync(fd, "}\n"); +} diff --git a/src/output/merge.ts b/src/output/merge.ts new file mode 100644 index 0000000..3d5e7c4 --- /dev/null +++ b/src/output/merge.ts @@ -0,0 +1,91 @@ +import {CallGraph} from "callgraph"; +import assert from "assert"; + +/** + * Merges multiple call graphs into one by deduplicating and suitably modifying entry IDs. + */ +export function merge(callgraphs: Array): CallGraph { + const result: CallGraph = { + entries: [], + files: [], + functions: [], + calls: [], + fun2fun: [], + call2fun: [], + ignore: [] + } + + for (const cg of callgraphs) { + + if (cg.entries) + for (const entry of cg.entries) + if (result.entries?.indexOf(entry) == -1) + result.entries.push(entry); + + const fileMap = new Map(); // original ID to result ID + if (cg.files) { + for (let originalId = 0; originalId < cg.files.length; originalId++) { + const file = cg.files[originalId]; + assert.ok(file); + let resultId = result.files?.indexOf(file); + if (resultId === -1) { + resultId = result.files.length; + result.files.push(file); + } + assert.equal(result.files[resultId], file); + fileMap.set(originalId, resultId); + } + } + + const functionsMap = new Map(); + if (cg.functions) { + for (let originalIdStr of Object.keys(cg.functions)) { + let originalId = parseInt(originalIdStr); + const fn = cg.functions[originalId]; + assert.ok(fn); + let resultId = (result.functions as Array).indexOf(fn); + if (resultId === -1) { + resultId = (result.functions as Array).length; + (result.functions as Array).push(fn); + } + assert.equal(result.functions[resultId], fn); + functionsMap.set(originalId, resultId); + } + } + + const callsMap = new Map(); + if (cg.calls) { + for (let originalIdStr of Object.keys(cg.calls)) { + let originalId = parseInt(originalIdStr); + const call = cg.calls[originalId]; + let resultId = (result.calls as Array).indexOf(call); + if (resultId == -1) { + resultId = (result.calls as Array).length; + (result.calls as Array).push(call); + } + callsMap.set(originalId, resultId); + } + } + + if (cg.fun2fun) + for (let [u, v] of cg.fun2fun) + if (functionsMap.has(u) && functionsMap.has(v)) { + const [w, x] = [functionsMap.get(u)!, functionsMap.get(v)!]; + result.fun2fun.push([w, x]); + } + + if (cg.call2fun) + for (let [u, v] of cg.call2fun) + if (callsMap.has(u) && functionsMap.has(v)) { + const [w, x] = [callsMap.get(u)!, functionsMap.get(v)!]; + result.call2fun.push([w, x]); + } + + if (cg.ignore) + for (const ignoreLoc of cg.ignore) + if (result.ignore?.indexOf(ignoreLoc) == -1) + result.ignore.push(ignoreLoc); + } + + return result; +} \ No newline at end of file diff --git a/src/output/visualizer.ts b/src/output/visualizer.ts new file mode 100644 index 0000000..8db74ef --- /dev/null +++ b/src/output/visualizer.ts @@ -0,0 +1,477 @@ +import {AnalysisState} from "../analysis/analysisstate"; +import {readFileSync, writeFileSync} from "fs"; +import {FunctionInfo, ModuleInfo, PackageInfo} from "../analysis/infos"; +import {addAll, getOrSet, mapGetMap, sourceLocationToStringWithFile,} from "../misc/util"; +import {ConstraintVar, NodeVar, ObjectPropertyVar} from "../analysis/constraintvars"; +import {FragmentState} from "../analysis/fragmentstate"; +import {NativeObjectToken, Token} from "../analysis/tokens"; +import {isIdentifier} from "@babel/types"; +import {VulnerabilityResults} from "../patternmatching/vulnerabilitydetector"; +import {Vulnerability} from "vulnerabilities"; + +export interface VisualizerGraphs { + graphs: Array<{ + title?: string, + kind: "callgraph" | "dataflow", + info?: string, + elements: Array<{data: Node | Edge}>, + vulnerabilities?: Array<{ + title: string // TODO: include more information? + } & + Record<"package" | "module" | "function", { + sources: Array; + targets: Array; + }>> + }>; +} + +export interface Node { + id: number; + kind: "package" | "module" | "function" | "variable"; + parent?: number; + name?: string; + fullName?: string; + callWeight?: number; // number of incoming call edges (normalized to 0-100) + tokenWeight?: number; // number of tokens (normalized to 0-100) + callCount?: number; // number of incoming call edges + tokenCount?: number; // number of tokens + isEntry?: "true"; + isReachable?: "true"; +} + +export interface Edge { + kind: "call" | "require" | "data"; + source: number; + target: number; + weight?: number; +} + +function IdGenerator() { + const ids = new Map(); + let next = 1; + return (x: T) => { + let t = ids.get(x); + if (!t) { + t = next++; + ids.set(x, t); + } + return t; + }; +} + +/** + * Collection of nodes and edges. + */ +class Elements { + + readonly elements: Array<{data: Node | Edge}> = []; + + add(data: Node | Edge) { + this.elements.push({data}); + } +} + +/** + * Finds the functions that are reachable from the entries, and their modules and packages. + */ +function getReachable(a: AnalysisState): Set { + const reachable = new Set(); + const w = new Set(); + function reach(v: ModuleInfo | FunctionInfo) { + if (!reachable.has(v) && !w.has(v)) + w.add(v); + } + for (const e of a.entryFiles) + w.add(a.moduleInfos.get(e)!); + for (const v of w) { + reachable.add(v); + for (const n of [...a.requireGraph.get(v) || [], ...a.functionToFunction.get(v) || []]) + reach(n); + } + for (const m of a.moduleInfos.values()) { + let r = false; + for (const f of m.functions.values()) + if (reachable.has(f)) { + r = true; + break; + } + if (r) + reachable.add(m); + if (reachable.has(m)) + reachable.add(m.packageInfo); + } + return reachable; +} + +/** + * Checks whether the constraint variable is "trivial", i.e., it has no tokens or it is a native object property + * with only the native library value, or it represents 'undefined'. + */ +function isTrivialVar(v: ConstraintVar, ts: Iterable, size: number): boolean { + if (size > 1) + return false; + if (size === 0 || (v instanceof NodeVar && isIdentifier(v.node) && v.node.loc?.start.line === 0)) + return true; + return v instanceof ObjectPropertyVar && v.obj instanceof NativeObjectToken && ts[Symbol.iterator]().next().value instanceof NativeObjectToken; +} + +/** + * Produces the call graph. + */ +function getVisualizerCallGraph(a: AnalysisState, vulnerabilities: VulnerabilityResults): VisualizerGraphs { + // count number of calls edges for all functions, modules and packages + const functionCallCounts = new Map(); + const moduleCallCounts = new Map(); + const packageCallCounts = new Map(); + let maxFunctionCallCount = 1, maxModuleCallCount = 1, maxPackageCallCount = 1; + for (const dsts of a.functionToFunction.values()) + for (const dst of dsts) { + const fw = getOrSet(functionCallCounts, dst, () => 0) + 1; + functionCallCounts.set(dst, fw); + if (fw > maxFunctionCallCount) + maxFunctionCallCount = fw; + const fm = getOrSet(moduleCallCounts, dst.moduleInfo, () => 0) + 1; + moduleCallCounts.set(dst.moduleInfo, fm); + if (fm > maxModuleCallCount) + maxModuleCallCount = fm; + const fp = getOrSet(packageCallCounts, dst.packageInfo, () => 0) + 1; + packageCallCounts.set(dst.packageInfo, fp); + if (fp > maxPackageCallCount) + maxPackageCallCount = fp; + } + for (const dsts of a.requireGraph.values()) + for (const dst of dsts) { + const fm = getOrSet(moduleCallCounts, dst, () => 0) + 1; + moduleCallCounts.set(dst, fm); + if (fm > maxModuleCallCount) + maxModuleCallCount = fm; + const fp = getOrSet(packageCallCounts, dst.packageInfo, () => 0) + 1; + packageCallCounts.set(dst.packageInfo, fp); + if (fp > maxPackageCallCount) + maxPackageCallCount = fp; + } + // prepare the new graph + const reachable = getReachable(a); + const id = IdGenerator(); + const e = new Elements(); + // add nodes for packages + for (const p of a.packageInfos.values()) { + const packageCallCount = packageCallCounts.get(p) ?? 0; + e.add({ + id: id(p), + kind: "package", + name: p.name, + fullName: p.toString(), + callWeight: Math.round(100 * packageCallCount / maxPackageCallCount), + callCount: packageCallCount, + isEntry: p.isEntry ? "true" : undefined, + isReachable: reachable.has(p) ? "true" : undefined + }); + } + // add nodes for modules + for (const m of a.moduleInfos.values()) { + const moduleCallCount = moduleCallCounts.get(m) ?? 0; + e.add({ + id: id(m), + kind: "module", + parent: id(m.packageInfo), + name: m.relativePath, + fullName: m.toString(), + callWeight: Math.round(100 * moduleCallCount / maxModuleCallCount), + callCount: moduleCallCount, + isEntry: m.isEntry ? "true" : undefined, + isReachable: reachable.has(m) ? "true" : undefined + }); + } + // add nodes for functions + for (const f of a.functionInfos.values()) { + const functionCallCount = functionCallCounts.get(f) ?? 0; + e.add({ + id: id(f), + kind: "function", + parent: id(f.moduleInfo), + name: f.name ?? "", + fullName: sourceLocationToStringWithFile(f.loc), + callWeight: Math.round(100 * functionCallCount / maxFunctionCallCount), + callCount: functionCallCount, + isReachable: reachable.has(f) ? "true" : undefined + }); + } + // add edges + let numEdges = 0; + for (const [src, dsts] of a.functionToFunction) + for (const dst of dsts) { + e.add({ + kind: "call", + source: id(src), + target: id(dst) + }); + numEdges++; + } + for (const [src, dsts] of a.requireGraph) + for (const dst of dsts) { + e.add({ + kind: "require", + source: id(src), + target: id(dst) + }); + numEdges++; + } + // add vulnerabilities + let vuls; + if (vulnerabilities.package && vulnerabilities.package.size > 0) { + const relevant = new Set(); + const sources = new Map>>(); + const targets = new Map>>(); + function add(v: Vulnerability, + p: PackageInfo | ModuleInfo | FunctionInfo, + kind: "package" | "module" | "function", + m: Map>>) { + let x = m.get(v); + if (!x) { + x = {package: new Set(), module: new Set(), function: new Set()}; + m.set(v, x); + } + x[kind].add(p); + } + for (const ts of vulnerabilities.package.values()) + for (const vs of ts.values()) + addAll(vs, relevant); + for (const [src, m] of vulnerabilities.package) + for (const [dst, vs] of m) + for (const v of vs) { + add(v, src, "package", sources); + add(v, dst, "package", targets); + } + if (vulnerabilities.module) + for (const [src, m] of vulnerabilities.module) + for (const [dst, vs] of m) + for (const v of vs) { + add(v, src, "module", sources); + add(v, dst, "module", targets); + } + if (vulnerabilities.function) + for (const [src, m] of vulnerabilities.function) + for (const [dst, vs] of m) + for (const v of vs) { + add(v, src, "function", sources); + add(v, dst, "function", targets); + } + vuls = []; + for (const v of relevant) { + const x: NonNullable[number] = { + title: v.osv.id, + package: {sources: [], targets: []}, + module: {sources: [], targets: []}, + function: {sources: [], targets: []} + }; + for (const kind of ["package", "module", "function"] as const) { + const ss = sources.get(v); + if (ss) + for (const s of ss[kind]) + x[kind].sources.push(id(s)); + const ts = targets.get(v); + if (ts) + for (const t of ts[kind]) + x[kind].targets.push(id(t)); + } + vuls.push(x); + } + } + // return the graph + return { + graphs: [{ + kind: "callgraph", + elements: e.elements, + info: `Packages: ${a.packageInfos.size}\nModules: ${a.moduleInfos.size}\nFunctions: ${a.functionInfos.size}\nCall edges: ${numEdges}\nMax number of calls for packages: ${maxPackageCallCount}, modules: ${maxModuleCallCount}, functions: ${maxFunctionCallCount}`, + vulnerabilities: vuls + }] + }; +} + +/** + * Produces the dataflow graphs. + */ +function getVisualizerDataFlowGraphs(a: AnalysisState, f: FragmentState): VisualizerGraphs { + // count tokens for constraint variables, modules and packages and find the nontrivial constraint variables and their parents + let maxVariableCount = 1, maxModuleTokenCount = 1, maxPackageTokenCount = 1; + const moduleTokenCounts = new Map(); + const packageTokenCounts = new Map(); + const nontrivialVars = new Set(), anyEdges = new Set(); + const parents = new Map(); + for (const [v, ts, size] of f.getAllVarsAndTokens()) { + if (!isTrivialVar(v, ts, size)) { + nontrivialVars.add(v); + const p = a.getConstraintVarParent(v); + if (p) + parents.set(v, p); + } + if (size - 1 > maxVariableCount) + maxVariableCount = size - 1; + } + for (const [src, dsts] of f.subsetEdges) + for (const dst of dsts) { + if (nontrivialVars.has(dst)) + anyEdges.add(src); + if (nontrivialVars.has(src)) + anyEdges.add(dst); + } + // prepare the graphs + const id = IdGenerator(); + const res: VisualizerGraphs = {graphs: []}; + for (const p of a.packageInfos.values()) { + // make a graph for each package + let maxLocalVariableCount = 1, maxLocalModuleTokenCount = 1 + const e = new Elements(); + const vars = new Set(); + for (const [v, , size] of f.getAllVarsAndTokens()) + if (nontrivialVars.has(v) && anyEdges.has(v)) { + const parent = parents.get(v); + if (parent === p || parent instanceof ModuleInfo && parent.packageInfo === p) { + e.add({ + id: id(v), + kind: "variable", + parent: id(parent), + fullName: v.toString(), // TODO: more information? + tokenWeight: Math.floor(100 * (size - 1) / maxLocalVariableCount), + tokenCount: size + }); + vars.add(v); + if (size - 1 > maxVariableCount) + maxVariableCount = size - 1; + if (size - 1 > maxLocalVariableCount) + maxLocalVariableCount = size - 1; + let pa; + if (parent instanceof ModuleInfo) { + pa = parent.packageInfo; + const fm = getOrSet(moduleTokenCounts, parent, () => 0) + 1; + moduleTokenCounts.set(parent, fm); + if (fm > maxModuleTokenCount) + maxModuleTokenCount = fm; + if (fm > maxLocalModuleTokenCount) + maxLocalModuleTokenCount = fm; + } else + pa = parent; + const pm = getOrSet(packageTokenCounts, pa, () => 0) + 1; + packageTokenCounts.set(pa, pm); + if (pm > maxPackageTokenCount) + maxPackageTokenCount = pm; + } + } + let numFunctions = 0; + for (const m of p.modules.values()) { + const moduleTokenCount = moduleTokenCounts.get(m) ?? 0; + e.add({ + id: id(m), + kind: "module", + parent: id(m.packageInfo), + name: m.relativePath, + fullName: m.toString(), + tokenWeight: Math.floor(100 * (moduleTokenCount - 1) / maxLocalModuleTokenCount), + tokenCount: moduleTokenCount + }); + numFunctions += m.functions.size; + } + for (const [src, dsts] of f.subsetEdges) + for (const dst of dsts) + if (nontrivialVars.has(src) && nontrivialVars.has(dst) && anyEdges.has(src) && anyEdges.has(dst) && vars.has(src) && vars.has(dst)) + e.add({ + kind: "data", + source: id(src), + target: id(dst) + }); + res.graphs.push({ + title: p.toString(), + kind: "dataflow", + elements: e.elements, + info: `Modules: ${p.modules.size}\nFunctions: ${numFunctions}\nVariables: ${vars.size}\nMax number of values for modules: ${maxLocalModuleTokenCount}, variables: ${maxLocalVariableCount}` + }); + } + res.graphs.sort((p1: {title?: string}, p2: {title?: string}) => p1.title!.localeCompare(p2.title!)); + // prepare an overview graph + const e = new Elements(); + // add the packages + for (const p of a.packageInfos.values()) { + const packageTokenCount = packageTokenCounts.get(p) ?? 0; + e.add({ + id: id(p), + kind: "package", + name: p.name, + fullName: p.toString(), + tokenWeight: Math.floor(100 * (packageTokenCount - 1) / maxPackageTokenCount), + tokenCount: packageTokenCount + }); + } + // add the modules + for (const m of a.moduleInfos.values()) { + const moduleTokenCount = moduleTokenCounts.get(m) ?? 0; + e.add({ + id: id(m), + kind: "module", + parent: id(m.packageInfo), + name: m.relativePath, + fullName: m.toString(), + tokenWeight: Math.floor(100 * (moduleTokenCount - 1) / maxModuleTokenCount), + tokenCount: moduleTokenCount + }); + } + // add the edges + const numEdges = new Map>(); + let maxNumEdges = 1; + for (const [src, dsts] of f.subsetEdges) { + const srcParent = parents.get(src); + if (srcParent) + for (const dst of dsts) { + if (nontrivialVars.has(src) && nontrivialVars.has(dst) && anyEdges.has(src) && anyEdges.has(dst)) { + const dstParent = parents.get(dst); + if (dstParent) { + const source = id(srcParent), target = id(dstParent); + if (source !== target) { + const m = mapGetMap(numEdges, source); + const w = getOrSet(m, target, () => 0) + 1; + m.set(target, w); + if (w > maxNumEdges) + maxNumEdges = w; + } + } + } + } + } + for (const [src, m] of numEdges) + for (const [dst, n] of m) + e.add({ + kind: "data", + source: src, + target: dst, + weight: Math.floor(100 * n / maxNumEdges) + }); + res.graphs.unshift({ + title: "Packages and modules", + kind: "dataflow", + elements: e.elements, + info: `Packages: ${a.packageInfos.size}\nModules: ${a.moduleInfos.size}\nFunctions: ${a.functionInfos.size}\nMax number of values for packages: ${maxPackageTokenCount}, modules: ${maxModuleTokenCount}, variables: ${maxVariableCount}` + }); + return res; +} + +function writeVisualizerHtml(filename: string, g: VisualizerGraphs) { + const DATA = "$DATA"; + const t = readFileSync(__dirname + "/../resources/visualizer.html", "utf-8"); + const i = t.indexOf(DATA); // string.replace doesn't like very long strings + const res = t.substring(0, i) + JSON.stringify(g) + t.substring(i + DATA.length); + writeFileSync(filename, res); +} + +/** + * Exports the call graph as an HTML file. + */ +export function exportCallGraphHtml(a: AnalysisState, filename: string, vulnerabilities: VulnerabilityResults) { + writeVisualizerHtml(filename, getVisualizerCallGraph(a, vulnerabilities)); +} + +/** + * Exports the data-flow graphs as an HTML file. + */ +export function exportDataFlowGraphHtml(a: AnalysisState, f: FragmentState, filename: string) { + writeVisualizerHtml(filename, getVisualizerDataFlowGraphs(a, f)); +} diff --git a/src/parsing/extras.ts b/src/parsing/extras.ts new file mode 100644 index 0000000..63f706c --- /dev/null +++ b/src/parsing/extras.ts @@ -0,0 +1,30 @@ +import {NodePath, PluginObj} from '@babel/core'; +import {TemplateBuilder} from '@babel/template'; +import {isTSExternalModuleReference, TSExportAssignment, TSImportEqualsDeclaration} from '@babel/types'; + +/** + * Replaces TypeScript "export =" and "import =" syntax. + * See https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats + * and https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require + */ +export function replaceTypeScriptImportExportAssignments({ template }: {template: TemplateBuilder}): PluginObj { + const moduleExportsDeclaration = template("module.exports = ASSIGNMENT;"); + const moduleImportsDeclaration = template("var ID = require(MODULE);"); + return { + visitor: { + TSExportAssignment(path: NodePath) { + path.replaceWith(moduleExportsDeclaration({ + ASSIGNMENT: path.node.expression + })); + }, + TSImportEqualsDeclaration(path: NodePath) { + if (!path.node.isExport && path.node.importKind === "value" && isTSExternalModuleReference(path.node.moduleReference)) + path.replaceWith(moduleImportsDeclaration({ + ID: path.node.id, + MODULE: path.node.moduleReference.expression + })); + // TODO: handle other forms of TSImportEqualsDeclaration? + } + } + }; +} diff --git a/src/parsing/parser.ts b/src/parsing/parser.ts new file mode 100644 index 0000000..62f2819 --- /dev/null +++ b/src/parsing/parser.ts @@ -0,0 +1,77 @@ +import {File} from "@babel/types"; +import logger from "../misc/logger"; +import {transformFromAstSync} from "@babel/core"; +import {parse, ParserOptions} from "@babel/parser"; +import {replaceTypeScriptImportExportAssignments} from "./extras"; +import {AnalysisState} from "../analysis/analysisstate"; + +/** + * Parses and desugars the given file + * @param str the contents of the file + * @param file the name of the file + * @param a analysis state object + * @return AST, or null if error occurred + */ +export function parseAndDesugar(str: string, file: string, a: AnalysisState): File | null { + + // parse the file + let originalAst: File; + try { + const options: ParserOptions = { + sourceFilename: file, + allowImportExportEverywhere: true, + allowAwaitOutsideFunction: true, + allowReturnOutsideFunction: true, + allowSuperOutsideMethod: true, + allowUndeclaredExports: true, + errorRecovery: true, + attachComment: false, + createParenthesizedExpressions: true, + sourceType: "unambiguous", + plugins: [ + "typescript", + "exportDefaultFrom", // https://github.com/leebyron/ecmascript-export-default-from + ["decorators", { decoratorsBeforeExport: false }] // TODO: decorators? + ] + } + try { + originalAst = parse(str, options); + } catch (e) { // 'jsx' conflicts with TypeScript legacy cast syntax, see https://babeljs.io/docs/en/babel-plugin-transform-typescript/ + if (logger.isVerboseEnabled()) + logger.verbose(`Parse error for ${file}${e instanceof Error ? `: ${e.message}` : ""}, retrying with JSX enabled`); + options.plugins!.push("jsx"); + originalAst = parse(str, options); + } + } catch (e) { + a.error(`Unrecoverable parse error for ${file}${e instanceof Error ? `: ${e.message}` : ""}`); + return null; + } + + // apply Babel transformations + let res; + try { + res = transformFromAstSync(originalAst, str, { + plugins: [ + replaceTypeScriptImportExportAssignments, + ["@babel/plugin-transform-typescript", { onlyRemoveTypeImports: true }], + ["@babel/plugin-transform-template-literals", { loose: true }] + ], // TODO: perform other transformations? + cwd: __dirname, + configFile: false, + ast: true, + code: logger.isDebugEnabled() + }); + } catch (e) { + a.error(`Babel transformation failed for ${file}${e instanceof Error ? `: ${e.message}` : ""}`); + return null; + } + if (!res) { + a.error(`Babel transformation failed silently for ${file}`); + return null; + } + if (res.code) // set 'code: true' above to output desugared code + if (logger.isDebugEnabled()) + logger.debug("Desugared code:\n" + res.code); + + return res.ast!; +} diff --git a/src/patternmatching/apiexported.ts b/src/patternmatching/apiexported.ts new file mode 100644 index 0000000..d114a01 --- /dev/null +++ b/src/patternmatching/apiexported.ts @@ -0,0 +1,209 @@ +import {AnalysisState} from "../analysis/analysisstate"; +import {AccessPathPatternCanonicalizer} from "./patternparser"; +import { + AllocationSiteToken, + FunctionToken, + NativeObjectToken, + ObjectToken, + PackageObjectToken +} from "../analysis/tokens"; +import {FragmentState} from "../analysis/fragmentstate"; +import { + AccessPathPattern, + CallResultAccessPathPattern, + ImportAccessPathPattern, + PropertyAccessPathPattern +} from "./patterns"; +import logger from "../misc/logger"; +import {mapGetSet, sourceLocationContains, sourceLocationToStringWithFileAndEnd} from "../misc/util"; +import {ConstraintVar, FunctionReturnVar, ObjectPropertyVar, ObjectPropertyVarObj} from "../analysis/constraintvars"; +import {resolve} from "path"; +import {FunctionInfo, ModuleInfo} from "../analysis/infos"; +import assert from "assert"; + +const MAX_ACCESS_PATHS = 10; + +/** + * Find the exported API of all modules. + */ +export function getAPIExported(a: AnalysisState, f: FragmentState): Map> { + logger.info("Collecting exported API"); + const c = new AccessPathPatternCanonicalizer; + const res = new Map>(); + const worklist = new Map>(); + + function add(v: ConstraintVar, ap: ImportAccessPathPattern | PropertyAccessPathPattern | CallResultAccessPathPattern) { + for (const t of f.getTokens(v)) + // TODO: ignore certain tokens? ((t instanceof NativeObjectToken && t.name === "exports") || t instanceof AllocationSiteToken || t instanceof FunctionToken) { + if (t instanceof NativeObjectToken || t instanceof AllocationSiteToken || t instanceof FunctionToken || t instanceof PackageObjectToken) { + const aps = mapGetSet(res, t); + let prefix: AccessPathPattern = ap; + do { // check if a prefix has already been recorded + if (aps.has(prefix)) + return; + if (prefix instanceof PropertyAccessPathPattern) + prefix = prefix.base; + else if (prefix instanceof CallResultAccessPathPattern) + prefix = prefix.fun; + } while (!(prefix instanceof ImportAccessPathPattern)); + if (aps.size + 1 >= MAX_ACCESS_PATHS) { + logger.debug(`Reached ${MAX_ACCESS_PATHS} access paths for ${t}, skipping remaining ones`); + if (aps.size >= MAX_ACCESS_PATHS) + continue; + } + if (logger.isDebugEnabled()) + logger.debug(`Added access path for ${t}: ${ap}`); + if (logger.isVerboseEnabled()) + if (t instanceof FunctionToken) + logger.info(`Access path for ${t.fun.type} at ${sourceLocationToStringWithFileAndEnd(t.fun.loc)}: ${ap}`); + aps.add(ap); + mapGetSet(worklist, t).add(ap); + } + } + + // find object properties + const objprops = new Map>(); + for (const v of f.vars) + if (v instanceof ObjectPropertyVar) + mapGetSet(objprops, v.obj).add(v.prop); + + // find exports + for (const m of a.moduleInfos.values()) + add(a.canonicalizeVar(new ObjectPropertyVar(a.canonicalizeToken(new NativeObjectToken("module", m)), "exports")), + c.canonicalize(new ImportAccessPathPattern(m.getOfficialName()))); // TODO: technically, official-name is not a glob? + + // iteratively find reachable objects and functions + for (const [t, aps] of worklist) + for (const ap of aps) { + aps.delete(ap); + if (aps.size === 0) + worklist.delete(t); + + // look at object properties + if (t instanceof ObjectToken || t instanceof NativeObjectToken || t instanceof PackageObjectToken) + for (const prop of mapGetSet(objprops, t)) + add(a.canonicalizeVar(new ObjectPropertyVar(t, prop)), + c.canonicalize(new PropertyAccessPathPattern(ap, [prop]))) + + // look at function returns + if (t instanceof FunctionToken) + add(a.canonicalizeVar(new FunctionReturnVar(t.fun)), + c.canonicalize(new CallResultAccessPathPattern(ap))); + + // TODO: we don't have patterns for class instantiation, methods and fields are described as properties of the classes + } + + return res; +} + +/** + * Reports access paths for all exported functions. + */ +export function reportAPIExportedFunctions(r: Map>) { + for (const [t, aps] of r) + for (const ap of aps) + if (t instanceof FunctionToken) + logger.info(`${sourceLocationToStringWithFileAndEnd(t.fun.loc)}: ${ap}`); +} + +/** + * Finds the function or module (as a top-level) at the given location. + */ +function findFunctionAtLocation(a: AnalysisState, loc: string): FunctionInfo | ModuleInfo | undefined { + const i = loc.lastIndexOf(":"); + if (i != -1) { + const file = resolve(loc.substring(0, i)); + const line = parseInt(loc.substring(i + 1), 10); + const modinfo = a.moduleInfos.get(file); + if (line > 0 && modinfo && modinfo.loc && line <= modinfo.loc.end.line) { + let best: FunctionInfo | ModuleInfo = modinfo; + for (const [fun, funinfo] of a.functionInfos) + if (fun.loc && sourceLocationContains(fun.loc, file, line)) + if (best.loc!.start.line < fun.loc.start.line || fun.loc.end.line < best.loc!.end.line) + best = funinfo; // assuming only a single best match on that line + return best; + } + } + return undefined; +} + +function getReverseCallGraph(a: AnalysisState): Map> { + const r = new Map>(); + for (const [from, tos] of a.functionToFunction) + for (const to of tos) + mapGetSet(r, to).add(from); + return r; +} + +function getReverseRequireGraph(a: AnalysisState): Map> { + const r = new Map>(); + for (const [from, tos] of a.requireGraph) + for (const to of tos) + mapGetSet(r, to).add(from); + return r; +} + +/** + * Finds the functions and modules (as top-level functions) that may reach the given function. + * The functions and modules are the keys of the resulting map; the values are the successors toward the given function. + */ +function findReachingFunctions(a: AnalysisState, fun: FunctionInfo | ModuleInfo): Map> { + const callers = getReverseCallGraph(a); + const requires = getReverseRequireGraph(a); + const r = new Map>(); + const w = new Set(); + r.set(fun, new Set()); + w.add(fun); + for (const f of w) { + w.delete(f); + const ps = f instanceof FunctionInfo ? callers.get(f) : requires.get(f); + if (ps) + for (const p of ps) { + if (!r.has(p)) + w.add(p); + mapGetSet(r, p).add(f); + } + } + return r; +} + +/** + * Reports access paths for functions and modules (as top-level functions) that may reach the given location. + */ +export function reportAccessPaths(a: AnalysisState, r: Map>, loc: string) { + const fun = findFunctionAtLocation(a, loc); + if (!fun) { + logger.error(`Location ${loc} not found`); + return; + } + if (logger.isDebugEnabled()) + logger.debug(`${loc} belongs to ${fun}`); + const reach = findReachingFunctions(a, fun); + logger.info(`Functions that may reach ${loc} (nearest first):`); + for (const [f, ns] of reach) { + logger.info(` ${f}`); + for (const n of ns) + logger.info(` ↳ ${n}`); + } + logger.info(`Access paths that may reach ${loc}:`); + for (const m of reach.keys()) + if (m instanceof ModuleInfo) + logger.info(` ${new ImportAccessPathPattern(m.getOfficialName())}`); + let more = false; + const all = new Set(); + for (const [t, aps] of r) + if (t instanceof FunctionToken) { + const f = a.functionInfos.get(t.fun); + assert(f); + if (reach.has(f)) { + for (const ap of aps) + all.add(ap.toString()); + if (aps.size >= MAX_ACCESS_PATHS) + more = true; + } + } + for (const ap of Array.from(all).sort()) + logger.info(` ${ap}`); + if (more) + logger.info(" ..."); +} \ No newline at end of file diff --git a/src/patternmatching/apiusage.ts b/src/patternmatching/apiusage.ts new file mode 100644 index 0000000..af72d0b --- /dev/null +++ b/src/patternmatching/apiusage.ts @@ -0,0 +1,180 @@ +import {AnalysisState} from "../analysis/analysisstate"; +import logger from "../misc/logger"; +import {mapGetSet, sourceLocationToStringWithFileAndEnd, SourceLocationWithFilename} from "../misc/util"; +import { + AbbreviatedPathPattern, + AccessPathPattern, + CallResultAccessPathPattern, + ComponentAccessPathPattern, + ImportAccessPathPattern, + PropertyAccessPathPattern +} from "./patterns"; +import {isCallExpression, isNewExpression, isOptionalCallExpression, Node} from "@babel/types"; +import {AccessPathPatternCanonicalizer} from "./patternparser"; +import {AccessPath} from "../analysis/accesspaths"; + +export type PatternType = "import" | "read" | "write" | "call" | "component"; + +export type AccessPathPatternToNodes = Record>>; +export type NodeToAccessPathPatterns = Record>>; +export type AccessPathString = string; +export type AccessPathPatternStringToNodes = Record>>; + +/** + * Finds the usage of the API of external modules. + */ +export function getAPIUsage(a: AnalysisState): [AccessPathPatternToNodes, NodeToAccessPathPatterns] { // TODO: parameter to choose which map to produce? + logger.info("Collecting API usage"); + const reached: AccessPathPatternToNodes = {import: new Map, read: new Map, write: new Map, call: new Map, component: new Map}; + const res1: AccessPathPatternToNodes = {import: new Map, read: new Map, write: new Map, call: new Map, component: new Map}; + const res2: NodeToAccessPathPatterns = {import: new Map, read: new Map, write: new Map, call: new Map, component: new Map}; + const c = new AccessPathPatternCanonicalizer; + const worklist = new Map>(); + + function add(t: PatternType, p: AccessPathPattern, ap: AccessPath, n: Node) { + + function sub(p: AccessPathPattern): AccessPathPattern | undefined { + return p instanceof PropertyAccessPathPattern ? p.base : + p instanceof CallResultAccessPathPattern ? p.fun : + p instanceof ComponentAccessPathPattern ? p.component : + undefined; + } + + function copyWithSub(as: AccessPathPattern, sub: AccessPathPattern): AccessPathPattern { + if (as instanceof PropertyAccessPathPattern) + return new PropertyAccessPathPattern(sub, as.props); + else if (as instanceof CallResultAccessPathPattern) + return new CallResultAccessPathPattern(sub); + else if (as instanceof ComponentAccessPathPattern) + return new ComponentAccessPathPattern(sub); + else + return as; + } + + // abbreviate long patterns + const p1 = sub(p); + if (p1) { + const p2 = sub(p1); + if (p2) { + if (p2 instanceof AbbreviatedPathPattern && p1 instanceof CallResultAccessPathPattern && !(p instanceof CallResultAccessPathPattern)) // m…()d --> m…d + p = copyWithSub(p, p2); + else { + const p3 = sub(p2); + if (p3) { + if (p3 instanceof AbbreviatedPathPattern) { + if (p1 instanceof CallResultAccessPathPattern) // m…b()d --> m…d + p = copyWithSub(p, p3); + else // m…bcd --> m…cd + p = copyWithSub(p, copyWithSub(p1, p3)); + } else { + const p4 = sub(p3); + if (p4) + if (p1 instanceof CallResultAccessPathPattern) // mab()d --> ma…d + p = copyWithSub(p, new AbbreviatedPathPattern(p3)); + else // mabcd --> ma…cd + p = copyWithSub(p, copyWithSub(p1, new AbbreviatedPathPattern(p3))); + + } + } + } + } + } + + p = c.canonicalize(p); + const aps = mapGetSet(reached[t], p); + if (!aps.has(n)) { + if (logger.isDebugEnabled()) + logger.debug(`Found ${t} ${p} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + aps.add(n); + function isReadAtCall(): boolean { + if (t === "read") { + const m = a.callResultAccessPaths.get(ap); + if (m) + for (const f of m.keys()) + if ((isCallExpression(f) || isOptionalCallExpression(f) || isNewExpression(f)) && f.callee === n) + return true; + } + return false; + } + if (isReadAtCall()) { // if read occurs at call function expression, exclude in output + if (logger.isDebugEnabled()) + logger.debug(`Read-call ${ap} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + } else { + mapGetSet(res1[t], p).add(n); + mapGetSet(res2[t], n).add(p); + } + mapGetSet(worklist, p).add(ap); + } + } + // find imports + for (const [ap, ns] of a.moduleAccessPaths) + for (const n of ns) + add("import", c.canonicalize(new ImportAccessPathPattern(ap.moduleInfo.getOfficialName())), ap, n); // TODO: technically, official-name is not a glob? + // iteratively find property reads, writes, calls and components + for (const [p, aps] of worklist) + for (const ap of aps) { + aps.delete(ap); + if (aps.size === 0) + worklist.delete(p); + // property reads + const m1 = a.propertyReadAccessPaths.get(ap); + if (m1) + for (const [prop, np] of m1) + for (const [n2, {bp}] of np) + add("read", c.canonicalize(new PropertyAccessPathPattern(p, [prop])), bp, n2); + // property writes + const m2 = a.propertyWriteAccessPaths.get(ap); + if (m2) + for (const [prop, np] of m2) + for (const [n2, {bp}] of np) + add("write", c.canonicalize(new PropertyAccessPathPattern(p, [prop])), bp, n2); + // calls + const m3 = a.callResultAccessPaths.get(ap); + if (m3) + for (const [n2, {bp}] of m3) + add("call", c.canonicalize(new CallResultAccessPathPattern(p)), bp, n2); + // components + const m4 = a.componentAccessPaths.get(ap); + if (m4) + for (const [n2, {bp}] of m4) + add("component", c.canonicalize(new ComponentAccessPathPattern(p)), bp, n2); + } + return [res1, res2]; +} + +export function reportAPIUsage(r1: AccessPathPatternToNodes, r2: NodeToAccessPathPatterns) { // TODO: split into two functions? + logger.info("API usage, access path patterns -> nodes:"); + let numAccessPathPatterns = 0, numAccessPathPatternsAtNodes = 0; + for (const [t, m] of Object.entries(r1)) { + for (const [p, ns] of m) { + logger.info(`${t} ${p}:`); + for (const n of ns) + logger.info(` ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + numAccessPathPatternsAtNodes += ns.size; + } + numAccessPathPatterns += m.size; + } + // logger.info("API usage, nodes -> access path patterns:"); // TODO: remove this part, also in getAPIUsage? + // for (const [t, m] of Object.entries(r2)) + // for (const [n, ps] of m) { + // logger.info(`${sourceLocationToStringWithFileAndEnd(n.loc)}:`); + // for (const p of ps) + // logger.info(` ${t} ${p}`); + // } + logger.info(`Access path patterns: ${numAccessPathPatterns}, access path patterns at nodes: ${numAccessPathPatternsAtNodes}`); +} + +export function convertAPIUsageToJSON(r: AccessPathPatternToNodes): AccessPathPatternStringToNodes { + const res: AccessPathPatternStringToNodes = {import: {}, read: {}, write: {}, call: {}, component: {}}; + for (const type of Object.getOwnPropertyNames(r) as Array) { + const t: Record> = {}; + for (const [p, nodes] of r[type]) { + const a: Array = []; + for (const n of nodes) + a.push(n.loc as SourceLocationWithFilename); + t[p.toString()] = a; + } + res[type] = t; + } + return res; +} diff --git a/src/patternmatching/astpatterns.ts b/src/patternmatching/astpatterns.ts new file mode 100644 index 0000000..95af560 --- /dev/null +++ b/src/patternmatching/astpatterns.ts @@ -0,0 +1,116 @@ +import {SimpleType, ValueType} from "./patterns"; +import {Ternary} from "../misc/util"; +import { + isArrayExpression, + isArrowFunctionExpression, + isBooleanLiteral, + isFunctionExpression, + isIdentifier, + isImportDeclaration, + isImportDefaultSpecifier, + isNullLiteral, + isNumericLiteral, + isObjectExpression, + isObjectMethod, + isObjectProperty, + isStringLiteral, + isTemplateLiteral, + isUnaryExpression, + Node +} from "@babel/types"; +import {getKey} from "../misc/asthelpers"; + +/** + * Returns true of the given node is an import declaration that contains a default import specifier. + */ +export function isDefaultImport(n: Node): boolean { + if (!isImportDeclaration(n)) + return false; + for (const spec of n.specifiers) + if (isImportDefaultSpecifier(spec)) + return true; + return false; +} + +/** + * Gets the simple type of the given node, returns undefined if not a simple type or uncertain. + */ +export function getSimpleType(n: Node): SimpleType | undefined { // TODO: like TAPIR, if the node is an identifier and it has exactly one definition, use that definition (same for getValueType below) - or even better, use proper flow analysis! + if (isIdentifier(n) && n.name === "undefined") // TODO: use the artificial declaration of 'undefined' instead? + return "undefined"; + if (isBooleanLiteral(n) || (isUnaryExpression(n) && n.operator === "!")) + return "boolean"; + if (isStringLiteral(n) || isTemplateLiteral(n)) + return "string"; + if (isNumericLiteral(n)) + return "number"; + if (isArrayExpression(n)) { + if (n.elements.length === 0) + return "empty-array"; + return "array"; + } + if (isObjectExpression(n)) + return "object"; + if (isNullLiteral(n)) + return "null"; + if (isFunctionExpression(n) || isArrowFunctionExpression(n) || isObjectMethod(n)) + return "function"; + return undefined; +} + +/** + * Gets the value type of the given node, returns undefined if not a value type or uncertain. + */ +export function getValueType(n: Node): ValueType | undefined { + if (isStringLiteral(n) || isNumericLiteral(n) || isBooleanLiteral((n))) + return n.value; + return undefined; +} + +/** + * Gets the number of function parameters, returns undefined if not a function or uncertain. + */ +export function getNumberOfFunctionParams(n: Node): number | undefined { + if (!isFunctionExpression(n) && !isArrowFunctionExpression(n) && !isObjectMethod(n)) + return undefined; + let plain = true; + for (const p of n.params) + if (!isIdentifier(p)) { + plain = false; + break; + } + if (!plain) + return undefined; + return n.params.length; +} + +/** + * Attempts to follow the given property access path for object expressions. + * Returns Maybe if uncertain and False if the access path definitely doesn't exist. + */ +export function followProps(n: Node, props: Array | undefined): Node | Ternary { + if (!props) + return n; + let m = n; + for (const prop of props) { + if (isObjectExpression(m)) { + let found = false; + for (const p of m.properties) + if (isObjectProperty(p) || isObjectMethod(p)) { + const key = getKey(p); + if (key !== undefined) { + if (key === prop) { + found = true; + m = isObjectProperty(p) ? p.value : p; + } + } else + return Ternary.Maybe; + } else + return Ternary.Maybe; + if (!found) + return Ternary.False; + } else + return Ternary.Maybe; + } + return m; +} diff --git a/src/patternmatching/patternloader.ts b/src/patternmatching/patternloader.ts new file mode 100644 index 0000000..997a4fe --- /dev/null +++ b/src/patternmatching/patternloader.ts @@ -0,0 +1,76 @@ +import {PatternWrapper, SemanticPatch, SemanticPatchNew} from "tapir"; +import {readFileSync} from "fs"; +import {AccessPathPatternCanonicalizer, parseDetectionPattern} from "./patternparser"; +import {DetectionPattern, ImportAccessPathPattern, PropertyAccessPathPattern} from "./patterns"; +import logger from "../misc/logger"; +import {addAll} from "../misc/util"; + +export function loadTapirDetectionPatternFiles(files: Array): Array { + const res: Array = []; + for (const file of files) { + logger.info(`Loading patterns from ${file}`); + for (const p of JSON.parse(readFileSync(file, "utf8")) as Array) + res.push(p); + } + return res; +} + +export function convertTapirPatterns(tapir: Array, c: AccessPathPatternCanonicalizer = new AccessPathPatternCanonicalizer()): Array { + const res: Array = []; + for (const p of tapir) { + const pattern = "detectionPattern" in p ? p.detectionPattern : "semanticPatchId" in p ? p.semanticPatch.detectionPattern : p.pattern; + try { + res.push(parseDetectionPattern(pattern, c)); + } catch (pos) { + logger.error(`Error: Pattern parse error:\n${pattern}${"semanticPatchId" in p ? ` (pattern #${p.semanticPatchId} version ${p.version})` : ""}`); + logger.error(`${" ".repeat(pos as number)}^ (column ${pos})`); + } + } + return res; +} + +export function removeObsoletePatterns(patterns: Array): Array { + const m = new Map(); + for (const p of patterns) + if ("semanticPatchId" in p) { + const q = m.get(p.semanticPatchId); + if ((!q || ("version" in q && q.version < p.version))) + if (p.enabled) + m.set(p.semanticPatchId, p); + else + m.delete(p.semanticPatchId); + } else if (!p.deprecation) { + if (m.has(p.id)) + logger.warn(`Multiple patterns with ID ${p.id}`); + m.set(p.id, p); + } + return Array.from(m.values()); +} + +/** + * Returns the globs that appear in module patterns. + */ +export function getGlobs(ds: Array): Set { + const s = new Set(); + for (const d of ds) + if (d) + d.ap.visitAccessPathPatterns(p => { + if (p instanceof ImportAccessPathPattern) + s.add(p.glob); + }); + return s; +} + +/** + * Returns the property names that appear in property access path patterns. + */ +export function getProperties(ds: Array): Set { + const s = new Set(); + for (const d of ds) + if (d) + d.ap.visitAccessPathPatterns(p => { + if (p instanceof PropertyAccessPathPattern) + addAll(p.props, s); + }); + return s; +} \ No newline at end of file diff --git a/src/patternmatching/patternmatcher.ts b/src/patternmatching/patternmatcher.ts new file mode 100644 index 0000000..18827e1 --- /dev/null +++ b/src/patternmatching/patternmatcher.ts @@ -0,0 +1,648 @@ +import {ModuleInfo} from "../analysis/infos"; +import { + AccessPathPattern, + CallDetectionPattern, + CallResultAccessPathPattern, + DetectionPattern, + DisjunctionAccessPathPattern, + ExclusionAccessPathPattern, + Filter, + Glob, + ImportAccessPathPattern, + ImportDetectionPattern, + NumArgsCallFilter, + PotentiallyUnknownAccessPathPattern, + PropertyAccessPathPattern, + ReadDetectionPattern, + Type, + TypeFilter, + WildcardAccessPathPattern, + WriteDetectionPattern +} from "./patterns"; +import assert from "assert"; +import {AnalysisState} from "../analysis/analysisstate"; +import {addAll, deleteAll, deleteMapSetAll, FilePath, mapGetSet, nodeToString, SourceLocationJSON, SourceLocationsToJSON, sourceLocationToStringWithFileAndEnd, Ternary} from "../misc/util"; +import { + isAssignmentExpression, + isCallExpression, + isExpression, + isIdentifier, + isMemberExpression, + isNewExpression, + isObjectProperty, + isOptionalCallExpression, + isOptionalMemberExpression, + Node +} from "@babel/types"; +import micromatch from "micromatch"; +import {AccessPathToken} from "../analysis/tokens"; +import logger from "../misc/logger"; +import {ConstraintVar, NodeVar} from "../analysis/constraintvars"; +import {AccessPath, CallResultAccessPath, ModuleAccessPath, PropertyAccessPath, UnknownAccessPath} from "../analysis/accesspaths"; +import {isDefaultImport} from "./astpatterns"; +import {FragmentState} from "../analysis/fragmentstate"; +import {expressionMatchesType} from "./typematcher"; +import {TypeScriptTypeInferrer} from "../typescript/typeinferrer"; +import {options} from "../options"; + +/** + * Different kinds of match uncertainty for detection pattern matching. + */ +export type Uncertainty = + "accessPath" | // triggers manually written question + {type: "type", exp: Node | undefined, kind: "base" | number | "value", propNames?: Array, typesToMatch: Array} | // uncertainty about a type match + {type: "numArg", exp: Node, numMinArgs: number | undefined, numMaxArgs: number | undefined} | // uncertainty about number of call arguments + "maybePromiseMatch"; // uncertainty about whether a call result is used as a promise +// FIXME: propNames currently unused + +export type UncertaintyJSON = + "accessPath" | + {type: "type", loc?: SourceLocationJSON, kind: "base" | number | "value", propNames?: Array, typesToMatch: Array} | // TODO: loc may be undefined for base filters only, see getPropertyReadObject + {type: "numArg", loc?: SourceLocationJSON, numMinArgs: number | undefined, numMaxArgs: number | undefined} | + "maybePromiseMatch"; + +/** + * Match for a DetectionPattern. + */ +export type DetectionPatternMatch = { + exp: Node, + uncertainties?: Array; +} + +/** + * Matching confidence levels for access path pattern matching. + */ +export const confidenceLevels = ["high", "low"] as const; + +export type ConfidenceLevel = typeof confidenceLevels[number]; + +/** + * Matches for an AccessPathPattern. + * Provides a set of access paths for each matched node. + */ +export type AccessPathPatternMatches = Record>>; + +export type PatternMatchesJSON = { + files: Array, + patterns: Array<{ + pattern: string, + matches: Array + }>; +}; + +export type PatternMatchJSON = { + loc: SourceLocationJSON, + uncertainties?: Array<{ + text: string, + uncertainty: UncertaintyJSON + }> +}; + +export class PatternMatcher { + + private readonly analysisState: AnalysisState; + + private readonly fragmentState: FragmentState; + + private readonly typer: TypeScriptTypeInferrer | undefined; + + /** + * Cache for glob matching. + */ + private readonly moduleCache: Map]>> = new Map; + + /** + * Cache for AccessPathPatterns (except those used in WritePropertyDetectionPatterns). + */ + private readonly expressionCache: Map = new Map; + + /** + * Cache for AccessPathPatterns used in WritePropertyDetectionPatterns. + */ + private readonly writeExpressionCache: Map = new Map; + + /** + * Cache for findUnknowns. + */ + private unknownsCache: Array | undefined; + + constructor(analysisState: AnalysisState, fragmentState: FragmentState, typer?: TypeScriptTypeInferrer) { + this.analysisState = analysisState; + this.fragmentState = fragmentState; + this.typer = typer; + } + + /** + * Finds the (non-analyzed) modules that match the given glob. + * Emits error if an analyzed module matches. + */ + findGlobMatches(glob: Glob): Array<[ModuleAccessPath, Set]> { + let res = this.moduleCache.get(glob); + if (!res) { + res = []; + this.moduleCache.set(glob, res); + const isMatch = micromatch.matcher(glob); + for (const [ap, ns] of this.analysisState.moduleAccessPaths) { + const m = ap.moduleInfo; + if (isMatch(m.getOfficialName()) || (ap.requireName && isMatch(ap.requireName))) + if (options.patterns && m instanceof ModuleInfo && !options.ignoreDependencies) + logger.error(`Error: Pattern contains analyzed module ${m.getOfficialName()} (see --ignore-dependencies)`); + else + res.push([ap, ns]); + } + } + return res; + } + + /** + * Finds the AST nodes that match the given access path pattern, together with the associated access paths. + */ + findAccessPathPatternMatches(p: AccessPathPattern, write?: boolean): AccessPathPatternMatches { + let cache = write ? this.writeExpressionCache : this.expressionCache; + let res = cache.get(p); + if (!res) { + const high = new Map, low = new Map; + res = {high, low}; + cache.set(p, res); + const a = this.analysisState; + const f = this.fragmentState; + + /** + * Adds matches (without considering demotion). + * @param level current confidence level + * @param ap current access path + * @param q property access or call expressions where PropertyAccessPaths/CallResultAccessPaths are created with the current access path as base/caller + * @param tmp matches are added to this map (before considering demotion) + * @param subvs constraint variables for sub-expressions of matches are added to this map + * @param exclude if present, skip these matches (for matches that have already been added to result) + */ + function addMatches(level: ConfidenceLevel, + ap: AccessPath, + q: Map | undefined, + tmp: Map>, + subvs: Map, + exclude?: Map>) { + if (q) + for (const [r, {bp, sub}] of q) // r is a property read (or call) expression where the base (or caller) matches the sub-pattern, bp is the access path created at that expression, sub is the constraint variable for the sub-expression + if (!exclude || !exclude.has(r) || !exclude.get(r)!.has(bp)) { + if (logger.isDebugEnabled()) + logger.debug(`Match ${bp} (sub: ${ap}) at ${nodeToString(r)} (confidence: ${level})`) + mapGetSet(tmp, r).add(bp); + subvs.set(r, sub); + } + } + + /** + * Finds the constraint variables that represent the sub-expression (base expression at property read / function expression at call) + * where all tokens have been matched by the sub-pattern with high confidence. + * Then transfers the matches found to the result, demoting high to low confidence for sub-expressions that are not fully matched. + * @param level current confidence level + * @param sub matches for sub-pattern + * @param tmp matches found (before considering demotion) + * @param subvs constraint variables for sub-expressions of matches + * @param nextsub matches are added here (if argument present) + */ + function transfer(level: ConfidenceLevel, + sub: AccessPathPatternMatches, + tmp: Map>, + subvs: Map, + nextsub?: AccessPathPatternMatches) { + // find sub-expressions that are fully matched + let covered: Set | undefined; + if (level === "high") { + covered = new Set; + for (const subv of subvs.values()) { + let isCovered = true; // true if all access paths at the sub-expression have been matched by the sub-pattern with high confidence + for (const t of f.getTokens(f.getRepresentative(subv))) + if (t instanceof AccessPathToken) { + let isMatched = false; + for (const aps of sub.high.values()) + if (aps.has(t.ap)) { + isMatched = true; + break; + } + if (!isMatched) { + isCovered = false; + break; + } + } else { + isCovered = false; + break; + } + if (logger.isDebugEnabled()) + logger.debug(`Covered ${subv}: ${isCovered}`); + if (isCovered) + covered.add(subv); + } + } + // transfer from tmp to result, demote if not fully matched + for (const [n, bps] of tmp) + for (const bp of bps) { + let newlevel = level; + if (level === "high" && !covered!.has(subvs.get(n)!)) { + if (logger.isDebugEnabled()) + logger.debug(`Demoting match ${nodeToString(n)} with ${p} to low confidence`); + newlevel = "low"; + } + const s = mapGetSet(res![newlevel], n); + if (!s.has(bp)) { + s.add(bp); + if (nextsub) + mapGetSet(nextsub![newlevel], n).add(bp); + } + } + } + + if (p instanceof ImportAccessPathPattern) { + for (const [ap, ns] of this.findGlobMatches(p.glob)) + for (const n of ns) + mapGetSet(high, n).add(ap); + // workaround to support TAPIR's treatment of default imports + for (const aps of high.values()) + for (const ap of aps) { + const ps = a.propertyReadAccessPaths.get(ap); + if (ps) { + const q = ps.get("default"); + if (q) + for (const [p, {bp}] of q) + mapGetSet(high, p).add(bp); + } + } + } else if (p instanceof PropertyAccessPathPattern) { + const sub = this.findAccessPathPatternMatches(p.base); + for (const level of confidenceLevels) { + const tmp = new Map>(); // temporary result (before deciding demotions) + const subvs = new Map(); + for (const aps of sub[level].values()) + for (const ap of aps) { // ap is an access path that matches the sub-pattern + const ps = (write ? a.propertyWriteAccessPaths : a.propertyReadAccessPaths).get(ap); + if (ps) + for (const prop of p.props) + addMatches(level, ap, ps.get(prop), tmp, subvs); + } + transfer(level, sub, tmp, subvs); + } + } else if (p instanceof CallResultAccessPathPattern) { + const sub = this.findAccessPathPatternMatches(p.fun); + for (const level of confidenceLevels) { + const tmp = new Map>(); + const subvs = new Map(); + for (const aps of sub[level].values()) + for (const ap of aps) + addMatches(level, ap, a.callResultAccessPaths.get(ap), tmp, subvs); + transfer(level, sub, tmp, subvs); + } + } else if (p instanceof DisjunctionAccessPathPattern) { + const subs = []; + for (const ap of p.aps) + subs.push(this.findAccessPathPatternMatches(ap)); + for (const sub of subs) + for (const level of confidenceLevels) + for (const [n, aps] of sub[level]) + addAll(aps, mapGetSet(res[level], n)); + for (const sub of subs) + for (const [n, aps] of sub.low) + deleteAll(aps.values(), mapGetSet(high, n)) + } else if (p instanceof ExclusionAccessPathPattern) { + const included = this.findAccessPathPatternMatches(p.include); + const excluded = this.findAccessPathPatternMatches(p.exclude); + // start with all the included matches + for (const level of confidenceLevels) + for (const [n, aps] of included[level]) + addAll(aps, mapGetSet(res[level], n)); + // remove the excluded high-confidence matches from the result + for (const [n, aps] of excluded.high) { + deleteMapSetAll(high, n, aps); + deleteMapSetAll(low, n, aps); + } + // demote the excluded low-confidence matches from the result + for (const [n, aps] of excluded.low) + for (const ap of aps) + if (high.get(n)?.delete(ap)) + mapGetSet(low, n).add(ap); + } else if (p instanceof PotentiallyUnknownAccessPathPattern) { + const sub = this.findAccessPathPatternMatches(p.ap); + for (const level of confidenceLevels) + for (const [n, aps] of sub[level]) + addAll(aps, mapGetSet(res[level], n)); + const ap = a.canonicalizeAccessPath(new UnknownAccessPath()); + for (const n of this.findUnknowns()) + mapGetSet(low, n).add(ap); + } else if (p instanceof WildcardAccessPathPattern) { + // add all expressions that can be reached from matches to p.ap in zero or more calls or property accesses + let sub = this.findAccessPathPatternMatches(p.ap); + // copy results from sub to res + for (const level of confidenceLevels) + for (const [n, bps] of sub[level]) + addAll(bps, mapGetSet(res![level], n)); + // transitively find matching property accesses and calls + const visited = {high: new Set, low: new Set}; + while (sub.high.size !== 0 || sub.low.size !== 0) { + const nextsub = {high: new Map, low: new Map}; + for (const level of confidenceLevels) { + const tmp = new Map>(); + const subvs = new Map(); + for (const aps of sub[level].values()) + for (const ap of aps) + if (!visited[level].has(ap)) { + visited[level].add(ap); + // look for PropertyAccessPath matches + const ps = (write ? a.propertyWriteAccessPaths : a.propertyReadAccessPaths).get(ap); + if (ps) + for (const q of ps.values()) + addMatches(level, ap, q, tmp, subvs, res[level]); + // look for CallResultAccessPath matches + addMatches(level, ap, a.callResultAccessPaths.get(ap), tmp, subvs, res[level]); + } + transfer(level, sub, tmp, subvs, nextsub); + } + sub = nextsub; + } + } else + assert.fail("Unexpected AccessPathPattern"); + } + if (logger.isDebugEnabled()) + for (const level of confidenceLevels) + for (const [e, aps] of res[level]) + for (const ap of aps) + logger.debug(`Pattern ${p} matched access path ${ap} at ${sourceLocationToStringWithFileAndEnd(e.loc)} (confidence: ${level})`); + return res; + } + + /** + * Finds the AST nodes that have UnknownAccessPath. + */ + findUnknowns(): Array { + if (!this.unknownsCache) { + this.unknownsCache = []; + for (const [v, ts] of this.fragmentState.getAllVarsAndTokens()) // only includes representatives, but always followed by property read + if (v instanceof NodeVar) + for (const t of ts) + if (t instanceof AccessPathToken && t.ap instanceof UnknownAccessPath) { + if (logger.isDebugEnabled()) + logger.debug(`Unknown: ${v} (${t})`); + this.unknownsCache.push(v.node); + } + } + return this.unknownsCache; + } + + /** + * Checks whether the given AST node matches the given filter. + * Also returns the relevant (sub-)expression (or the expression itself if a spread expression appears). + */ + filterMatches(n: Node, filter: Filter): [Ternary, Node] { + if (!(isCallExpression(n) || isOptionalCallExpression(n) || isNewExpression(n))) + assert.fail(`Unexpected node type ${n.type}`); + if (filter instanceof NumArgsCallFilter) { + let simple = true, exps = 0; + for (const arg of n.arguments) + if (isExpression(arg)) + exps++; + else + simple = false; + let res; + if (simple) + res = (filter.minArgs === undefined || filter.minArgs <= n.arguments.length) && + (filter.maxArgs === undefined || n.arguments.length <= filter.maxArgs) ? Ternary.True : Ternary.False; + else if (filter.maxArgs === undefined && filter.minArgs !== undefined && filter.minArgs <= exps) + res = Ternary.True; + else + res = Ternary.Maybe; + return [res, n]; + } + if (!(filter instanceof TypeFilter)) + assert.fail("Unexpected Filter"); + for (const arg of n.arguments) + if (!isExpression(arg)) + return [Ternary.Maybe, n]; + let arg; + if (filter.selector.head === "base") { + arg = n.callee; + if ((isMemberExpression(n.callee) || isOptionalMemberExpression(n.callee)) && isExpression(n.callee.object)) + arg = n.callee.object; + else + return [Ternary.Maybe, n]; + } + else if (filter.selector.head < 0) { + if (n.arguments.length + filter.selector.head >= 0) + arg = n.arguments[n.arguments.length + filter.selector.head]; + else + return [Ternary.False, n]; + } else { + if (filter.selector.head < n.arguments.length) + arg = n.arguments[filter.selector.head]; + else + return [Ternary.False, n]; + } + return [expressionMatchesType(arg, filter.selector.props, filter.types, this.typer), arg]; + } + + /** + * Finds the AST nodes that match the given detection pattern, + * with descriptions of the causes of uncertainty for low-confidence matches. + */ + findDetectionPatternMatches(d: DetectionPattern): Array { + const res: Array = []; + if (d instanceof ImportDetectionPattern) { + const sub = this.findAccessPathPatternMatches(d.ap); + for (const level of confidenceLevels) + for (const exp of sub[level].keys()) + if (!isMemberExpression(exp) && !isIdentifier(exp)) // excluding E.default expressions and identifiers + if (!d.onlyDefault || isDefaultImport(exp)) + res.push({exp, uncertainties: level === "low" ? ["accessPath" as const] : undefined}); + } else if (d instanceof ReadDetectionPattern) { + const sub = this.findAccessPathPatternMatches(d.ap); + for (const level of confidenceLevels) { + for (const exp of sub[level].keys()) { + if (!d.notInvoked || !this.analysisState.invokedExpressions.has(exp)) { + const uncertainties: Array = []; + if (level === "low" && !d.baseFilter) // uncertainty is added through the base filter in this case + uncertainties.push("accessPath"); + if (d.baseFilter && level === "low") { // if certain on access path, do not check base filter + const expObject = getPropertyReadObject(exp); + if (expObject) + switch (expressionMatchesType(expObject, undefined, d.baseFilter, this.typer)) { + case Ternary.False: + continue; + case Ternary.Maybe: + uncertainties.push({type: "type", exp: expObject, kind: "base", typesToMatch: d.baseFilter}); + break; + } + else // TODO: unsure where the base expression is, just using 'undefined' (could be improved if findAccessPathPatternMatches also returned the associated expressions for the sub-matches) + uncertainties.push({type: "type", exp: undefined, kind: "base", typesToMatch: d.baseFilter}); + } + res.push({exp, uncertainties}); + } + // for identifier matches in imports, also include uses of the identifier + if (isIdentifier(exp)) { + const refs = this.analysisState.importDeclRefs.get(exp); + if (refs) + for (const n of refs) { + const uncertainties: Array = []; + if (level === "low") + uncertainties.push("accessPath"); + res.push({exp: n, uncertainties}); + } + } + } + } + } else if (d instanceof WriteDetectionPattern) { + const sub = this.findAccessPathPatternMatches(d.ap, true); + for (const level of confidenceLevels) + for (const exp of sub[level].keys()) { + if (!isAssignmentExpression(exp)) + assert.fail(`Unexpected node type ${exp.type}, expected AssignmentExpression`); + if (!isMemberExpression(exp.left)) + assert.fail(`Unexpected node type ${exp.left.type}, expected MemberExpression`); + const uncertainties: Array = []; + if (level === "low") + uncertainties.push("accessPath"); + if (d.valueFilter) + switch (expressionMatchesType(exp.right, undefined, d.valueFilter, this.typer)) { + case Ternary.False: + continue; + case Ternary.Maybe: + uncertainties.push({type: "type", exp: exp.right, kind: "value", typesToMatch: d.valueFilter}); + break; + } + if (d.baseFilter) + switch (expressionMatchesType(exp.left.object, undefined, d.baseFilter, this.typer)) { + case Ternary.False: + continue; + case Ternary.Maybe: + uncertainties.push({type: "type", exp: exp.left.object, kind: "base", typesToMatch: d.baseFilter}); + break; + } + res.push({exp, uncertainties}); + } + } else if (d instanceof CallDetectionPattern) { + // 'call' patterns match entire call expressions but refer only to the functions being called, + // so we wrap the access path pattern in a CallResultAccessPathPattern + const sub = this.findAccessPathPatternMatches(new CallResultAccessPathPattern(d.ap)); + for (const level of confidenceLevels) + matches: for (const exp of sub[level].keys()) { + if ((!d.onlyReturnChanged || !this.analysisState.callsWithUnusedResult.has(exp)) && + (!d.onlyNonNewCalls || !isNewExpression(exp)) && + (!d.onlyWhenUsedAsPromise || this.analysisState.callsWithResultMaybeUsedAsPromise.has(exp))) { + const uncertainties: Array = []; + if (d.onlyWhenUsedAsPromise && this.analysisState.callsWithResultMaybeUsedAsPromise.has(exp)) + uncertainties.push("maybePromiseMatch"); + if (level === "low" && !d.filters?.some(f => f instanceof TypeFilter && f.selector.head === "base")) // if uncertain and there is a base filter, an Uncertainty will be added below so skip here + uncertainties.push("accessPath"); + if (d.filters) + for (const f of d.filters) + if (!(level === "high" && f instanceof TypeFilter && f.selector.head === "base")) { // skip base type filters if the access path match is certain + const [t, arg] = this.filterMatches(exp, f); + switch (t) { + case Ternary.False: + continue matches; + case Ternary.Maybe: + if (f instanceof NumArgsCallFilter) + uncertainties.push({ + type: "numArg", + exp, + numMinArgs: f.minArgs, + numMaxArgs: f.maxArgs + }); + else if (f instanceof TypeFilter) + uncertainties.push({ + type: "type", + exp: arg, + kind: f.selector.head, + typesToMatch: f.types + }); // FIXME: f.arg.props not used? + else + throw new Error("Unexpected Filter"); + break; + } + } + res.push({exp, uncertainties}); + } + } + } else + assert.fail("Unexpected DetectionPattern"); + return res; + } +} + +/** + * Finds the base object expression of a property read, + * returns undefined for property reads at imports and destructuring assignments. + */ +function getPropertyReadObject(exp: Node): Node | undefined { + if (isMemberExpression(exp) || isOptionalMemberExpression(exp)) + return exp.object; + if (isIdentifier(exp)) // example: import { foo } from "bar" + return undefined; + if (isObjectProperty(exp)) // example: const { foo } = require("bar") + return undefined; + assert.fail(`Unexpected node type ${exp.type} at ${sourceLocationToStringWithFileAndEnd(exp.loc)}`); +} + +export function convertPatternMatchesToJSON(patterns: Array, matcher: PatternMatcher): PatternMatchesJSON { + const res: PatternMatchesJSON = {files: [], patterns: []}; + const locs = new SourceLocationsToJSON(res.files); + function convertUncertaintyToJSON(u: Uncertainty): UncertaintyJSON { + if (typeof u === "object" && "exp" in u) { + const r: UncertaintyJSON & {exp?: Node} = {...u, loc: u.exp && locs.makeLocString(u.exp.loc)}; + delete r.exp; + return r; + } else + return u; + } + for (const p of patterns) + if (p) { + const ms = matcher.findDetectionPatternMatches(p); + if (ms.length > 0) { + const matches: Array = []; + res.patterns.push({pattern: p.toString(), matches}); + for (const m of ms) { + const match: PatternMatchJSON = { + loc: locs.makeLocString(m.exp.loc), + uncertainties: [] + }; + matches.push(match); + if (m.uncertainties && m.uncertainties.length > 0) { + for (const u of m.uncertainties) + match.uncertainties!.push({ + uncertainty: convertUncertaintyToJSON(u), + text: generateQuestion(u) ?? "Access path match uncertain" + }); + } + } + } + } + return res; +} + +/** + * Generates a human-readable question from the given uncertainty description, + * or returns undefined if uncertain access path match. + */ +export function generateQuestion(u: Uncertainty): string | undefined { + if (u === "accessPath") + return undefined; + else if (u === "maybePromiseMatch") + return "Is the result used as a promise?"; + else if (u.type === "type") { + // TODO: compute string as done in JSFIXMain#getTextFromMatchResult case for MaybeTypeMatch + const prefix = u.kind === "base" ? "the base expression" : u.kind === "value" ? "the expression" : `argument ${u.kind + 1}`; + assert(u.typesToMatch.length > 0, "typesToMatch empty"); + const suffix = u.typesToMatch.length > 1 ? + `one of the types ${u.typesToMatch.slice(0, -1).join(", ")}, or ${u.typesToMatch[u.typesToMatch.length - 1]}` : + `type ${u.typesToMatch[0]}`; + return `Is ${prefix} of ${suffix}?`; + } else if (u.type === "numArg") { + const prefix = "Is the call supplied with "; + if (u.numMinArgs === undefined) + return `${prefix}at most ${u.numMaxArgs} argument${u.numMaxArgs === 1 ? "" : "s"}?` + else if (u.numMaxArgs === undefined) + return `${prefix}at least ${u.numMinArgs} argument${u.numMinArgs === 1 ? "" : "s"}?` + else if (u.numMinArgs === u.numMaxArgs) + return `${prefix}exactly ${u.numMinArgs} argument${u.numMinArgs === 1 ? "" : "s"}?` + else + return `Is the call supplied with at least ${u.numMinArgs} and at most ${u.numMaxArgs} arguments?`; + } else + throw new Error("Unexpected Uncertainty type"); +} diff --git a/src/patternmatching/patternparser.ts b/src/patternmatching/patternparser.ts new file mode 100644 index 0000000..63aa7b7 --- /dev/null +++ b/src/patternmatching/patternparser.ts @@ -0,0 +1,384 @@ +import { + AccessPathPattern, + TypeFilter, + CallDetectionPattern, + Filter, + FilterSelector, + CallResultAccessPathPattern, + DetectionPattern, + DisjunctionAccessPathPattern, + ExclusionAccessPathPattern, + Glob, + ImportAccessPathPattern, + ImportDetectionPattern, + NumArgsCallFilter, + PotentiallyUnknownAccessPathPattern, + PropertyAccessPathPattern, + ReadDetectionPattern, + SimpleType, + Type, + WildcardAccessPathPattern, + WriteDetectionPattern +} from "./patterns"; + +export class AccessPathPatternCanonicalizer { + + canonical: Map = new Map; + + canonicalize(p: T): T { + const key = p.toString(); + const c = this.canonical.get(key) as T; + if (c) + return c; + this.canonical.set(key, p); + return p; + } +} + +// parse-unparse detection pattern if run as script +if (require.main === module) { + console.log(parseDetectionPattern(process.argv[2], new AccessPathPatternCanonicalizer()).toString()); +} + +/** + * Attempts to parse an access path pattern. + * @throws position in string where parsing failed, in case of parse error + */ +export function parseDetectionPattern(pattern: string, c: AccessPathPatternCanonicalizer): DetectionPattern { + + function parseSpace(start: number, optional: boolean = true): number { + let pos = start; + while (pattern[pos] === " ") + pos++; + if (!optional && pos === start) + throw pos; + return pos; + } + + function parseEnd(start: number) { + if (start != pattern.length) + throw start; + } + + function parseOptionalKeyword(start: number, keyword: string): [boolean, number] { + if (pattern.substring(start, start + keyword.length).toLowerCase() === keyword) + return [true, start + keyword.length]; + else + return [false, start]; + } + + function parseChar(start: number, char: string): number { + if (pattern[start] === char) + return start + 1; + else + throw start; + } + + function parseAccessPathPattern(start: number): [AccessPathPattern, number] { + let p, pos; + pos = parseSpace(start); + switch (pattern[pos]) { + case "<": + let g; + [g, pos] = parseGlob(pos + 1); + p = c.canonicalize(new ImportAccessPathPattern(g)); + pos = parseSpace(pos); + pos = parseChar(pos, ">"); + break; + case "{": + pos = pos + 1; + const aps: Array = []; + do { + let q; + [q, pos] = parseAccessPathPattern(pos); + pos = parseSpace(pos); + aps.push(q); + } while (pattern[pos] === "," && ++pos); + pos = parseChar(pos, "}"); + p = c.canonicalize(new DisjunctionAccessPathPattern(aps)); + break; + case "(": + let incl, excl; + [incl, pos] = parseAccessPathPattern(pos + 1); + pos = parseSpace(pos); + pos = parseChar(pos, "\\"); + [excl, pos] = parseAccessPathPattern(pos); + pos = parseSpace(pos); + pos = parseChar(pos, ")"); + p = c.canonicalize(new ExclusionAccessPathPattern(incl, excl)); + break; + default: + throw start; + } + pos = parseSpace(pos); + while ("(.?*".includes(pattern[pos])) { + if (pattern[pos] === ".") { + pos++; + pos = parseSpace(pos); + const props: Array = []; + if (pattern[pos] === "{" && ++pos) { + do { + let prop; + pos = parseSpace(pos); + [prop, pos] = parseProp(pos); + pos = parseSpace(pos); + props.push(prop); + } while (pattern[pos] === "," && ++pos); + pos = parseChar(pos, "}"); + } else { + let prop; + [prop, pos] = parseProp(pos); + props.push(prop) + } + p = c.canonicalize(new PropertyAccessPathPattern(p, props)); + } else if (pattern[pos] === "(" && pattern[pos + 1] === ")") { + pos += 2; + pos = parseSpace(pos); + p = c.canonicalize(new CallResultAccessPathPattern(p)); + } else if (pattern[pos] === "?") { + pos++; + pos = parseSpace(pos); + p = c.canonicalize(new PotentiallyUnknownAccessPathPattern(p)); + } else if (pattern[pos] === "*" && pattern[pos + 1] === "*") { + pos += 2; + pos = parseSpace(pos); + p = c.canonicalize(new WildcardAccessPathPattern(p)); + } else + throw pos + 1; + pos = parseSpace(pos); + } + return [p, pos]; + } + + function parseProp(start: number): [string, number] { + let pos = start; + while (pos < pattern.length && !",{}./()*<>[]: ".includes(pattern[pos])) + pos++; + if (pos === start) + throw pos; + return [pattern.substring(start, pos), pos]; + } + + function parseGlob(start: number): [Glob, number] { + let end = pattern.indexOf(">", start); // TODO: ignoring that '>' may be escaped/quoted + if (end === -1) + end = pattern.length; + const glob = pattern.substring(start, end).trim(); + return [glob, end]; + } + + function parseOptionalNumber(start: number, allowNegative: boolean = false): [number | undefined, number] { + let pos = start; + if (allowNegative && pattern[pos] === "-") + pos++; + while (pattern[pos] >= "0" && pattern[pos] <= "9") + pos++; + if (pos === start) + return [undefined, start]; + return [parseInt(pattern.substring(start, pos), 10), pos]; + } + + function parseTypeScriptType(start: number): [string, number] { // TODO: what syntax should be allowed for TS types? + let pos = start, c: string; + // noinspection CommaExpressionJS + while (c = pattern[pos], (c >= "0" && c <= "9") || (c >= "a" && c <= 'z') || (c >= "A" && c <= "Z") || "._$".includes(c)) + pos++; + if (pos === start) + throw start; + return [pattern.substring(start, pos), pos]; + } + + function parseFilter(start: number): [Filter, number] { + let pos = start; + if (pattern[pos] === "[") { + pos++; + pos = parseSpace(pos); + let minArgs; + [minArgs, pos] = parseOptionalNumber(pos); + pos = parseSpace(pos); + pos = parseChar(pos, ","); + pos = parseSpace(pos); + let maxArgs; + [maxArgs, pos] = parseOptionalNumber(pos); + pos = parseSpace(pos); + pos = parseChar(pos, "]"); + pos = parseSpace(pos); + return [new NumArgsCallFilter(minArgs, maxArgs), pos]; + } else { + let selector; + [selector, pos] = parseFilterSelector(pos); + pos = parseSpace(pos); + pos = parseChar(pos, ":"); + pos = parseSpace(pos); + let types; + [types, pos] = parseTypes(pos); + pos = parseSpace(pos); + return [new TypeFilter(selector, types), pos]; + } + } + + function parseColonTypes(start: number): [Array, number] { + pos = parseSpace(start); + pos = parseChar(pos, ":"); + pos = parseSpace(pos); + let types; + [types, pos] = parseTypes(pos); + pos = parseSpace(pos); + return [types, pos]; + } + + function parseOptionalBaseFilter(start: number): [Array | undefined, number] { + let [base, pos] = parseOptionalKeyword(start, "base"); + if (!base) + return [undefined, pos]; + let types; + [types, pos] = parseColonTypes(pos); + return [types, pos]; + } + + function parseOptionalValueFilter(start: number): [Array | undefined, number] { + let [value, pos] = parseOptionalKeyword(start, "value"); + if (!value) + return [undefined, pos]; + let types; + [types, pos] = parseColonTypes(pos); + return [types, pos]; + } + + function parseOptionalValueBaseFilters(start: number): [Array | undefined, Array | undefined, number] { + let [valueFilter, pos] = parseOptionalValueFilter(start); + let baseFilter; + [baseFilter, pos] = parseOptionalBaseFilter(pos); + if (!valueFilter && baseFilter) + [valueFilter, pos] = parseOptionalValueFilter(pos); + return [valueFilter, baseFilter, pos]; + } + + function parseFilterSelector(start: number): [FilterSelector, number] { + let head: number | "base" | undefined; + let [base, pos] = parseOptionalKeyword(start, "base"); + if (base) + head = "base"; + else { + [head, pos] = parseOptionalNumber(pos, true); + if (head === undefined) + throw start; + } + const props = []; + pos = parseSpace(pos); + while (pattern[pos] === "." && ++pos) { + let prop; + pos = parseSpace(pos); + [prop, pos] = parseProp(pos); + pos = parseSpace(pos); + props.push(prop); + } + return [new FilterSelector(head, props.length > 0 ? props : undefined), pos]; + } + + function parseTypes(start: number): [Array, number] { + let pos = start; + let types: Array = []; + if (pattern[pos] === "{" && ++pos) { + do { + let typ; + pos = parseSpace(pos); + [typ, pos] = parseType(pos); + pos = parseSpace(pos); + types.push(typ); + } while (pattern[pos] === "," && ++pos); + pos = parseChar(pos, "}"); + } else { + let typ; + [typ, pos] = parseType(pos); + types.push(typ); + } + return [types, pos]; + } + + function parseType(start: number): [Type, number] { + for (const t of ["undefined", "boolean", "string", "number", "array", "empty-array", "object", "null", "function", "any"]) + if (pattern.startsWith(t, start)) { + let num, pos = start + t.length; + if (t === "function") + [num, pos] = parseOptionalNumber(pos); + return [new Type(t as SimpleType, num, undefined, undefined), pos]; + } + let match, pos; + [match, pos] = parseOptionalKeyword(start, "true"); + if (match) + return [new Type(undefined, undefined, true, undefined), pos]; + [match, pos] = parseOptionalKeyword(start, "false"); + if (match) + return [new Type(undefined, undefined, false, undefined), pos]; + if (pattern[pos] === "\"" && ++pos) { + const i = pattern.indexOf("\"", pos); + if (i === -1) + throw pos; + const str = pattern.substring(pos, i); + pos = i + 1; + return [new Type(undefined, undefined, str, undefined), pos]; + } + let num; + [num, pos] = parseOptionalNumber(start, true); // TODO: support floating-point numbers? + if (num !== undefined) + return [new Type(undefined, undefined, num, undefined), pos]; + let tsType; + [tsType, pos] = parseTypeScriptType(start); + return [new Type(undefined, undefined, undefined, tsType), pos]; + } + + let pos, res, p, b; + pos = parseSpace(0); + if (([b, pos] = parseOptionalKeyword(pos, "import")) && b) { + let onlyDefault; + [onlyDefault, pos] = parseOptionalKeyword(pos, "d"); + pos = parseSpace(pos, false); + if (pattern[pos] !== "<") { // legacy mode + let g; + [g, pos] = parseGlob(pos); + p = c.canonicalize(new ImportAccessPathPattern(g)); + } else { + [p, pos] = parseAccessPathPattern(pos); + if (!(p instanceof ImportAccessPathPattern)) + throw pos; + } + res = new ImportDetectionPattern(p, onlyDefault); + } else if (([b, pos] = parseOptionalKeyword(pos, "read")) && b) { + let notInvoked; + [notInvoked, pos] = parseOptionalKeyword(pos, "o") + pos = parseSpace(pos, false); + [p, pos] = parseAccessPathPattern(pos); + if (!(p instanceof PropertyAccessPathPattern)) + throw pos; + let baseFilter; + [baseFilter, pos] = parseOptionalBaseFilter(pos); + res = new ReadDetectionPattern(p, notInvoked, baseFilter); + } else if (([b, pos] = parseOptionalKeyword(pos, "write")) && b) { + pos = parseSpace(pos, false); + [p, pos] = parseAccessPathPattern(pos); + if (!(p instanceof PropertyAccessPathPattern)) + throw pos; + let valueFilter, baseFilter; + [valueFilter, baseFilter, pos] = parseOptionalValueBaseFilters(pos); + res = new WriteDetectionPattern(p, valueFilter, baseFilter); + } else if (([b, pos] = parseOptionalKeyword(pos, "call")) && b) { + let onlyReturnChanged, onlyWhenUsedAsPromise, onlyNonNewCalls; + [onlyReturnChanged, pos] = parseOptionalKeyword(pos, "r"); + [onlyWhenUsedAsPromise, pos] = parseOptionalKeyword(pos, "promise"); + [onlyNonNewCalls, pos] = parseOptionalKeyword(pos, "notnew"); + pos = parseSpace(pos, false); + [p, pos] = parseAccessPathPattern(pos) + let filters: Array = []; + while (pos < pattern.length) { + let filter; + [filter, pos] = parseFilter(pos); + filters.push(filter); + } + res = new CallDetectionPattern(p, onlyReturnChanged, onlyWhenUsedAsPromise, onlyNonNewCalls, filters.length > 0 ? filters : undefined); + } else + throw 0; + pos = parseSpace(pos); + parseEnd(pos); + return res; +} diff --git a/src/patternmatching/patterns.ts b/src/patternmatching/patterns.ts new file mode 100644 index 0000000..36ae0d6 --- /dev/null +++ b/src/patternmatching/patterns.ts @@ -0,0 +1,374 @@ +import assert from "assert"; + +export type Glob = string; + +export abstract class DetectionPattern { + + readonly ap: AccessPathPattern; + + protected constructor(ap: AccessPathPattern) { + this.ap = ap; + } + + abstract toString(): string; +} + +export class ImportDetectionPattern extends DetectionPattern { + + readonly onlyDefault: boolean; // TODO: change to "importDefault"? + + constructor(ap: ImportAccessPathPattern, onlyDefault: boolean) { + super(ap); + this.onlyDefault = onlyDefault; + } + + toString(): string { + return `import${this.onlyDefault ? "D" : ""} ${this.ap}`; + } +} + +export class ReadDetectionPattern extends DetectionPattern { + + readonly notInvoked: boolean; // TODO: change to "readNotCall"? + + readonly baseFilter: Array | undefined; + + constructor(ap: PropertyAccessPathPattern, notInvoked: boolean, baseFilter: Array | undefined) { + super(ap); + this.notInvoked = notInvoked; + this.baseFilter = baseFilter; + } + + toString(): string { + const base = this.baseFilter ? ` base:${this.baseFilter.length === 1 ? this.baseFilter[0] : `{${this.baseFilter.join(",")}`}` : ""; + return `read${this.notInvoked ? "O" : ""} ${this.ap}${base}`; + } +} + +export class WriteDetectionPattern extends DetectionPattern { + + readonly valueFilter: Array | undefined; + + readonly baseFilter: Array | undefined; + + constructor(ap: PropertyAccessPathPattern, valueFilter: Array | undefined, baseFilter: Array | undefined) { + super(ap); + this.valueFilter = valueFilter; + this.baseFilter = baseFilter; + // TODO: assert type 'any' not in filters? + } + + toString(): string { + const base = this.baseFilter ? ` base:${this.baseFilter.length === 1 ? this.baseFilter[0] : `{${this.baseFilter.join(",")}`}` : ""; + const value = this.valueFilter ? ` value:${this.valueFilter.length === 1 ? this.valueFilter[0] : `{${this.valueFilter.join(",")}`}` : ""; + return `write ${this.ap}${base}${value}`; + } +} + +export class CallDetectionPattern extends DetectionPattern { // TODO: introduce ComponentDetectionPattern? (see ComponentAccessPathPattern) + + readonly onlyReturnChanged: boolean; // TODO: change to "callReturns"? + + readonly onlyWhenUsedAsPromise: boolean; + + readonly onlyNonNewCalls: boolean; + + readonly filters: Array | undefined; + + constructor(ap: AccessPathPattern, onlyReturnChanged: boolean, onlyWhenUsedAsPromise: boolean, onlyNonNewCalls: boolean, filters: Array | undefined) { + super(ap); + this.onlyReturnChanged = onlyReturnChanged; + this.onlyWhenUsedAsPromise = onlyWhenUsedAsPromise; + this.onlyNonNewCalls = onlyNonNewCalls; + this.filters = filters; + } + + toString(): string { + return `call${this.onlyReturnChanged ? "R" : this.onlyWhenUsedAsPromise ? "Promise" : this.onlyNonNewCalls ? "NotNew" : ""} ${this.ap}${this.filters && this.filters.length > 0 ? " " + this.filters.join(" ") : ""}`; + } +} + +export interface AccessPathPattern { + + toString(): string; + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void; +} + +export class ImportAccessPathPattern { + + readonly glob: Glob; + + constructor(glob: Glob) { + this.glob = glob; + } + + toString(): string { + return `<${this.glob}>`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + } +} + +export class PropertyAccessPathPattern { + + readonly base: AccessPathPattern; + + readonly props: Array; + + constructor(base: AccessPathPattern, props: Array) { + this.base = base; + this.props = props; + } + + toString(): string { + return `${this.base}.${this.props.length === 1 ? `${this.props[0]}` : `{${this.props.join(',')}}`}`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.base.visitAccessPathPatterns(visitor); + } +} + +export class CallResultAccessPathPattern { + + readonly fun: AccessPathPattern; + + constructor(fun: AccessPathPattern) { + this.fun = fun; + } + + toString(): string { + return `${this.fun}()`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.fun.visitAccessPathPatterns(visitor); + } +} + +export class ComponentAccessPathPattern { + + readonly component: AccessPathPattern; + + constructor(component: AccessPathPattern) { + this.component = component; + } + + toString(): string { + return `${this.component}<>`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.component.visitAccessPathPatterns(visitor); + } +} + +export class AbbreviatedPathPattern { + + readonly prefix: AccessPathPattern; + + constructor(prefix: AccessPathPattern) { + this.prefix = prefix; + } + + toString(): string { + return `${this.prefix}…`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.prefix.visitAccessPathPatterns(visitor); + } +} + +export class DisjunctionAccessPathPattern { + + readonly aps: Array; + + constructor(aps: Array) { + this.aps = aps; + } + + toString(): string { + return `{${this.aps.map((ap: AccessPathPattern) => ap.toString()).join(',')}}`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + for (const ap of this.aps) + ap.visitAccessPathPatterns(visitor); + } +} + +export class ExclusionAccessPathPattern { + + readonly include: AccessPathPattern; + + readonly exclude: AccessPathPattern; + + constructor(include: AccessPathPattern, exclude: AccessPathPattern) { + this.include = include; + this.exclude = exclude; + } + + toString(): string { + return `(${this.include}\\${this.exclude})`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.include.visitAccessPathPatterns(visitor); + this.exclude.visitAccessPathPatterns(visitor); + } +} + +export class WildcardAccessPathPattern { + + readonly ap: AccessPathPattern; + + constructor(ap: AccessPathPattern) { + this.ap = ap; + } + + toString(): string { + return `${this.ap}**`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.ap.visitAccessPathPatterns(visitor); + } +} + +export class PotentiallyUnknownAccessPathPattern { + + readonly ap: AccessPathPattern; + + constructor(ap: AccessPathPattern) { + this.ap = ap; + } + + toString(): string { + return `${this.ap}?`; + } + + visitAccessPathPatterns(visitor: (p: AccessPathPattern) => void): void { + visitor(this); + this.ap.visitAccessPathPatterns(visitor); + } +} + +export abstract class Filter { + + abstract toString(): string; +} + +export class NumArgsCallFilter extends Filter { + + readonly minArgs: number | undefined; + + readonly maxArgs: number | undefined; + + constructor(minArgs: number | undefined, maxArgs: number | undefined) { + super(); + this.minArgs = minArgs; + this.maxArgs = maxArgs; + } + + public toString(): string { + return `[${this.minArgs !== undefined ? this.maxArgs : ""},${this.maxArgs !== undefined ? this.maxArgs : ""}]`; + } +} + +export class TypeFilter extends Filter { + + readonly selector: FilterSelector; + + readonly types: Array; + + constructor(selector: FilterSelector, types: Array) { + super(); + this.selector = selector; + this.types = types; + // TODO: type 'any' should only be used with access path selectors + } + + public toString(): string { + return `${this.selector}:${this.types.length === 1 ? this.types[0] : `{${this.types.join(",")}`}`; + } +} + +export class FilterSelector { + + head: number | "base"; + + props: Array | undefined; + + constructor(head: number | "base", props: Array | undefined) { + this.head = head; + this.props = props; + } + + public toString(): string { + return `${this.head}${this.props ? `.${this.props.join(".")}` : ""}`; + } +} + +export type SimpleType = "undefined" | "boolean" | "string" | "number" | "array" | "empty-array" | "object" | "null" | "function" | "any"; + +export type ValueType = string | number | boolean; + +export class Type { + + readonly simpleType: SimpleType | undefined; // 'any' only for access path selectors + + readonly functionArgs: number | undefined; // only for type "function" + + readonly valueType: ValueType | undefined; + + readonly tsType: string | undefined; // TODO: assume disjoint from String(this.type)? + + constructor(simpleType: SimpleType | undefined, functionArgs: number | undefined, valueType: ValueType | undefined, tsType: string | undefined) { + assert((simpleType !== undefined ? 1 : 0) + (valueType !== undefined ? 1 : 0) + (tsType !== undefined ? 1 : 0) === 1); + assert(!functionArgs || simpleType === "function"); + this.simpleType = simpleType; + this.functionArgs = functionArgs; + this.valueType = valueType; + this.tsType = tsType; + } + + toString(): string { + if (this.simpleType) + return this.simpleType + (this.functionArgs !== undefined ? this.functionArgs : ""); + if (this.tsType) + return this.tsType; + assert(this.valueType !== undefined, "Unexpected type in toString"); + switch (typeof this.valueType) { + case "string": + return `"${this.valueType}"`; + case "number": + case "boolean": + return this.valueType.toString(); + default: + assert.fail(`Unexpected type ${typeof this.valueType}`); + } + } + + static makeSimpleType(simpleType: SimpleType, functionArgs?: number | undefined): Type { + return new Type(simpleType, functionArgs, undefined, undefined) + } + + static makeValueType(valueType: ValueType): Type { + return new Type(undefined, undefined, valueType, undefined); + } + + static makeTSType(tsType: string): Type { + return new Type(undefined, undefined, undefined, tsType); + } +} diff --git a/src/patternmatching/tapirpatterns.ts b/src/patternmatching/tapirpatterns.ts new file mode 100644 index 0000000..bfa38be --- /dev/null +++ b/src/patternmatching/tapirpatterns.ts @@ -0,0 +1,177 @@ +import {Match, PatchType, PatternWrapper, SemanticPatch} from "tapir"; +import {DetectionPattern} from "./patterns"; +import { + convertTapirPatterns, + getGlobs, + getProperties, + loadTapirDetectionPatternFiles, + removeObsoletePatterns +} from "./patternloader"; +import {setDefaultTrackedModules, setPatternProperties} from "../options"; +import {AnalysisState} from "../analysis/analysisstate"; +import {FragmentState} from "../analysis/fragmentstate"; +import {TypeScriptTypeInferrer} from "../typescript/typeinferrer"; +import {AnalysisDiagnostics} from "diagnostics"; +import logger, {writeStdOutIfActive} from "../misc/logger"; +import {sourceLocationToStringWithFileAndEnd, SourceLocationWithFilename} from "../misc/util"; +import {TimeoutException} from "../misc/timer"; +import {DetectionPatternMatch, generateQuestion, PatternMatcher} from "./patternmatcher"; + +/** + * Loads patterns from TAPIR pattern files. + * Also sets default external modules according to globs in patterns (so this function should be run *before* analyzeFiles). + */ +export function tapirLoadPatterns(patternFiles: Array): [Array, Array] { + const tapirPatterns = removeObsoletePatterns(loadTapirDetectionPatternFiles(patternFiles)); + const patterns = convertTapirPatterns(tapirPatterns); + setDefaultTrackedModules(getGlobs(patterns)); + setPatternProperties(getProperties(patterns)); + return [tapirPatterns, patterns]; +} + +/** + * Performs pattern matching on the given analysis state. + * @param tapirPatterns TAPIR patterns + * @param patterns parsed patterns (or 'undefined', in case of parse errors) + * @param analysisState analysis state + * @param fragmentState fragment state + * @param typer TypeScript type inferrer + * @param expected expected matches (optional) + * @param diagnostics analysis diagnostics (optional), for setting aborted in case of timeout + * @return number of matches and misses of different categories + */ +export function tapirPatternMatch(tapirPatterns: Array, patterns: Array, analysisState: AnalysisState, fragmentState: FragmentState, typer?: TypeScriptTypeInferrer, expected?: (PatchType | Match)[], diagnostics?: AnalysisDiagnostics): { + matches: number, + matchesLow: number, + expectedMatches: number, + unexpectedMatches: number, + misses: number, + expectedLow: number, + expectedHigh: number, + unexpectedLow: number, + unexpectedHigh: number, + matchesTapirFalsePositives: number, + missesTapirFalsePositives: number, + missesParseErrors: number, + missesFileNotAnalyzed: number +} { + writeStdOutIfActive("Pattern matching..."); + const matcher = new PatternMatcher(analysisState, fragmentState, typer); + let matches = 0, matchesLow = 0, expectedMatches = 0, unexpectedMatches = 0, + expectedLow = 0, expectedHigh = 0, unexpectedLow = 0, unexpectedHigh = 0, + matchesTapirFalsePositives = 0, missesTapirFalsePositives = 0, + missesParseErrors = 0, missesFileNotAnalyzed = 0; + const expectedRemaining = Array.from(expected || []); + function isTapirFalsePositive(q: PatchType | Match) { + return "questions" in q && q.questions.find(e => e.answer === "no" && !["transformation", "extra", "ask-before-patch"].includes(e.type)); + } + function isHigh(m: DetectionPatternMatch): boolean { + return !m.uncertainties?.length; + } + try { + for (let i = 0; i < patterns.length; i++) { + const tp = tapirPatterns[i]; + const tpId = "semanticPatchId" in tp ? tp.semanticPatchId : tp.id; + const tpPattern = "semanticPatchId" in tp ? tp.semanticPatch.detectionPattern : tp.pattern; + const tpVersion = "version" in tp ? ` (version ${tp.version})` : ""; + const p = patterns[i]; + if (p) { + analysisState.timeoutTimer.checkTimeout(); + const ms = matcher.findDetectionPatternMatches(p); // the set of expressions that match tp and p + for (const m of ms) { + logger.info(`Pattern #${tpId}: ${tpPattern}${tpVersion} matches ${sourceLocationToStringWithFileAndEnd(m.exp.loc)} (confidence: ${isHigh(m) ? "high" : "low"})`); + if (m.uncertainties && m.uncertainties.length > 0) { + for (const u of m.uncertainties) + logger.info(`Uncertainty: ${generateQuestion(u) ?? "Access path match uncertain"}`); + matchesLow++; + } + } + matches += ms.length; + if (expected) + forEachMatch: for (const m of ms) { + for (const q of expected) { + let c1, c2, c3; + // noinspection CommaExpressionJS + if (("classification" in q ? q.classification : q.semanticPatchId) === tpId && (m.exp.loc as SourceLocationWithFilename)?.filename?.endsWith(q.file) && + ("semanticPatchVersion" in q && "version" in tp ? q.semanticPatchVersion === tp.version : true) && + ("lineNumber" in q ? q.lineNumber === m.exp.loc?.start.line : + (c1 = q.loc.indexOf(":"), q.loc.substring(0, c1) === m.exp.loc?.start.line.toString() && + (c2 = q.loc.indexOf(":", c1 + 1), c3 = q.loc.indexOf(":", c2 + 1), q.loc.substring(c2 + 1, c3) === m.exp.loc?.end.line.toString())) +// TODO: Babel parser tab width is apparently hardwired to 1, unfortunately +// q.loc === `${exp.loc?.start.line}:${exp.loc?.start.column}:${exp.loc?.end.line}:${exp.loc?.end.column}` + )) { + let confidence; + if ("highConfidence" in q) { + if (q.highConfidence === isHigh(m)) { + confidence = `expected ${isHigh(m) ? "high" : "low"}`; + if (q.highConfidence) + expectedHigh++; + else + expectedLow++; + } else if (q.highConfidence) { + confidence = "unexpected low"; + unexpectedLow++; + } else { + confidence = "unexpected high"; + unexpectedHigh++; + } + } + const tapirFalsePositive = isTapirFalsePositive(q); + logger.info(`Expected match for pattern #${tpId}${tpVersion} at ${q.file}:${"lineNumber" in q ? q.lineNumber : q.loc}` + + (confidence ? ` (confidence: ${confidence})` : "") + + (tapirFalsePositive ? " (TAPIR false positive)" : "")); + if (tapirFalsePositive) + matchesTapirFalsePositives++; + expectedMatches++; + expectedRemaining.splice(expectedRemaining.indexOf(q), 1); + continue forEachMatch; + } + } + logger.warn(`Unexpected match for pattern #${tpId}${tpVersion} at ${sourceLocationToStringWithFileAndEnd(m.exp.loc)} (confidence: ${isHigh(m) ? "high" : "low"})`); + unexpectedMatches++; + } + } else + logger.info(`Skipping pattern #${tpId}${tpVersion} due to parse error`); + } + } catch (ex) { + if (ex instanceof TimeoutException) { + logger.error("Time limit reached, pattern matching aborted"); + if (diagnostics) + diagnostics.timeout = true; + } else + throw ex; + } + if (analysisState.filesAnalyzed.length === 0) + logger.warn("Zero files analyzed"); + if (expected) + for (const q of expectedRemaining) { + const tapirFalsePositive = isTapirFalsePositive(q); + const id = "classification" in q ? q.classification : q.semanticPatchId; + const version = "semanticPatchVersion" in q ? ` (version ${q.semanticPatchVersion})` : ""; + const fileAnalyzed = analysisState.filesAnalyzed.find(f => f.endsWith(q.file)) !== undefined; + logger.warn(`Missed match for pattern #${id}${version} at ${q.file}:${"lineNumber" in q ? q.lineNumber : q.loc}` + + (tapirFalsePositive ? " (TAPIR false positive)" : "") + + ("highConfidence" in q ? ` (${q.highConfidence ? "high" : "low"} confidence)` : "") + + (fileAnalyzed ? "" : " (file not analyzed)")); + if (tapirFalsePositive) + missesTapirFalsePositives++; + if (analysisState.filesWithParseErrors.find(f => f.endsWith(q.file))) + missesParseErrors++; + if (!fileAnalyzed) + missesFileNotAnalyzed++; + } + logger.info(`Matches: ${matches}${expected ? `, expected: ${expected.length}` : ""} (patterns: ${patterns.length})`); + if (expected) { + logger.info(`Expected matches: ${expectedMatches}, unexpected: ${unexpectedMatches}, misses: ${expectedRemaining.length}`); + logger.info(`Confidence expected low: ${expectedLow}, expected high: ${expectedHigh}, unexpected low: ${unexpectedLow}, unexpected high: ${unexpectedHigh}`); + logger.info(`TAPIR false positives matches: ${matchesTapirFalsePositives}, misses: ${missesTapirFalsePositives}`); + logger.info(`Misses in files with parse errors: ${missesParseErrors}, misses in files not analyzed: ${missesFileNotAnalyzed}`); + } + const misses = expectedRemaining.length; + return { + matches, matchesLow, expectedMatches, unexpectedMatches, misses, + expectedLow, expectedHigh, unexpectedLow, unexpectedHigh, + matchesTapirFalsePositives, missesTapirFalsePositives, + missesParseErrors, missesFileNotAnalyzed + }; +} diff --git a/src/patternmatching/typematcher.ts b/src/patternmatching/typematcher.ts new file mode 100644 index 0000000..3e2a930 --- /dev/null +++ b/src/patternmatching/typematcher.ts @@ -0,0 +1,88 @@ +import {isNode, Node} from "@babel/types"; +import {Type} from "./patterns"; +import {sourceLocationToStringWithFileAndEnd, Ternary, ternaryOr, ternaryToString} from "../misc/util"; +import {followProps, getNumberOfFunctionParams, getSimpleType, getValueType} from "./astpatterns"; +import {TypeScriptTypeInferrer} from "../typescript/typeinferrer"; +import logger from "../misc/logger"; + +/** + * Gets the type of the given node, returns undefined if uncertain. + */ +function getType(n: Node, typer: TypeScriptTypeInferrer | undefined): Type | undefined { + let t1; + const valueType = getValueType(n); + if (valueType !== undefined) + t1 = new Type(undefined, undefined, valueType, undefined); + else { + const simpleType = getSimpleType(n); + if (simpleType !== undefined) + t1 = new Type(simpleType, simpleType === "function" ? getNumberOfFunctionParams(n) : undefined, undefined, undefined); + } + const t2 = typer && n.loc ? typer.convertType(typer.getType(n.loc)) : undefined; + if (t1) { + if (t2) { + if (logger.isDebugEnabled() && t1.toString() !== t2.toString() && + !(t1.simpleType === "function" && t2.simpleType === "function" && t1.functionArgs !== undefined && t2.functionArgs === undefined) && // TODO: ignoring info currently not supported by typeinferrer + !(t1.simpleType === "empty-array" && t2.simpleType === "array")) + logger.debug(`Inferred types differ at ${sourceLocationToStringWithFileAndEnd(n.loc)}: ${t1} <-> ${t2}`); + const m12 = matches(t1, t2); + const m21 = matches(t2, t1); + if (m12 === Ternary.False || m21 === Ternary.False) + logger.warn(`Warning: Incompatible types inferred at ${sourceLocationToStringWithFileAndEnd(n.loc)}: ${t1} <-> ${t2}`); + else if (matches(t2, t1) === Ternary.True) { + if (logger.isDebugEnabled() && t1.toString() !== t2.toString()) + logger.debug(`Choosing TypeScript type ${t2} over ${t1}`); + return t2; + } + } else if (typer) + logger.warn(`Warning: No TypeScript type inferred for ${t1} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + return t1; + } else { + if (logger.isDebugEnabled() && t2 && (t2.simpleType !== undefined || t2.valueType !== undefined)) + logger.debug(`No pattern type inferred for ${t2} at ${sourceLocationToStringWithFileAndEnd(n.loc)}`); + return t2; + } +} + +/** + * Checks whether t1 matches t2. + * Returns True if all t1 values are also t2 values, Maybe if some but not all t1 values are also t2 values, and False if no t1 values are t2 values. + */ +function matches(t1: Type, t2: Type): Ternary { // TODO: "function" and "array" are currently not treated as subtypes of "object" + if (t2.simpleType === "any" || + (t1.simpleType !== undefined && t1.simpleType === t2.simpleType && (t1.simpleType !== "function" || t2.functionArgs === undefined || t1.functionArgs === t2.functionArgs)) || + (t1.valueType !== undefined && t1.valueType === t2.valueType) || + (t1.valueType !== undefined && typeof t1.valueType === t2.simpleType) || + (t1.simpleType === "empty-array" && t2.simpleType === "array") || + (t1.tsType !== undefined && t1.tsType === t2.tsType)) + return Ternary.True; + if (t1.simpleType === "any" || + (t2.simpleType && t2.simpleType === t1.simpleType && (t2.simpleType !== "function" || t1.functionArgs === undefined || t2.functionArgs === t1.functionArgs)) || + (t2.valueType !== undefined && t2.valueType === t1.valueType) || + (t2.valueType !== undefined && typeof t2.valueType === t1.simpleType) || + (t2.simpleType === "empty-array" && t1.simpleType === "array") || + (t2.tsType !== undefined && (t1.tsType !== undefined || t1.simpleType === "object" || t1.simpleType === "array" || t1.simpleType === "function"))) + return Ternary.Maybe; + return Ternary.False; +} + +/** + * Checks whether the given expression matches the given union type. + */ +export function expressionMatchesType(n: Node, props: Array | undefined, ts: Array, typer: TypeScriptTypeInferrer | undefined): Ternary { + const m = followProps(n, props); + if (!isNode(m)) + return m; + const mt = getType(m, typer); + if (!mt) + return Ternary.Maybe; + let res = Ternary.False; + for (const t of ts) { + if (t.tsType && !typer) + logger.error("Error: Pattern uses TypeScript type, but TypeScript type inference is not enabled (see option --typescript)"); + res = ternaryOr(res, matches(mt, t)); + } + if (logger.isDebugEnabled()) + logger.debug(`expressionMatchesType node: ${sourceLocationToStringWithFileAndEnd(n.loc)}, type: ${ts.join(",")}, result: ${ternaryToString(res)}`); + return res; +} diff --git a/src/patternmatching/vulnerabilitydetector.ts b/src/patternmatching/vulnerabilitydetector.ts new file mode 100644 index 0000000..cb465f0 --- /dev/null +++ b/src/patternmatching/vulnerabilitydetector.ts @@ -0,0 +1,389 @@ +import {FunctionInfo, ModuleInfo, PackageInfo} from "../analysis/infos"; +import {OpenSourceVulnerability} from "osv"; +import {gt, gte, lt} from "semver"; +import {Vulnerability} from "vulnerabilities"; +import {NodePath} from "@babel/traverse"; +import {Class, Function, Node} from "@babel/types"; +import { + addAll, + mapGetArray, + mapGetMap, + mapGetSet, + sourceLocationToStringWithFile, + sourceLocationToStringWithFileAndEnd +} from "../misc/util"; +import logger, {writeStdOutIfActive} from "../misc/logger"; +import {AnalysisState} from "../analysis/analysisstate"; +import {DetectionPattern} from "./patterns"; +import {AccessPathPatternCanonicalizer, parseDetectionPattern} from "./patternparser"; +import {FragmentState} from "../analysis/fragmentstate"; +import {TypeScriptTypeInferrer} from "../typescript/typeinferrer"; +import {AnalysisDiagnostics} from "diagnostics"; +import {PatternMatcher} from "./patternmatcher"; +import {TimeoutException} from "../misc/timer"; + +/** + * If a -> b -> vs is in the map, package a depends on package b which has vulnerabilities vs. + */ +export type PackageDependencyVulnerabilities = Map>>; + +/** + * If a -> b -> vs is in the map, module a depends on module b which has vulnerabilities vs. + */ +export type ModuleDependencyVulnerabilities = Map>>; + +/** + * If a -> b -> vs is in the map, function/module a reaches function/module b which has vulnerabilities vs. + */ +export type FunctionReachabilityVulnerabilities = Map>>; + +/** + * If a -> b -> vs is in the map, call site a reaches function/module b which has vulnerabilities vs. + */ +export type CallReachabilityVulnerabilities = Map>>; + +/** + * If a -> b -> vs is in the map, call site a in function b matches a pattern for a vulnerability in vs. + */ +export type PatternMatchVulnerabilities = Map>>; + +export type VulnerabilityResults = { + package?: PackageDependencyVulnerabilities, + module?: ModuleDependencyVulnerabilities, + function?: FunctionReachabilityVulnerabilities, + call?: CallReachabilityVulnerabilities, + matches?: PatternMatchVulnerabilities +} + +/** + * Information about vulnerabilities in analyzed code. + */ +export class VulnerabilityDetector { + + /** + * Collection of vulnerabilities, grouped by package name. + */ + private vulnerabilities: Map> = new Map(); + + private patterns: Map> = new Map(); + + /** + * Map from packages to known vulnerabilities they contain. + */ + vulnerabilityPackageMatches = new Map>(); + + /** + * Map from modules to known vulnerabilities they contain. + */ + vulnerabilityModuleMatches = new Map>(); + + /** + * Map from functions to known vulnerabilities they contain. + */ + vulnerabilityFunctionMatches = new Map>(); + + constructor(vs: Array) { + const c = new AccessPathPatternCanonicalizer() + for (const v of vs) { + if (v.osv.affected) + for (const affected of v.osv.affected) + if (affected.package && affected.package.ecosystem === "npm") + mapGetArray(this.vulnerabilities, affected.package.name).push(v); + if (v.patterns) + for (const p of v.patterns) { + const dp = `call ${p}`; + try { + mapGetArray(this.patterns, v).push(parseDetectionPattern(dp, c)); + } catch (dpos) { + const pos = dpos as number - 5; + logger.error(`Error: Pattern parse error:\n${p} (${v.osv.id})`); + logger.error(`${" ".repeat(pos as number)}^ (column ${pos})`); + } + } + } + } + + getPatterns(): Array { + const res = []; + for (const ps of this.patterns.values()) + for (const p of ps) + res.push(p); + return res; + } + + /** + * Collects vulnerabilities for the given package. + */ + reachedPackage(packageInfo: PackageInfo) { + const vs = this.vulnerabilities.get(packageInfo.name); + if (vs) { + for (const v of vs) + if (this.checkPackageMatchesOpenSourceVulnerability(packageInfo, v.osv)) { + mapGetSet(this.vulnerabilityPackageMatches, packageInfo).add(v); + logger.info(`Detected vulnerable package ${packageInfo} (vulnerability: ${v.osv.id})`); + } + } + } + + /** + * Collects vulnerabilities for the given module. + */ + reachedModule(moduleInfo: ModuleInfo) { + const vs = this.vulnerabilityPackageMatches.get(moduleInfo.packageInfo); + if (vs) { + for (const v of vs) + if (this.checkModuleContainsVulnerabilityLocation(moduleInfo, v.location)) { + mapGetSet(this.vulnerabilityModuleMatches, moduleInfo).add(v); + logger.info(`Detected vulnerable module ${moduleInfo} (vulnerability: ${v.osv.id})`); + if (v.location && !v.location.line && !v.location.code) // if no line or code is given, we assume the whole module is vulnerable + mapGetSet(this.vulnerabilityFunctionMatches, moduleInfo).add(v); + } + } + } + + /** + * Collects vulnerabilities for the given function. + */ + reachedFunction(fun: NodePath, functionInfo: FunctionInfo) { + const vs = this.vulnerabilityModuleMatches.get(functionInfo.moduleInfo); + if (vs) { + for (const v of vs) + if (this.checkFunctionContainsVulnerabilityLocation(fun, v.location)) { + mapGetSet(this.vulnerabilityFunctionMatches, functionInfo).add(v); + logger.info(`Detected vulnerable function ${functionInfo} (vulnerability: ${v.osv.id})`); + } + } + } + + /** + * Finds the packages that may depend on a vulnerable package. + */ + findPackagesThatMayDependOnVulnerablePackages(a: AnalysisState): PackageDependencyVulnerabilities { + const reverseDependencies = new Map>(); + for (const p of a.packageInfos.values()) + for (const d of p.directDependencies) + mapGetSet(reverseDependencies, d).add(p); + return this.propagateBackwards(this.vulnerabilityPackageMatches, reverseDependencies); + } + + /** + * Finds the modules that may depend on a vulnerable module. + */ + findModulesThatMayDependOnVulnerableModules(a: AnalysisState): ModuleDependencyVulnerabilities { + const reverseDependencies = new Map>(); + for (const p of a.packageInfos.values()) + for (const m of p.modules.values()) { + const r = a.requireGraph.get(m); + if (r) + for (const d of r) + mapGetSet(reverseDependencies, d).add(m); + } + return this.propagateBackwards(this.vulnerabilityModuleMatches, reverseDependencies); + } + + /** + * Finds the functions that may reach a vulnerable function (including module top-level functions). + */ + findFunctionsThatMayReachVulnerableFunctions(a: AnalysisState): FunctionReachabilityVulnerabilities { + const reverseDependencies = new Map>(); + function add(d: FunctionInfo | ModuleInfo, f: FunctionInfo | ModuleInfo) { + if (!d.packageInfo.isEntry || !f.packageInfo.isEntry) // stop when reaching entry package + mapGetSet(reverseDependencies, d).add(f); + } + for (const p of a.packageInfos.values()) + for (const m of p.modules.values()) { + const r = a.requireGraph.get(m); + if (r) + for (const d of r) + add(d, m); + for (const f of m.functions.values()) { + const r = a.functionToFunction.get(f); + if (r) + for (const d of r) + add(d, f); + } + const t = a.functionToFunction.get(m); + if (t) + for (const d of t) + add(d, m); + } + return this.propagateBackwards(this.vulnerabilityFunctionMatches, reverseDependencies); + } + + /** + * Finds the call sites that may reach a vulnerable function (including module top-level functions) in a non-entry package. + */ + findCallsThatMayReachVulnerableFunctions(a: AnalysisState, fvs: FunctionReachabilityVulnerabilities): CallReachabilityVulnerabilities { + const res = new Map>>(); + for (const [call, s] of a.callToFunctionOrModule) + for (const f of s) + if (f instanceof FunctionInfo || f instanceof ModuleInfo) + if (!f.packageInfo.isEntry) { // exclude call edges to entry package + const m = fvs.get(f); + if (m) + for (const [dst, vs] of m) + addAll(vs, mapGetSet(mapGetMap(res, call), dst)); + } + return res; + } + + private propagateBackwards(matches: Map>, reverseDependencies: Map>): Map>> { + // initialize work-list + const w = new Map>>(); + for (const [p, vs] of matches) + w.set(p, new Map([[p, new Set(vs)]])); + // propagate backwards + const res = new Map>>(); + for (const [p, m] of w) { + w.delete(p); + for (const [q, vs] of m) { + addAll(vs, mapGetSet(mapGetMap(res, p), q)); + const r = reverseDependencies.get(p); + if (r) + for (const d of r) + for (const v of vs) { + const m = mapGetMap(res, d); + const t = mapGetMap(w, d); + if (!mapGetSet(m, q).has(v)) + mapGetSet(t, q).add(v); + } + } + } + return res; + } + + /** + * Reports the vulnerability reachability results. + */ + reportResults(a: AnalysisState, vulnerabilities: VulnerabilityResults) { + if (vulnerabilities.package) + for (const [src, m] of vulnerabilities.package) + for (const [dst, vs] of m) + if (src !== dst) + for (const v of vs) + if (src.isEntry) + logger.warn(`Vulnerability warning: Main package ${src} depends on vulnerable package ${dst} (vulnerability: ${v.osv.id})`); + else + logger.warn(`Vulnerability warning: Dependency package ${src} depends on vulnerable package ${dst} (vulnerability: ${v.osv.id})`); + if (vulnerabilities.module) + for (const [src, m] of vulnerabilities.module) + for (const [dst, vs] of m) + if (src !== dst) + for (const v of vs) + if (src.isEntry) + logger.warn(`Vulnerability warning: Main module ${src} depends on vulnerable module ${dst} (vulnerability: ${v.osv.id})`); + else + logger.warn(`Vulnerability warning: Dependency module ${src} depends on vulnerable module ${dst} (vulnerability: ${v.osv.id})`); + if (vulnerabilities.function) + for (const [src, m] of vulnerabilities.function) + for (const [dst, vs] of m) + if (src !== dst) + for (const v of vs) + if (src.packageInfo.isEntry) + logger.warn(`Vulnerability warning: Main package ${src instanceof FunctionInfo ? "function" : "module"} ${src} may reach vulnerable function ${dst} (vulnerability: ${v.osv.id})`); + else + logger.warn(`Vulnerability warning: Dependency ${src instanceof FunctionInfo ? "function" : "module"} ${src} may reach vulnerable function ${dst} (vulnerability: ${v.osv.id})`); + if (vulnerabilities.call) + for (const [src, m] of vulnerabilities.call) + for (const [dst, vs] of m) + for (const v of vs) + if (a.callToContainingFunction.get(src)!.packageInfo.isEntry) + logger.warn(`Vulnerability warning: Main package function call site ${sourceLocationToStringWithFile(src.loc)} may reach vulnerable function ${dst} (vulnerability: ${v.osv.id})`); + else + logger.warn(`Vulnerability warning: Dependency function call site ${sourceLocationToStringWithFile(src.loc)} may reach vulnerable function ${dst} (vulnerability: ${v.osv.id})`); + // TODO: also report paths, use SARIF format? + } + + /** + * Checks whether a package matches an OSV entry. + * Based on https://ossf.github.io/osv-schema/#evaluation. + */ + private checkPackageMatchesOpenSourceVulnerability(p: PackageInfo, osv: OpenSourceVulnerability): boolean { + if (osv.affected) + for (const affected of osv.affected) + if (affected.package && affected.package.ecosystem === "npm" && affected.package.name === p.name) { + if (p.version === undefined) + return true; + if (affected.versions) + for (const version of affected.versions) + if (version === p.version) + return true; + if (affected.ranges) { + for (const range of affected.ranges) { + let beforeLimits = false; + if (!range.events.find(event => "limit" in event)) + beforeLimits = true; + else + for (const event of range.events) + if (event.limit && (event.limit === "*" || lt(p.version, event.limit))) { + beforeLimits = true; + break; + } + if (beforeLimits) { + let match = false; + for (const event of range.events) { // TODO: assuming already sorted - OK? + if (event.introduced && (event.introduced === "0" || gte(p.version, event.introduced))) + match = true; + else if (event.fixed && gte(p.version, event.fixed)) + match = false; + else if (event.last_affected && gt(p.version, event.last_affected)) + match = false; + } + if (match) + return true; + } + } + } + } + return false; + } + + /** + * Checks if the location is likely in the module. + */ + private checkModuleContainsVulnerabilityLocation(moduleInfo: ModuleInfo, loc: Vulnerability["location"]): boolean { + return Boolean(loc && loc.file === moduleInfo.relativePath); + } + + /** + * Checks if the location is likely in the function (or one of its inner functions). + * It is assumed that the function is in the module. + */ + private checkFunctionContainsVulnerabilityLocation(fun: NodePath, loc: Vulnerability["location"]): boolean { + if (!(loc && loc.file && loc.line && loc.code)) + return false; + const source = fun.getSource(); + const firstMatch = source.indexOf(loc.code); + if (firstMatch === -1) + return false; + const multipleMatches = source.indexOf(loc.code, firstMatch + 1) !== -1; + if (!multipleMatches) + return true; + return Boolean(fun.node.loc?.start.line && fun.node.loc?.end.line && fun.node.loc?.start.line <= loc.line + 1 && loc.line <= fun.node.loc?.end.line + 1); + } + + /** + * Performs pattern matching on the given analysis state. + */ + patternMatch(analysisState: AnalysisState, fragmentState: FragmentState, typer?: TypeScriptTypeInferrer, diagnostics?: AnalysisDiagnostics): PatternMatchVulnerabilities { + const res = new Map>>(); + writeStdOutIfActive("Pattern matching..."); + try { + const matcher = new PatternMatcher(analysisState, fragmentState, typer); + for (const [v, ps] of this.patterns) + for (const p of ps) + for (const m of matcher.findDetectionPatternMatches(p)) { // TODO: special mode for findDetectionPatternMatches to ignore certainty information? + logger.info(`Detected call to vulnerable function: ${p} at ${sourceLocationToStringWithFileAndEnd(m.exp.loc)} (vulnerability: ${v.osv.id})`); + mapGetSet(mapGetMap(res, m.exp), analysisState.callToContainingFunction.get(m.exp)).add(v); + } + } catch (ex) { + if (ex instanceof TimeoutException) { + logger.error("Time limit reached, pattern matching aborted"); + if (diagnostics) + diagnostics.timeout = true; + } else + throw ex; + } + return res; + } +} \ No newline at end of file diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..2f1c11c --- /dev/null +++ b/src/server.ts @@ -0,0 +1,399 @@ +#!/usr/bin/env node + +import * as readline from "readline"; +import {logToFile, setLogLevel} from "./misc/logger"; +import { + AbortRequest, + AnalyzeRequest, + ApiUsageRequest, + ApiUsageResponse, + CallGraphRequest, + CallGraphResponse, + ClearRequest, + DiagnosticsRequest, + DiagnosticsResponse, + ExpandPathsRequest, + ExpandPathsResponse, + FilesRequest, + HTMLCallGraphRequest, + HTMLDataFlowGraphRequest, + Message, + OptionsRequest, + PatternFilesRequest, + PatternMatchRequest, + PatternMatchResponse, + PatternsRequest, + ReachablePackagesRequest, + ReachablePackagesResponse, + Request, + RequestCommands, + ResetRequest, + Response, + TSLibraryUsageRequest, + TSLibraryUsageResponse, + TypeScriptRequest +} from "ipc"; +import {options, resetOptions, setDefaultTrackedModules, setOptions, setPatternProperties} from "./options"; +import {autoDetectBaseDir, expand} from "./misc/files"; +import {analyzeFiles} from "./analysis/analyzer"; +import {TypeScriptTypeInferrer} from "./typescript/typeinferrer"; +import {PatternWrapper, SemanticPatch} from "tapir"; +import {DetectionPattern} from "./patternmatching/patterns"; +import {convertTapirPatterns, getGlobs, getProperties, loadTapirDetectionPatternFiles, removeObsoletePatterns} from "./patternmatching/patternloader"; +import {convertPatternMatchesToJSON, PatternMatcher} from "./patternmatching/patternmatcher"; +import {convertAPIUsageToJSON, getAPIUsage} from "./patternmatching/apiusage"; +import Solver, {AbortedException} from "./analysis/solver"; +import {program} from "commander"; +import winston from "winston"; +import {tmpdir} from "os"; +import {AnalysisStateReporter} from "./output/analysisstatereporter"; +import {exportCallGraphHtml, exportDataFlowGraphHtml} from "./output/visualizer"; +import {VulnerabilityDetector, VulnerabilityResults} from "./patternmatching/vulnerabilitydetector"; +import {readFileSync} from "fs"; +import {Vulnerability} from "vulnerabilities"; +import {addAll} from "./misc/util"; + +const VERSION = require("../package.json").version; + +program + .name("jelly-server") + .version(VERSION) + .addHelpText("before", "Copyright (C) 2023 Anders Møller\n") + .option("-f, --logfile ", "log file (default: $TMP/jelly-PID.log)") + .option("-l, --loglevel ", "analysis log level (info/warn/error)", "error") + .option("-r, --loglevel-server ", "server log level (verbose/info/error)", "info") + .action(main) + .showHelpAfterError() + .parse(); + +async function main() { + setOptions(program.opts()); + options.logfile ??= `${tmpdir()}/jelly-${process.pid}.log`; + logToFile(options.logfile); + if (options.loglevel === "debug" || options.loglevel === "verbose") + options.loglevel = "info"; // prevent internal analysis log messages in server mode + setLogLevel(options.loglevel); + const logger = winston.createLogger({ + level: options.loglevelServer, + format: winston.format.combine( + winston.format.timestamp(), + winston.format.printf(({level, message, timestamp}) => + `${timestamp} [${level}]: ${message}`) + ), + transports: new winston.transports.File({ + filename: options.logfile + }) + }); + + logger.info(`Starting server, analysis log level: ${options.loglevel}, server log level: ${options.loglevelServer}`); + + type RequestHandlers = { [Property in RequestCommands]: (req?: Request) => Promise }; + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false + }); + + rl.on("line", async (input: string) => { + const trimmed = input.trim(); + if (logger.isVerboseEnabled()) + logger.verbose(`Message received: ${trimmed}`); + try { + const message = JSON.parse(trimmed) as Message; + if (message.type === "request") { + const req = message as Request; + const handler = (requestHandlers as RequestHandlers)[req.command]; + if (handler) { + try { + const res = await handler(req); + if (res) + sendResponse(res); + } catch (err) { + sendErrorResponse((err as any)?.message, req); + } + } else + sendErrorResponse(`Unrecognized command: ${req.command}`, req); + } else + sendErrorResponse(`Unexpected message type: ${message.type}`); + } catch (err) { + sendErrorResponse("Unable to parse request"); + } + }); + + rl.on("close", () => { + logger.info("Connection closed, shutting down"); + process.exit(0); + }); + + process.on("SIGINT", () => { + logger.info("Received SIGINT, shutting down"); + process.exit(0); + }); + + let seq = 1; + + function prepareResponse(success: S, req: Request | undefined, extra: {message?: M, body?: B} = {}): Response & {success: S, message: typeof extra.message, body: typeof extra.body} { + return { + type: "response", + seq: seq++, + command: req?.command, + request_seq: req?.seq, + success, + message: extra.message, + body: extra.body + }; + } + + function sendResponse(res: Response) { + const str = JSON.stringify(res); + process.stdout.write(`Content-Length: ${str.length}\r\n\r\n`); + process.stdout.write(str); + process.stdout.write("\r\n"); + if (logger.isVerboseEnabled()) + logger.verbose(`Message sent: ${JSON.stringify(res, undefined, 2)}`); + } + + function sendErrorResponse(message: string, req?: Request) { + logger.error(message); + sendResponse(prepareResponse(false, req, {message})); + } + + let analyzing: boolean = false; + let aborting: boolean = false; + let files: Array | undefined; + let solver: Solver | undefined; + let typer: TypeScriptTypeInferrer | undefined; + let tapirPatterns: Array | undefined; + let patterns: Array | undefined; + let globs: Set | undefined; + let props: Set | undefined; + let vulnerabilityDetector: VulnerabilityDetector | undefined; + + function clearAnalysisData() { + files = solver = typer = tapirPatterns = patterns = globs = props = undefined; + if (typeof gc === "function") + gc(); + } + + const requestHandlers = { + + exit: async () => { + logger.info("Received exit command, shutting down"); + process.exit(0); + }, + + options: async (req: OptionsRequest) => { + setOptions(req.arguments); + setLogLevel(options.loglevel); + if (options.vulnerabilities) { // TODO: support loading from network? + logger.info(`Loading vulnerability patterns from ${options.vulnerabilities}`); + vulnerabilityDetector = new VulnerabilityDetector(JSON.parse(readFileSync(options.vulnerabilities, "utf8")) as Array); // TODO: use when setting globs and props? (see also main.ts) + } + logger.info("Options set"); + return prepareResponse(true, req); + }, + + expandpaths: async (req: ExpandPathsRequest) => { + const body = expand(req.arguments); + const res: ExpandPathsResponse = prepareResponse(true, req, {body}); + logger.info("Expanded paths"); + return res; + }, + + files: async (req: FilesRequest) => { + if (analyzing) + return prepareResponse(false, req, {message: "Analysis in progress"}); + files = req.arguments; + autoDetectBaseDir(files); + logger.info("Files selected"); + return prepareResponse(true, req); + }, + + analyze: async (req: AnalyzeRequest) => { + if (!files) + return prepareResponse(false, req, {message: "Files have not been selected"}); + if (analyzing) + return prepareResponse(false, req, {message: "Analysis already in progress"}); + solver = undefined; + let gs, ps; + if (globs || vulnerabilityDetector) { + gs = new Set() + addAll(globs, gs); + ps = new Set(); + addAll(props, ps); + if (vulnerabilityDetector) { + const qs = vulnerabilityDetector.getPatterns(); + addAll(getGlobs(qs), gs); + addAll(getProperties(qs), ps); + } + } + setDefaultTrackedModules(gs); + setPatternProperties(options.apiUsage ? undefined : (ps || new Set)); + analyzing = true; + aborting = false; + logger.info("Starting analysis"); + try { + solver = new Solver(() => aborting); + await analyzeFiles(files, solver); + logger.info(`Analysis completed${solver.diagnostics.aborted ? " (aborted)" : solver.diagnostics.timeout ? " (timeout)" : ""}`); + return prepareResponse(true, req); + } catch (ex) { + logger.info("Analysis terminated unsuccessfully"); + if (ex instanceof AbortedException) + return prepareResponse(false, req, {message: "Analysis was aborted"}); + throw ex; + } finally { + analyzing = aborting = false; + } + }, + + abort: async (req: AbortRequest) => { + if (!analyzing) + return prepareResponse(false, req, {message: "Analysis not currently running"}); + logger.info("Aborting analysis"); + aborting = true; + return prepareResponse(true, req); + }, + + clear: async (req: ClearRequest) => { + if (analyzing) + return prepareResponse(false, req, {message: "Analysis in progress"}); + clearAnalysisData(); + logger.info("Analysis data cleared"); + return prepareResponse(true, req); + }, + + reset: async (req: ResetRequest) => { + if (analyzing) + return prepareResponse(false, req, {message: "Analysis in progress"}); + clearAnalysisData(); + resetOptions(); + logger.info("Reset completed"); + return prepareResponse(true, req); + }, + + typescript: async (req: TypeScriptRequest) => { + if (!files) + return prepareResponse(false, req, {message: "No files selected"}); + typer = new TypeScriptTypeInferrer(files); + logger.info("TypeScript parsing done"); + return prepareResponse(true, req); + }, + + diagnostics: async (req: DiagnosticsRequest) => { + if (!solver) + return prepareResponse(false, req, {message: "Analysis results not available"}); + solver.updateDiagnostics(); + const res: DiagnosticsResponse = prepareResponse(true, req, {body: solver.diagnostics}); + logger.info("Sending analysis diagnostics"); + return res; + }, + + apiusage: async (req: ApiUsageRequest) => { + if (!solver || analyzing) + return prepareResponse(false, req, {message: "Analysis results not available"}); + if (!options.apiUsage) + return prepareResponse(false, req, {message: "API usage not enabled, must be enabled before analyze"}); + const [r1,] = getAPIUsage(solver.analysisState); + const body = convertAPIUsageToJSON(r1); + const res: ApiUsageResponse = prepareResponse(true, req, {body}); + logger.info("Sending API usage"); + return res; + }, + + patternfiles: async (req: PatternFilesRequest) => { + if (analyzing) + return prepareResponse(false, req, {message: "Analysis in progress"}); + tapirPatterns = removeObsoletePatterns(loadTapirDetectionPatternFiles(req.arguments)); + patterns = convertTapirPatterns(tapirPatterns); + globs = getGlobs(patterns); + props = getProperties(patterns); + logger.info("Patterns loaded from files"); + return prepareResponse(true, req); + }, + + patterns: async (req: PatternsRequest) => { + if (analyzing) + return prepareResponse(false, req, {message: "Analysis in progress"}); + tapirPatterns = removeObsoletePatterns(req.arguments); + patterns = convertTapirPatterns(tapirPatterns); + globs = getGlobs(patterns); + props = getProperties(patterns); + logger.info("Patterns loaded"); + return prepareResponse(true, req); + }, + + patternmatch: async (req: PatternMatchRequest) => { + if (!solver || analyzing) + return prepareResponse(false, req, {message: "Analysis results not available"}); + if (!tapirPatterns || !patterns) + return prepareResponse(false, req, {message: "Patterns have not been loaded"}); + const matcher = new PatternMatcher(solver.analysisState, solver.fragmentState, typer); + const body = convertPatternMatchesToJSON(patterns, matcher); + const res: PatternMatchResponse = prepareResponse(true, req, {body}); + logger.info("Sending pattern matching results"); + return res; + }, + + callgraph: async (req: CallGraphRequest) => { + if (!solver || !files) + return prepareResponse(false, req, {message: "Analysis results not available"}); + const res: CallGraphResponse = prepareResponse(true, req, {body: new AnalysisStateReporter(solver.analysisState, solver.fragmentState).callGraphToJSON(files)}); + logger.info("Sending call graph"); + return res; + }, + + htmlcallgraph: async (req: HTMLCallGraphRequest) => { + if (!solver || !files) + return prepareResponse(false, req, {message: "Analysis results not available"}); + if (!options.callgraphHtml) + return prepareResponse(false, req, {message: "Option callgraphHtml not set"}); + let vr: VulnerabilityResults = {}; + if (vulnerabilityDetector && options.vulnerabilities) { + const a = solver.analysisState, f = solver.fragmentState; + vr.package = vulnerabilityDetector.findPackagesThatMayDependOnVulnerablePackages(a); + vr.module = vulnerabilityDetector.findModulesThatMayDependOnVulnerableModules(a); + vr.function = vulnerabilityDetector.findFunctionsThatMayReachVulnerableFunctions(a); + vr.call = vulnerabilityDetector.findCallsThatMayReachVulnerableFunctions(a, vr.function); + vr.matches = vulnerabilityDetector.patternMatch(a, f, typer, solver.diagnostics) + // TODO: include vulnerability pattern match reachability like in main.ts + } + exportCallGraphHtml(solver.analysisState, options.callgraphHtml, vr); + logger.info("Call graph HTML file generated"); + return prepareResponse(true, req); + }, + + htmldataflowgraph: async (req: HTMLDataFlowGraphRequest) => { + if (!solver || !files) + return prepareResponse(false, req, {message: "Analysis results not available"}); + if (!options.dataflowHtml) + return prepareResponse(false, req, {message: "Option dataflowHtml not set"}); + exportDataFlowGraphHtml(solver.analysisState, solver.fragmentState, options.dataflowHtml); + logger.info("Data-flow graph HTML file generated"); + return prepareResponse(true, req); + }, + + tslibraryusage: async (req: TSLibraryUsageRequest) => { + if (!typer) + return prepareResponse(false, req, {message: "TypeScript parsing result not available"}); + const res: TSLibraryUsageResponse = prepareResponse(true, req, {body: typer.libraryUsageToJSON(typer.getLibraryUsage())}); + logger.info("Sending TypeScript library usage"); + return res; + }, + + reachablepackages: async (req: ReachablePackagesRequest) => { + if (!solver) + return prepareResponse(false, req, {message: "Analysis results not available"}); + const packages: ReachablePackagesResponse["body"] = []; + for (const p of solver.analysisState.packageInfos.values()) + packages.push({ + name: p.name, + version: p.version + }); + const res: ReachablePackagesResponse = prepareResponse(true, req, {body: packages}); + logger.info("Sending reachable packages"); + return res; + }, + }; +} \ No newline at end of file diff --git a/src/testing/fetch.ts b/src/testing/fetch.ts new file mode 100644 index 0000000..87fc8fb --- /dev/null +++ b/src/testing/fetch.ts @@ -0,0 +1,31 @@ +// fetches repos for call graph experiments + +import fs from "fs"; +import {execSync} from "child_process"; + +const reposFile = process.argv[2]; +const installDir = process.argv[3]; +if (!reposFile || !installDir) { + console.error("Error: Missing argument"); + process.exit(-1); +} + +fs.mkdirSync(installDir, { recursive: true }); +console.log(`Reading ${reposFile}`); +const j = JSON.parse(fs.readFileSync(reposFile, "utf8")) as Array<{github_repo: string, branch: string}>; +let i = 0; +for (const entry of j) { + try { + if (!entry.github_repo || !entry.branch) { + console.error(`github_repo or branch not found in ${reposFile}`); + process.exit(-1); + } + console.log(`Installing (${++i}/${j.length}) ${entry.github_repo} in ${installDir}`); + execSync(`git clone --depth 1 --branch ${entry.branch} https://github.com/${entry.github_repo} ${entry.github_repo} -q`, + { cwd: installDir, encoding: "utf8", stdio: "inherit" }); + execSync(`npm install --ignore-scripts`, + { cwd: `${installDir}/${entry.github_repo}`, encoding: "utf8", stdio: "inherit" }); + } catch (e) { + console.error(e); + } +} diff --git a/src/testing/runner.ts b/src/testing/runner.ts new file mode 100644 index 0000000..15923ee --- /dev/null +++ b/src/testing/runner.ts @@ -0,0 +1,48 @@ +// runs Jelly on all packages listed as dependencies in a given package.json file + +// usage example: +// NODE_OPTIONS=--max-old-space-size=4096 node lib/testing/runner.js ../jelly-benchmarks/data/top100/package.json tmp/top100-results.json 60 ignoreDependencies + +import fs, {readFileSync} from "fs"; +import {options} from "../options"; +import {analyzeFiles} from "../analysis/analyzer"; +import {dirname} from "path"; +import {expand, writeStreamedStringify} from "../misc/files"; +import logger, {setLogLevel} from "../misc/logger"; +import {AnalysisDiagnostics} from "diagnostics"; +import {AnalysisStateReporter} from "../output/analysisstatereporter"; +import Solver from "../analysis/solver"; + +const jsonFile = process.argv[2]; +const outFile = process.argv[3]; +const timeout = Number(process.argv[4]); +const ignoreDependencies = process.argv[5] === "ignoreDependencies"; +if (!jsonFile || !outFile || !timeout) { + console.error("Error: Missing argument"); + process.exit(-1); +} + +(async function() { + const f = JSON.parse(readFileSync(jsonFile, {encoding: "utf8"})); + let count = 0; + const packages = Object.keys(f.dependencies); + const results: { [index: string]: AnalysisDiagnostics } = {}; + for (const d of packages) { + setLogLevel("info"); + logger.info(`Analyzing package ${d} (${++count}/${packages.length})`); + options.basedir = dirname(jsonFile); + options.ignoreDependencies = ignoreDependencies; + options.tty = false; + options.warningsUnsupported = false; + options.timeout = timeout; + setLogLevel("warn"); + const solver = new Solver(); + await analyzeFiles(expand([`${options.basedir}/node_modules/${d}`]), solver); + results[d] = solver.diagnostics; + setLogLevel("verbose"); + new AnalysisStateReporter(solver.analysisState, solver.fragmentState).reportLargestTokenSets(); + } + const fd = fs.openSync(outFile, "w"); + writeStreamedStringify(results, fd, undefined, 2); + fs.closeSync(fd); +})(); \ No newline at end of file diff --git a/src/testing/runtest.ts b/src/testing/runtest.ts new file mode 100644 index 0000000..d5dbe82 --- /dev/null +++ b/src/testing/runtest.ts @@ -0,0 +1,88 @@ +import {options} from "../options"; +import {tapirLoadPatterns, tapirPatternMatch} from "../patternmatching/tapirpatterns"; +import {analyzeFiles} from "../analysis/analyzer"; +import assert from "assert"; +import {testSoundness} from "../dynamic/soundnesstester"; +import {AnalysisStateReporter} from "../output/analysisstatereporter"; +import Solver from "../analysis/solver"; +import {AnalysisState} from "../analysis/analysisstate"; +import {getAPIUsage} from "../patternmatching/apiusage"; + +export async function runTest(basedir: string, + app: string | Array, + args: { + soundness?: string, + patterns?: Array, + matches?: {total: number, low?: number}, + functionInfos?: number, + moduleInfos?: number, + numberOfFunctionToFunctionEdges?: number, + oneCalleeCalls?: number, + funFound?: number, + funTotal?: number, + callFound?: number, + callTotal?: number, + apiUsageAccessPathPatternsAtNodes?: number + }) { + options.basedir = basedir; + options.patterns = args.patterns; + options.soundness = args.soundness; + + let tapirPatterns, detectionPatterns; + if (args.patterns) + [tapirPatterns, detectionPatterns] = tapirLoadPatterns(args.patterns); + + if (args.apiUsageAccessPathPatternsAtNodes !== undefined) { + options.apiUsage = options.ignoreDependencies = true; + options.trackedModules ??= ['**']; + } + + const solver = new Solver(); + await analyzeFiles(Array.isArray(app) ? app : [app], solver); + + let funFound, funTotal, callFound, callTotal; + if (args.soundness) + [funFound, funTotal, callFound, callTotal] = testSoundness(args.soundness, solver.analysisState); + + if (args.functionInfos !== undefined) + expect(solver.analysisState.functionInfos.size).toBe(args.functionInfos); + if (args.moduleInfos !== undefined) + expect(solver.analysisState.moduleInfos.size).toBe(args.moduleInfos); + if (args.numberOfFunctionToFunctionEdges !== undefined) + expect(solver.analysisState.numberOfFunctionToFunctionEdges).toBe(args.numberOfFunctionToFunctionEdges); + if (args.oneCalleeCalls !== undefined) + expect(new AnalysisStateReporter(solver.analysisState, solver.fragmentState).getOneCalleeCalls()).toBe(args.oneCalleeCalls); + if (args.funFound !== undefined) + expect(funFound).toBe(args.funFound); + if (args.funTotal !== undefined) + expect(funTotal).toBe(args.funTotal); + if (args.callFound !== undefined) + expect(callFound).toBe(args.callFound); + if (args.callTotal !== undefined) + expect(callTotal).toBe(args.callTotal); + if (args.matches) { + assert(tapirPatterns !== undefined && detectionPatterns !== undefined); + const {matches, matchesLow} = tapirPatternMatch(tapirPatterns, detectionPatterns, solver.analysisState, solver.fragmentState); + expect(matches).toBe(args.matches.total); + if (args.matches.low !== undefined) + expect(matchesLow).toBe(args.matches.low); + } + if (args.apiUsageAccessPathPatternsAtNodes !== undefined) { + const [r1,] = getAPIUsage(solver.analysisState); + let numAccessPathPatternsAtNodes = 0; + for (const m of Object.values(r1)) + for (const ns of m.values()) + numAccessPathPatternsAtNodes += ns.size; + expect(numAccessPathPatternsAtNodes).toBe(args.apiUsageAccessPathPatternsAtNodes); + } +} + +export function hasEdge(a: AnalysisState, fromStr: string, toStr: string): boolean { + for (const [from, tos] of a.functionToFunction) + for (const to of tos) { + // console.log(`${from} -> ${to}`); + if (from.toString().includes(fromStr) && to.toString().includes(toStr)) + return true; + } + return false; +} diff --git a/src/typescript/moduleresolver.ts b/src/typescript/moduleresolver.ts new file mode 100644 index 0000000..ac32eb2 --- /dev/null +++ b/src/typescript/moduleresolver.ts @@ -0,0 +1,27 @@ +import * as ts from "typescript"; +import {FilePath} from "../misc/util"; + +const options = { // TODO: get options from tsconfig.json if available? (see typeinferrer.ts) + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.CommonJS, + allowJs: true, + checkJs: true, + noDtsResolution: true // if not enabled, .d.ts files take priority over .js files +}; // TODO: set typeRoots to options.basedir? + +const host = ts.createCompilerHost(options); + +/** + * Resolves a module name using the TypeScript compiler. + * @param str module name + * @param file current file path + * @return resolved file path if successful + * @throws exception if the module is not found + */ +export function tsResolveModuleName(str: string, file: FilePath): FilePath { + const t = str.endsWith(".ts") ? str.substring(0, str.length - 3) : str; + const filepath = ts.resolveModuleName(t, file, options, host).resolvedModule?.resolvedFileName; + if (!filepath) + throw new Error; + return filepath; +} diff --git a/src/typescript/typeinferrer.ts b/src/typescript/typeinferrer.ts new file mode 100644 index 0000000..805c047 --- /dev/null +++ b/src/typescript/typeinferrer.ts @@ -0,0 +1,234 @@ +import * as ts from 'typescript'; +import {SymbolFlags, TypeFlags} from 'typescript'; +import {SourceLocation} from "@babel/types"; +import logger, {writeStdOutIfActive} from "../misc/logger"; +import {FilePath, mapArrayAdd, SourceLocationJSON, SourceLocationsToJSON, sourceLocationToStringWithFileAndEnd, SourceLocationWithFilename} from "../misc/util"; +import {dirname, resolve} from "path"; +import Timer from "../misc/timer"; +import {Type} from "../patternmatching/patterns"; +import {existsSync, readFileSync} from "fs"; +import {options} from "../options"; + +/** + * Map from package name to locations of TypeScript AST nodes with a type from that package. + */ +export type LibraryUsage = Map>; + +export type LibraryUsageJSON = { + files: Array, + packages: Record> +}; + +/** + * Infers types using TypeScript. + */ +export class TypeScriptTypeInferrer { + + readonly program: ts.Program; + + readonly checker: ts.TypeChecker; + + readonly files = new Map(); + + readonly timer = new Timer(); // TODO: include time for calls to getTypeAtLocation and getLibraryUsage? + + /** + * Parses and infers types for the given files (and reachable files). + */ + constructor(files: string[]) { + writeStdOutIfActive("Parsing as TypeScript..."); + logger.info("Parsing as TypeScript"); + const tsconfig = ts.findConfigFile(options.basedir, ts.sys.fileExists); + const inside = tsconfig && tsconfig.startsWith(options.basedir); + if (tsconfig && inside) + logger.verbose(`Using ${tsconfig}`); + this.program = tsconfig && inside ? this.createProgram(tsconfig) : + ts.createProgram(files.map(f => resolve(options.basedir, f)), { // TODO: use typeAcquisition? + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.CommonJS, + allowJs: true, + checkJs: true + }); + this.checker = this.program.getTypeChecker(); + if (logger.isDebugEnabled()) + logger.debug("Parsed files (excluding declaration files):"); + for (const file of this.program.getSourceFiles()) + if (!file.isDeclarationFile) { + this.files.set(file.fileName, file); + logger.debug(file.fileName); + } + logger.info(`TypeScript parsing time: ${this.timer.elapsed()}ms, files: ${this.program.getSourceFiles().length}`); + } + + private createProgram(tsconfigPath: string): ts.Program { + const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); + const configHostParser: ts.ParseConfigHost = { + fileExists: existsSync, + readDirectory: ts.sys.readDirectory, + readFile: file => readFileSync(file, 'utf8'), + useCaseSensitiveFileNames: process.platform === 'linux' + }; + const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), {noEmit: true}); + const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); + // TODO: set default typeRoots to options.basedir? + return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); + } + + /** + * Returns the TypeScript type for the given TypeScript node, or undefined if not found. + */ + getTypeAtTSNode(node: ts.Node, loc: SourceLocation | string): ts.Type | undefined { + try { + return this.checker.getTypeAtLocation(node); + } catch (e) { + logger.error(`TypeScript internal error while querying type at ${typeof loc === "string" ? loc : sourceLocationToStringWithFileAndEnd(loc)}: ${e}`); + return undefined; + } + } + + /** + * Returns the TypeScript type for the given location (with filename), or undefined if not found. + */ + getType(loc: SourceLocationWithFilename | SourceLocation): ts.Type | undefined { + const file = "filename" in loc && this.files.get(loc.filename); + if (!file) + return undefined; + try { + const start = file.getPositionOfLineAndCharacter(loc.start.line - 1, loc.start.column); + const end = file.getPositionOfLineAndCharacter(loc.end.line - 1, loc.end.column); + let node: ts.Node = (ts as any).getTokenAtPosition(file, start); + while (node && node.end < end) + node = node.parent; + if (!node) + return undefined; + const posLineChar = file.getLineAndCharacterOfPosition(node.getStart()); + const endLineChar = file.getLineAndCharacterOfPosition(node.getEnd()); + if (logger.isVerboseEnabled() && + posLineChar.line + 1 !== loc.start.line || + posLineChar.character !== loc.start.column || + endLineChar.line + 1 !== loc.end.line || + endLineChar.character !== loc.end.column) + logger.verbose(`TypeScript AST node misaligned: ${file.fileName}:${posLineChar.line + 1}:${posLineChar.character + 1}:${endLineChar.line + 1}:${endLineChar.character + 1}, expected ${sourceLocationToStringWithFileAndEnd(loc)}`); // FIXME: no longer relevant? + const type = this.getTypeAtTSNode(node, loc); + if (logger.isDebugEnabled()) + logger.debug(`TypeScript type at ${sourceLocationToStringWithFileAndEnd(loc)}: ${type ? this.checker.typeToString(type) : "???"}`); + return type; + } catch { + return undefined; + } + } + + /** + * Converts a TypeScript type to a pattern type, or returns undefined if unable. + */ + convertType(type: ts.Type | undefined): Type | undefined { + if (!type) + return undefined; + if (type.flags & TypeFlags.Any && !(type.symbol && type.symbol.flags & SymbolFlags.Transient)) // transient represents unresolved types + return Type.makeSimpleType("any"); + if (type.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) + return Type.makeSimpleType("string"); + if (type.flags & TypeFlags.Number) + return Type.makeSimpleType("number"); + if (type.flags & TypeFlags.Boolean) + return Type.makeSimpleType("boolean"); + if (type.flags & TypeFlags.Undefined) + return Type.makeSimpleType("undefined"); + if (type.flags & TypeFlags.Null) + return Type.makeSimpleType("null"); + if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) + return Type.makeValueType((type as any).value); + if (type.flags & TypeFlags.BooleanLiteral) { + const intrinsicName = (type as any).intrinsicName; + if (intrinsicName === "true") + return Type.makeValueType(true); + if (intrinsicName === "false") + return Type.makeValueType(false); + } + if (type.flags & TypeFlags.Object && type.symbol) { + if (type.symbol.escapedName === "Array") + return Type.makeSimpleType("array"); // TODO: detect empty-array? + if (type.symbol.escapedName === "__object") + return Type.makeSimpleType("object"); + if (type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) + return Type.makeSimpleType("function", undefined); // TODO: detect function arity + if (type.symbol.flags & (SymbolFlags.Interface | SymbolFlags.Class | SymbolFlags.RegularEnum)) + return Type.makeTSType(type.symbol.escapedName as string); + } + if (type.aliasSymbol) + return Type.makeTSType(type.aliasSymbol.escapedName as string); + // TODO: TypeFlags.Union, TypeFlags.Intersection ... ? + return undefined; + } + + /** + * Returns a library usage map. + */ + getLibraryUsage(): LibraryUsage { + const res: LibraryUsage = new Map(); + const program = this.program; + const checker = this.checker; + for (const file of this.files.values()) { + function visit(node: ts.Node) { + function getLoc(start: ts.LineAndCharacter, end: ts.LineAndCharacter): string { + return `${file.fileName}:${start.line + 1}:${start.character + 1}:${end.line + 1}:${end.character + 1}`; + } + function visitType(type: ts.Type) { + if (type.isUnionOrIntersection()) { // FIXME: how to handle union/intersection types properly? other relevant kinds of composite types? + for (const t of type.types) + visitType(t); + } else if (!(type.flags & TypeFlags.Any)) { + const declFile = type.symbol?.getDeclarations()?.[0]?.getSourceFile().fileName; + if (declFile) { + const packageFile = (program as any).sourceFileToPackageName.get(declFile) as string; + if (packageFile) { + const i1 = packageFile.indexOf("/"); + const i2 = packageFile.startsWith("@") ? packageFile.indexOf("/", i1 + 1) : i1; + const packageName = packageFile.substring(0, i2); + const start = file.getLineAndCharacterOfPosition(node.getStart()); + const end = file.getLineAndCharacterOfPosition(node.getEnd()); + mapArrayAdd(packageName, [{ + filename: file.fileName, + start: {line: start.line + 1, column: start.character}, + end: {line: end.line + 1, column: end.character} + }, + type], + res); + if (logger.isVerboseEnabled()) + logger.verbose(`${getLoc(start, end)}: ${checker.typeToString(type)} in ${packageName}, type.flags=${type.flags}, type.symbol.flags=${type.symbol?.flags}`); + return; + } + } + // XXX + // if (logger.isVerboseEnabled()) { + // const start = file.getLineAndCharacterOfPosition(node.getStart()); + // const end = file.getLineAndCharacterOfPosition(node.getEnd()); + // logger.info(`No library usage found at ${getLoc(start, end)}: ${checker.typeToString(type)}, type.flags=${type.flags}, type.symbol.flags=${type.symbol?.flags}`); + // } + } + } + try { + visitType(checker.getTypeAtLocation(node)); + } catch (e) { + logger.warn(`TypeScript internal error while querying type: ${e}`); + //console.error(e); // XXX + } + ts.forEachChild(node, visit); + } + ts.forEachChild(file, visit); + } + return res; + } + + libraryUsageToJSON(u: LibraryUsage): LibraryUsageJSON { + const res: LibraryUsageJSON = {files: [], packages: {}}; + const locs = new SourceLocationsToJSON(res.files); + for (const [p, as] of u) { + const bs: Array<[SourceLocationJSON, string]> = []; + for (const [loc, type] of as) + bs.push([locs.makeLocString(loc), this.checker.typeToString(type)]); + res.packages[p] = bs; + } + return res; + } +} diff --git a/tests/differential/compare.ts b/tests/differential/compare.ts new file mode 100644 index 0000000..d064317 --- /dev/null +++ b/tests/differential/compare.ts @@ -0,0 +1,322 @@ +// @ts-nocheck +// too many import from jelly-previous, so disable ts check, otherwise it has compile error when we run test the first time. +import Solver from "../../src/analysis/solver"; +import {FunctionInfo, ModuleInfo} from "../../src/analysis/infos"; +import {AnalysisState} from "../../src/analysis/analysisstate"; +import logger from "../../src/misc/logger"; +import {AllocationSiteToken, FunctionToken, SnapshotObjectToken, Token} from "../../src/analysis/tokens"; +import {mapGetSet, sourceLocationToString, SourceLocationWithFilename} from "../../src/misc/util"; +import {Node} from "@babel/core"; +import {ConstraintVar} from "../../src/analysis/constraintvars"; +import {default as PrevSolver} from "jelly-previous/src/analysis/solver"; +import {FunctionInfo as PrevFunctionInfo, ModuleInfo as PrevModuleInfo} from "jelly-previous/src/analysis/infos"; +import {AnalysisState as PrevAnalysisState} from "jelly-previous/src/analysis/analysisstate"; +import {ConstraintVar as PrevConstraintVar} from "jelly-previous/src/analysis/constraintvars"; +import {SourceLocation} from "@babel/types"; +import {SnapshotFunctionDescriptor} from "../../src/blended/snapshots"; +import * as PrevTokens from "jelly-previous/src/analysis/tokens"; +import {codeFromLocation} from "../../src/misc/files"; + +/** + * Compares the previous and the current version of Jelly on the given package. + * 1. The reachable functions (in app scope) in call graph are more or equal to previous version. + * 2. The reachable modules (in app scope) in call graph are greater or more to previous version. + * 3. One callee calls (in app scope) are more or equal to previous version. + * 4. The number of constraint variables should be greater or equal to previous. + * 5. The number of tokens in each constraint variables should be greater or equal to previous. + * 6. The dataflow edge from an app's constraint variables to module's shouldn't be missing in current version. + * 7. The dataflow edge from a module's constraint variables to app's shouldn't be missing in current version. + * 8. The dataflow reachability from an app to app shouldn't be missing in current version. + * 9. The call graph edge from an app's call site to callee shouldn't missing in current version. + * 10. The call graph reachability from an app to app shouldn't be missing in current version. + */ +export function compare(prevSolver: PrevSolver, currSolver: Solver, _package: string) { + let entryModulePaths: string[] = filterEntryModules(prevSolver.analysisState.moduleInfos).map(([filepath, _]) => filepath); + + /** + * Filter function in application scope from functionInfos. + */ + function filterEntryFunctions(functionInfos: Map) { + return [...functionInfos].filter(([_, functionInfo]) => functionInfo.moduleInfo.isEntry); + } + + /** + * Filter module in application scope from moduleInfos. + */ + function filterEntryModules(moduleInfos: Map) { + return [...moduleInfos].filter(([_, moduleInfo]) => moduleInfo.isEntry); + } + + /** + * Get the number of callsites in app scope with only one callee. + */ + function getOneCalleeCallsInAppScope(a: AnalysisState | PrevAnalysisState) { + let r = 0; + for (const c of a.callLocations) { + if (c.loc && "filename" in c.loc && entryModulePaths.includes(c.loc.filename)) { + const cs = a.callToFunction.get(c); + if (cs) + if (cs.size === 1) + r++; + else if (cs.size > 1) + if (logger.isDebugEnabled()) + logger.debug(`Call with multiple callees at ${c}: ${cs.size}`); + } + } + return r; + } + + // 1. The reachable functions (in app scope) in call graph are more or equal to previous version. + expect(filterEntryFunctions(currSolver.analysisState.functionInfos).length, + `Unexpected analysisState.functionInfos.size`) + .toBeGreaterThanOrEqual(filterEntryFunctions(prevSolver.analysisState.functionInfos).length); + // 2. The reachable modules (in app scope) in call graph are greater or more to previous version. + expect(filterEntryModules(currSolver.analysisState.moduleInfos).length, + `Unexpected analysisState.moduleInfos.size`) + .toBeGreaterThanOrEqual(filterEntryModules(prevSolver.analysisState.moduleInfos).length); + // 3. One callee calls (in app scope) are more or equal to previous version. + expect(getOneCalleeCallsInAppScope(currSolver.analysisState), + `Unexpected number of OneCalleeCalls`) + .toBeGreaterThanOrEqual(getOneCalleeCallsInAppScope(prevSolver.analysisState)); + + /** + * Get tokens by previous version's constraint variable. + */ + function getTokensByPrevConstraintVar(prevVar: PrevConstraintVar, currSolver: Solver): Array { + for (const [newConstraintVar, tokens] of currSolver.fragmentState.getAllVarsAndTokens()) + if (varToCodeString(newConstraintVar) === varToCodeString(prevVar)) + return Array.from(tokens); + return []; + } + + // compare dataflow graph: constraint variables->tokens map (4 and 5) + for (const [prevConstraintVar, prevTokens] of prevSolver.fragmentState.getAllVarsAndTokens()) { + let m = prevSolver.analysisState.getConstraintVarParent(prevConstraintVar); + if (m && m.isEntry) { + const prevTokenStrings = Array.from(prevTokens) + .filter((t: Token) => { + const loc = getTokenLocation(t); + return loc && entryModulePaths.includes((loc).filename); + }).map((t: any) => t.toString()); + const currTokens = getTokensByPrevConstraintVar(prevConstraintVar, currSolver); + const currTokenStrings = currTokens + .filter((t: Token) => { + const loc = getTokenLocation(t); + return loc && entryModulePaths.includes((loc).filename); + }).map((t: any) => t.toString()); + for (const prevTokenString of prevTokenStrings) { + expect(currTokenStrings, + `Token ${prevTokenString} in ${prevConstraintVar.toString()} is missing`) + .toContain(prevTokenString); + } + } + } + // compare dataflow graph: edge app->module, module->app (6 and 7) + const currSubsetStrEdge = new Map>(); + for (const fromVar of currSolver.fragmentState.vars) { + for (const toVar of mapGetSet(currSolver.fragmentState.subsetEdges, fromVar)) { + const m = currSolver.analysisState.getConstraintVarParent(fromVar); + const n = currSolver.analysisState.getConstraintVarParent(toVar); + if ((m && m.isEntry) || (n && n.isEntry)) { + const s = mapGetSet(currSubsetStrEdge, varToCodeString(fromVar)); + const toVarStr = varToCodeString(toVar); + if (toVarStr) + s.add(toVarStr); + } + } + } + for (const fromVar of prevSolver.fragmentState.vars) { + for (const toVar of mapGetSet(prevSolver.fragmentState.subsetEdges, fromVar)) { + const m = prevSolver.analysisState.getConstraintVarParent(fromVar); + const n = prevSolver.analysisState.getConstraintVarParent(toVar); + if (varToCodeString(fromVar) && varToCodeString(toVar)) + if ((m && m.isEntry) || (n && n.isEntry) ) { + expect(currSubsetStrEdge.keys(), + `Dataflow doesn't have node ${varToCodeString(fromVar)}`) + .toContain(varToCodeString(fromVar)); + expect(mapGetSet(currSubsetStrEdge, varToCodeString(fromVar)), + `Dataflow edge(app->module,module->app) \u2329${varToCodeString(fromVar)}\u232a->\u2329${varToCodeString(toVar)}\u232a doesn't exist`) + .toContain(varToCodeString(toVar)); + } + } + } + + /** + * A breadth-first search to get reachable nodes. + */ + function bfsReachability(graph: Map>, start: T): Set { + const queue: Array = [start]; + const visited: Set = new Set(); + while (queue.length > 0) { + const node = queue.shift(); + if (node && !visited.has(node)) { + visited.add(node); + queue.push(...mapGetSet(graph, node)); + } + } + return visited; + } + // 8. The dataflow reachability from a app to app shouldn't be missing in current version. + const currReachability = new Map>(); + for (const fromVar of currSolver.fragmentState.vars) { + const m = currSolver.analysisState.getConstraintVarParent(fromVar); + if (m && m.isEntry) { + const s = new Set(); + const fromVarStr = varToCodeString(fromVar); + if (fromVarStr) + currReachability.set(fromVarStr, s); + for (const toVar of bfsReachability(currSolver.fragmentState.subsetEdges, fromVar)) { + const toVarStr = varToCodeString(toVar); + if (toVarStr) + s.add(toVarStr); + } + } + } + for (const fromVar of prevSolver.fragmentState.vars) { + const m = prevSolver.analysisState.getConstraintVarParent(fromVar); + if (m && m.isEntry) { + for (const toVar of bfsReachability(prevSolver.fragmentState.subsetEdges, fromVar)) { + const n = prevSolver.analysisState.getConstraintVarParent(toVar); + if (n && n.isEntry && n !== m && varToCodeString(fromVar) && varToCodeString(toVar)) + expect(mapGetSet(currReachability, varToCodeString(fromVar)), + `Dataflow reachability(app-->app) \u2329${varToCodeString(fromVar)}\u232a-->\u2329${varToCodeString(toVar)}\u232a doesn't exist`) + .toContain(varToCodeString(toVar)); + } + } + } + + // compare call graph: app --> function or module edge + + /** + * Transform a map from node to node to a map from string + * And only keep the call node that is in the application module. + */ + function transformCallToFunctionOrModuleToStringMap(callToFunctionOrModule: Map>) { + const ret: Map> = new Map>(); + for (const [callNode, funcOrModules] of callToFunctionOrModule) { + if (callNode.loc && "filename" in callNode.loc && entryModulePaths.includes(callNode.loc.filename)) { + const s = new Set(); + for (const callee of funcOrModules) + s.add(funcOrModuleToString(callee)); + ret.set(`'${codeFromLocation(callNode.loc)}'${sourceLocationToString(callNode.loc)}`, s); + } + } + return ret; + } + + // 9. The callgraph edge from a app's callsite to callee shouldn't missing in current version. + let prevNode2callee = transformCallToFunctionOrModuleToStringMap(prevSolver.analysisState.callToFunctionOrModule); + let currNode2callee = transformCallToFunctionOrModuleToStringMap(currSolver.analysisState.callToFunctionOrModule); + for (const [caller, callees] of prevNode2callee) + for (const callee of callees) + expect(mapGetSet(currNode2callee, caller), + `CallGraph edge(app->module) ${caller} -> ${callee} is missing` + ).toContain(callee); + + /** + * Transform a map from CallGraph to String Graph. + * @param cgEdge + */ + function translateNodeGraphToStringGraph(cgEdge: Map>): [Set, Map>] { + const vertices = new Set(); + const edges = new Map>(); + for (const [caller, callees] of cgEdge) { + const s = new Set(); + for (const callee of callees) { + const calleeStr = funcOrModuleToString(callee); + s.add(calleeStr); + vertices.add(calleeStr); + } + const callerStr = funcOrModuleToString(caller); + vertices.add(callerStr); + edges.set(callerStr, s); + } + return [vertices, edges]; + } + + // 10. The callgraph reachability from a app to app shouldn't missing in current version. + const [_, currCGEdge] = getCallGraph(currSolver.analysisState); + const [__, currCGEdgeStr] = translateNodeGraphToStringGraph(currCGEdge); + const [prevCGvertices, prevCGEdges] = getCallGraph(prevSolver.analysisState); + for (const from of prevCGvertices) + if (from.packageInfo.isEntry) { + const currReachability = bfsReachability(currCGEdgeStr, funcOrModuleToString(from)); + for (const to of bfsReachability(prevCGEdges, from)) + if (to.packageInfo.isEntry) + expect(currReachability, + `CallGraph reachability(app-->app) \u2329${funcOrModuleToString(from)} --> ${funcOrModuleToString(to)} is missing` + ).toContain(funcOrModuleToString(to)); + } +} + +/** + * Return the code with location string of the constraint variable, otherwise return variable.toString. + */ + +export function varToCodeString(v: T): string | undefined { // TODO: Will be replaced with constraintVarToString in the future. + if (!("loc" in v)) + return v.toString(); + else if (v.loc && "filename" in v.loc && v.loc.filename !== "%nodejs" && v.loc.filename !== "%ecmascript") + return `'${codeFromLocation(v.loc)}'[${sourceLocationToString(v.loc, true, true)}]`; + else + return undefined; // dummy location +} + +/** + * Extract the source location from a token, if token doesn't contain source location, return undefined. + */ +export function getTokenLocation(token: T): SourceLocation | undefined { + if (token instanceof FunctionToken || token instanceof PrevTokens.FunctionToken) { + if (token.fun.loc) + return token.fun.loc; + } else if (token instanceof AllocationSiteToken || token instanceof PrevTokens.AllocationSiteToken) { + if (token.allocSite.loc) + return token.allocSite.loc; + } else if (token instanceof SnapshotObjectToken || token instanceof PrevTokens.SnapshotObjectToken) { + if (token.obj instanceof SnapshotFunctionDescriptor) + return token.obj.loc; + } + return undefined; +} + +/** + * A string formatter for function/module info, + * if the constraint variable has source location, it is ast node, return the code of the constraint variable with constraint variable.toString() + * otherwise, return variable.toString(). + */ +export function funcOrModuleToString(info: FunctionInfo | ModuleInfo | PrevFunctionInfo | PrevModuleInfo): string { // TODO: Will be replaced with function.toString() and module.toString() in the future. + if ((info instanceof FunctionInfo || info instanceof PrevFunctionInfo) && info.loc) + return `'${codeFromLocation(info.loc)}'${sourceLocationToString(info.loc)}}`; + else + return `${(info).path}`; +} + + +/** + * Merge require graph and function to function graph to a call graph ( Function/Module × Function/Module ) + * @param a analysis state + * @returns a tuple of vertexes and edges + */ +export function getCallGraph(a: A): + [Set, Map>] { + const vertexes = new Set(); + const edges = new Map>(); + for (const [caller, callees] of a.requireGraph) { + const s = mapGetSet(edges, caller); + vertexes.add(caller); + for (const callee of callees) { + s.add(callee); + vertexes.add(callee); + } + } + for (const [caller, callees] of a.functionToFunction) { + const s = mapGetSet(edges, caller); + vertexes.add(caller); + for (const callee of callees) { + s.add(callee); + vertexes.add(callee); + } + } + return [vertexes, edges]; +} \ No newline at end of file diff --git a/tests/differential/install.ts b/tests/differential/install.ts new file mode 100644 index 0000000..10bc54e --- /dev/null +++ b/tests/differential/install.ts @@ -0,0 +1,92 @@ +import fs from "fs"; +import {execSync} from "child_process"; + +/** + * The git tag of the version to be installed. + */ +const tag = process.env.TAG; + +/** + * Location for previous version of Jelly. + */ +const jellyPrevious = `${__dirname}/../node_modules/jelly-previous`; + +/** + * Location for test packages. + */ +export const packagesDir = `${__dirname}/../../tmp/packages`; + +// noinspection JSUnusedGlobalSymbols +/** + * Install the tagged version of Jelly if tests/node_modules/jelly-previous doesn't exist: + * 1. Git clone the tagged version of Jelly + * 2. Install its dependencies + * 3. Rename the package name to jelly-previous + * 4. Copy the utils.ts to cloned version + * 5. Remove unnecessary files + * Executed via globalSetup in jest.config.js. + */ +export default function() { + // if previous version of Jelly exists, check if it is the correct version and overwrite the utils.ts + if (!tag) { + console.error("Environment variable TAG not set, aborting"); + process.exit(1); + } + const packageJsonFile = `${jellyPrevious}/package.json`; + if (fs.existsSync(packageJsonFile)) { + const packageJson = require(packageJsonFile); + console.log(`Previous version '${packageJson.name}' exists`); + if (packageJson.name === `jelly-${tag}`) + return; + console.log(`Tag does not match '${tag}'`) + fs.rmSync(jellyPrevious, {recursive: true}); + } + console.log(`Installing previous version '${tag}' of Jelly...`); + fs.mkdirSync(jellyPrevious, {recursive: true}); + execSync(`git clone --depth=1 --single-branch --branch ${tag} git@github.com:cs-au-dk/jelly.git -c advice.detachedHead=false ${jellyPrevious}`); + execSync(`cd ${jellyPrevious} && npm install --force`); + const packageJson = require(packageJsonFile); + packageJson.name = `jelly-${tag}`; + packageJson.version = tag; + fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2)); + fs.copyFileSync(`${__dirname}/../../src/misc/util.ts`, `${jellyPrevious}/src/misc/util.ts`); + fs.rmSync(`${jellyPrevious}/tests`, {recursive: true}); + fs.rmSync(`${jellyPrevious}/.idea`, {recursive: true}); + fs.rmSync(`${jellyPrevious}/.git`, {recursive: true}); + fs.rmSync(`${jellyPrevious}/bin`, {recursive: true}); + fs.rmSync(`${jellyPrevious}/.gitignore`, {force: true}); + fs.rmSync(`${jellyPrevious}/.gitattributes`, {force: true}); +} + +/** + * Download the package and install its dependencies. + * @param name package name + * @param version package version + */ +export function preparePackage(name: string, version: string) { + const pkgNameNoDir = name.replace('/', '-').replace("@", ""); + const packageDir = `${packagesDir}/${pkgNameNoDir}`; + const versionDir = `${packageDir}/${version}`; + + // Step 1, downloading + if (fs.existsSync(packageDir)) + return; + fs.mkdirSync(packageDir, {recursive: true}); + if (!fs.existsSync(`${versionDir}/package.json`)) { + if (fs.existsSync(versionDir)) + fs.rmSync(versionDir, {recursive: true}); + const downloadCmd = `cd ${packageDir} && npm pack ${name}@${version}`; + execSync(downloadCmd); + fs.mkdirSync(versionDir, {recursive: true}); + const tarCmd = `cd ${packageDir} && tar -zxvf ${pkgNameNoDir}-${version}.tgz -C ${versionDir} && rm -rf ${pkgNameNoDir}-${version}.tgz`; + execSync(tarCmd); + const mvCmd = `cd ${versionDir}/* && mv * ../ && rm -rf ${versionDir}/package`; + execSync(mvCmd); + } + + // Step 2, npm install + if (!fs.existsSync(`${versionDir}/node_modules`)) { + const npmInstall = `cd ${versionDir} && npm install --ignore-scripts --force`; + execSync(npmInstall); + } +} diff --git a/tests/differential/jelly.difftest.ts b/tests/differential/jelly.difftest.ts new file mode 100644 index 0000000..0edff99 --- /dev/null +++ b/tests/differential/jelly.difftest.ts @@ -0,0 +1,88 @@ +import Solver from "../../src/analysis/solver"; +import {options, resetOptions} from "../../src/options"; +import {analyzeFiles} from "../../src/analysis/analyzer"; +import logger from "../../src/misc/logger"; +import {expand} from "../../src/misc/files"; +import {compare} from "./compare"; +import {preparePackage, packagesDir} from "./install"; + +beforeEach(async () => { + // @ts-ignore + let prevOpts = await import("jelly-previous/src/options"); + prevOpts.resetOptions(); + resetOptions(); + logger.transports[0].level = options.loglevel = "info"; +}); + +describe("tiny", () => { + test("simple", async () => { + // we need ts-ignore when import from jelly-previous, otherwise we get compile error. + // @ts-ignore + const PrevSolver = (await import("jelly-previous/src/analysis/solver")).default; + // @ts-ignore + const prevOptions = (await import("jelly-previous/src/options")).options; + // @ts-ignore + const prevAnalyzeFiles = (await import("jelly-previous/src/analysis/analyzer")).analyzeFiles; + let packageName = "simple"; + options.basedir = prevOptions.basedir = `${__dirname}/simple`; + const app = "application.js"; + options.callgraphRequire = options.callgraphImplicit = options.callgraphNative = + prevOptions.callgraphRequireCalls = prevOptions.callgraphImplicitCalls = prevOptions.callgraphNativeCalls = true; // TODO: remove eventually (also below) + // options.bottomUp = true; + const solver = new Solver(); + await analyzeFiles([app], solver); + const oldSolver = new PrevSolver(); + await prevAnalyzeFiles([app], oldSolver); + compare(oldSolver, solver, packageName); + }); +}); + +describe("small", () => { + let typedDB: Array<{ name: string, version: string }> = require("./small.json"); + test.each(typedDB)(`$name:$version`, async ({name, version}) => { + // @ts-ignore + const PrevSolver = (await import("jelly-previous/src/analysis/solver")).default; + // @ts-ignore + const prevOptions = (await import("jelly-previous/src/options")).options; + // @ts-ignore + const prevAnalyzeFiles = (await import("jelly-previous/src/analysis/analyzer")).analyzeFiles; + preparePackage(name, version); + options.basedir = prevOptions.basedir = + `${packagesDir}/${name.replace("@", "").replace("/", "-")}/${version}`; + const files = expand(options.basedir); + options.callgraphRequire = options.callgraphImplicit = options.callgraphNative = + prevOptions.callgraphRequireCalls = prevOptions.callgraphImplicitCalls = prevOptions.callgraphNativeCalls = true; + // options.bottomUp = true; + const solver = new Solver(); + await analyzeFiles(files, solver); + const oldSolver = new PrevSolver(); + await prevAnalyzeFiles(files, oldSolver); + compare(oldSolver, solver, name); + } + ); +}); + +describe("large", () => { + let typedDBfull: Array<{ name: string, version: string }> = require("./large.json"); + test.each(typedDBfull)(`$name:$version`, async ({name, version}) => { + // @ts-ignore + const PrevSolver = (await import("jelly-previous/src/analysis/solver")).default; + // @ts-ignore + const prevOptions = (await import("jelly-previous/src/options")).options; + // @ts-ignore + const prevAnalyzeFiles = (await import("jelly-previous/src/analysis/analyzer")).analyzeFiles; + preparePackage(name, version); + options.basedir = prevOptions.basedir = + `${packagesDir}/${name.replace("@", "").replace("/", "-")}/${version}`; + const files = expand(options.basedir); + options.callgraphRequire = options.callgraphImplicit = options.callgraphNative = + prevOptions.callgraphRequireCalls = prevOptions.callgraphImplicitCalls = prevOptions.callgraphNativeCalls = true; + // options.bottomUp = true; + const solver = new Solver(); + await analyzeFiles(files, solver); + const oldSolver = new PrevSolver(); + await prevAnalyzeFiles(files, oldSolver); + compare(oldSolver, solver, name); + } + ); +}); diff --git a/tests/differential/large.json b/tests/differential/large.json new file mode 100644 index 0000000..9486793 --- /dev/null +++ b/tests/differential/large.json @@ -0,0 +1,474 @@ +[ + { + "name": "open", + "version": "8.4.0" + }, + { + "name": "ember-load-initializers", + "version": "2.1.2" + }, + { + "name": "eslint-config-react-app", + "version": "7.0.1" + }, + { + "name": "react-app-rewired", + "version": "2.2.1" + }, + { + "name": "lodash.clonedeep", + "version": "4.5.0" + }, + { + "name": "ncp", + "version": "2.0.0" + }, + { + "name": "nanoid", + "version": "4.0.0" + }, + { + "name": "mustache", + "version": "4.2.0" + }, + { + "name": "passport", + "version": "0.6.0" + }, + { + "name": "graceful-fs", + "version": "4.2.10" + }, + { + "name": "husky", + "version": "8.0.2" + }, + { + "name": "react-window", + "version": "1.8.8" + }, + { + "name": "fast-deep-equal", + "version": "3.1.3" + }, + { + "name": "cross-env", + "version": "7.0.3" + }, + { + "name": "get-port", + "version": "6.1.2" + }, + { + "name": "eslint-plugin-promise", + "version": "6.1.1" + }, + { + "name": "object-assign", + "version": "4.1.1" + }, + { + "name": "url-join", + "version": "5.0.0" + }, + { + "name": "gulp-rename", + "version": "2.0.0" + }, + { + "name": "dotenv", + "version": "16.0.3" + }, + { + "name": "reflect-metadata", + "version": "0.1.13" + }, + { + "name": "abort-controller", + "version": "3.0.0" + }, + { + "name": "strip-ansi", + "version": "7.0.1" + }, + { + "name": "es6-promise", + "version": "4.2.8" + }, + { + "name": "path-to-regexp", + "version": "6.2.1" + }, + { + "name": "babel-plugin-dynamic-import-node", + "version": "2.3.3" + }, + { + "name": "resize-observer-polyfill", + "version": "1.5.1" + }, + { + "name": "redux-thunk", + "version": "2.4.2" + }, + { + "name": "mime-types", + "version": "2.1.35" + }, + { + "name": "colors", + "version": "1.4.0" + }, + { + "name": "babel-plugin-transform-react-remove-prop-types", + "version": "0.4.24" + }, + { + "name": "reselect", + "version": "4.1.7" + }, + { + "name": "query-string", + "version": "8.1.0" + }, + { + "name": "turbo", + "version": "1.6.3" + }, + { + "name": "babel-plugin-add-module-exports", + "version": "1.0.4" + }, + { + "name": "compare-versions", + "version": "6.0.0-rc.1" + }, + { + "name": "minimatch", + "version": "5.1.2" + }, + { + "name": "stylelint-config-standard", + "version": "29.0.0" + }, + { + "name": "react-is", + "version": "18.2.0" + }, + { + "name": "lodash.merge", + "version": "4.6.2" + }, + { + "name": "serve-favicon", + "version": "2.5.0" + }, + { + "name": "is-ci", + "version": "3.0.1" + }, + { + "name": "redux-saga", + "version": "1.2.2" + }, + { + "name": "url", + "version": "0.11.0" + }, + { + "name": "memoize-one", + "version": "6.0.0" + }, + { + "name": "hoist-non-react-statics", + "version": "3.3.2" + }, + { + "name": "color", + "version": "4.2.3" + }, + { + "name": "constructs", + "version": "10.1.200" + }, + { + "name": "eslint-plugin-no-only-tests", + "version": "3.1.0" + }, + { + "name": "benchmark", + "version": "2.1.4" + }, + { + "name": "karma-sourcemap-loader", + "version": "0.3.8" + }, + { + "name": "webpack-node-externals", + "version": "3.0.0" + }, + { + "name": "karma-jasmine", + "version": "5.1.0" + }, + { + "name": "object-hash", + "version": "3.0.0" + }, + { + "name": "identity-obj-proxy", + "version": "3.0.0" + }, + { + "name": "lodash.get", + "version": "4.4.2" + }, + { + "name": "escape-string-regexp", + "version": "5.0.0" + }, + { + "name": "camelcase", + "version": "7.0.1" + }, + { + "name": "fsevents", + "version": "2.3.2" + }, + { + "name": "regenerator-runtime", + "version": "0.13.11" + }, + { + "name": "whatwg-fetch", + "version": "3.6.2" + }, + { + "name": "lru-cache", + "version": "7.14.1" + }, + { + "name": "i18next-browser-languagedetector", + "version": "7.0.1" + }, + { + "name": "react-refresh", + "version": "0.14.0" + }, + { + "name": "sinon-chai", + "version": "3.7.0" + }, + { + "name": "tree-kill", + "version": "1.2.2" + }, + { + "name": "bip39", + "version": "3.0.4" + }, + { + "name": "cors", + "version": "2.8.5" + }, + { + "name": "stylelint-config-recommended", + "version": "9.0.0" + }, + { + "name": "lodash.isequal", + "version": "4.5.0" + }, + { + "name": "case-sensitive-paths-webpack-plugin", + "version": "2.4.0" + }, + { + "name": "json-loader", + "version": "0.5.7" + }, + { + "name": "redux-devtools-extension", + "version": "2.13.9" + }, + { + "name": "flat", + "version": "5.0.2" + }, + { + "name": "loader-utils", + "version": "3.2.1" + }, + { + "name": "js-cookie", + "version": "3.0.1" + }, + { + "name": "delay", + "version": "5.0.0" + }, + { + "name": "find-up", + "version": "6.3.0" + }, + { + "name": "tslib", + "version": "2.4.1" + }, + { + "name": "lodash.throttle", + "version": "4.1.1" + }, + { + "name": "debug", + "version": "4.3.4" + }, + { + "name": "copy-to-clipboard", + "version": "3.3.3" + }, + { + "name": "loader.js", + "version": "4.7.0" + }, + { + "name": "eslint-config-next", + "version": "13.1.1" + }, + { + "name": "grunt-contrib-copy", + "version": "1.0.0" + }, + { + "name": "config", + "version": "3.3.8" + }, + { + "name": "md5", + "version": "2.3.0" + }, + { + "name": "buffer", + "version": "6.0.3" + }, + { + "name": "ua-parser-js", + "version": "1.0.32" + }, + { + "name": "url-parse", + "version": "1.5.10" + }, + { + "name": "which", + "version": "3.0.0" + }, + { + "name": "flow-bin", + "version": "0.196.3" + }, + { + "name": "dotenv-expand", + "version": "10.0.0" + }, + { + "name": "pretty-bytes", + "version": "6.0.0" + }, + { + "name": "classnames", + "version": "2.3.2" + }, + { + "name": "process", + "version": "0.11.10" + }, + { + "name": "mime", + "version": "3.0.0" + }, + { + "name": "cookie", + "version": "0.5.0" + }, + { + "name": "slugify", + "version": "1.6.5" + }, + { + "name": "pluralize", + "version": "8.0.0" + }, + { + "name": "file-saver", + "version": "2.0.5" + }, + { + "name": "lodash.debounce", + "version": "4.0.8" + }, + { + "name": "ms", + "version": "2.1.3" + }, + { + "name": "unified", + "version": "10.1.2" + }, + { + "name": "stylelint-config-standard-scss", + "version": "6.1.0" + }, + { + "name": "karma-firefox-launcher", + "version": "2.1.2" + }, + { + "name": "csv-parse", + "version": "5.3.3" + }, + { + "name": "eslint-import-resolver-alias", + "version": "1.1.2" + }, + { + "name": "path", + "version": "0.12.7" + }, + { + "name": "unist-util-visit", + "version": "4.1.1" + }, + { + "name": "json5", + "version": "2.2.2" + }, + { + "name": "uuid", + "version": "9.0.0" + }, + { + "name": "invariant", + "version": "2.2.4" + }, + { + "name": "browser-sync", + "version": "2.27.11" + }, + { + "name": "p-limit", + "version": "4.0.0" + }, + { + "name": "chalk", + "version": "5.2.0" + }, + { + "name": "styled-components", + "version": "5.3.6" + }, + { + "name": "karma-chai", + "version": "0.1.0" + } +] diff --git a/tests/differential/simple/application.js b/tests/differential/simple/application.js new file mode 100644 index 0000000..707080b --- /dev/null +++ b/tests/differential/simple/application.js @@ -0,0 +1,4 @@ +let m=require("myModule"); +let i=()=>{console.log("i")}; +let j=m.f(i); +j(); diff --git a/tests/differential/simple/node_modules/myModule/index.js b/tests/differential/simple/node_modules/myModule/index.js new file mode 100644 index 0000000..75af71a --- /dev/null +++ b/tests/differential/simple/node_modules/myModule/index.js @@ -0,0 +1,10 @@ +let f = (a) => { + return g(a); +}; +let g = (b) => { + return h(b); +}; +let h = (c) => { + return c; +} +exports.f = f; diff --git a/tests/differential/simple/node_modules/myModule/package.json b/tests/differential/simple/node_modules/myModule/package.json new file mode 100644 index 0000000..b118738 --- /dev/null +++ b/tests/differential/simple/node_modules/myModule/package.json @@ -0,0 +1,11 @@ +{ + "name": "myModule", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/tests/differential/small.json b/tests/differential/small.json new file mode 100644 index 0000000..8d78d47 --- /dev/null +++ b/tests/differential/small.json @@ -0,0 +1,74 @@ +[ + { + "name": "eslint-config-prettier", + "version": "8.5.0" + }, + { + "name": "ws", + "version": "8.11.0" + }, + { + "name": "redux-logger", + "version": "3.0.6" + }, + { + "name": "form-data", + "version": "4.0.0" + }, + { + "name": "eslint-config-google", + "version": "0.14.0" + }, + { + "name": "ember-resolver", + "version": "9.0.1" + }, + { + "name": "eslint-config-standard", + "version": "17.0.0" + }, + { + "name": "jest-puppeteer", + "version": "6.2.0" + }, + { + "name": "chai-as-promised", + "version": "7.1.1" + }, + { + "name": "mkdirp", + "version": "1.0.4" + }, + { + "name": "cross-spawn", + "version": "7.0.3" + }, + { + "name": "eslint-plugin-simple-import-sort", + "version": "8.0.0" + }, + { + "name": "preact", + "version": "10.11.3" + }, + { + "name": "eventemitter3", + "version": "5.0.0" + }, + { + "name": "js-base64", + "version": "3.7.3" + }, + { + "name": "@tailwindcss/forms", + "version": "0.5.3" + }, + { + "name": "cookie-parser", + "version": "1.4.6" + }, + { + "name": "eslint-plugin-standard", + "version": "5.0.0" + } +] diff --git a/tests/helloworld/app.js b/tests/helloworld/app.js new file mode 100644 index 0000000..afdcd24 --- /dev/null +++ b/tests/helloworld/app.js @@ -0,0 +1,12 @@ +const express = require('express'); + +const app = express(); + +app.get('/', function(req, res) { + res.send("Hello world!"); + console.log("Response sent"); +}); + +const PORT = 3000; +app.listen(PORT); +console.log(`Listening on port ${PORT}`); \ No newline at end of file diff --git a/tests/helloworld/app.json b/tests/helloworld/app.json new file mode 100644 index 0000000..9e3c36d --- /dev/null +++ b/tests/helloworld/app.json @@ -0,0 +1,601 @@ +{ + "entries": ["app.js"], + "time": "Wed, 28 Sep 2022 18:56:42 GMT", + "files": [ + "app.js", + "node_modules/express/index.js", + "node_modules/express/lib/express.js", + "node_modules/body-parser/index.js", + "node_modules/depd/index.js", + "node_modules/depd/lib/compat/index.js", + "node_modules/merge-descriptors/index.js", + "node_modules/express/lib/application.js", + "node_modules/finalhandler/index.js", + "node_modules/debug/src/index.js", + "node_modules/debug/src/node.js", + "node_modules/debug/src/debug.js", + "node_modules/ms/index.js", + "node_modules/encodeurl/index.js", + "node_modules/escape-html/index.js", + "node_modules/on-finished/index.js", + "node_modules/ee-first/index.js", + "node_modules/parseurl/index.js", + "node_modules/statuses/index.js", + "node_modules/unpipe/index.js", + "node_modules/express/lib/router/index.js", + "node_modules/express/lib/router/route.js", + "node_modules/array-flatten/array-flatten.js", + "node_modules/express/lib/router/layer.js", + "node_modules/path-to-regexp/index.js", + "node_modules/methods/index.js", + "node_modules/utils-merge/index.js", + "node_modules/setprototypeof/index.js", + "node_modules/express/lib/middleware/init.js", + "node_modules/express/lib/middleware/query.js", + "node_modules/qs/lib/index.js", + "node_modules/qs/lib/stringify.js", + "node_modules/qs/lib/utils.js", + "node_modules/qs/lib/formats.js", + "node_modules/qs/lib/parse.js", + "node_modules/express/lib/view.js", + "node_modules/express/lib/utils.js", + "node_modules/safe-buffer/index.js", + "node_modules/content-disposition/index.js", + "node_modules/content-type/index.js", + "node_modules/send/index.js", + "node_modules/http-errors/index.js", + "node_modules/inherits/inherits.js", + "node_modules/toidentifier/index.js", + "node_modules/destroy/index.js", + "node_modules/etag/index.js", + "node_modules/fresh/index.js", + "node_modules/mime/mime.js", + "node_modules/send/node_modules/ms/index.js", + "node_modules/range-parser/index.js", + "node_modules/proxy-addr/index.js", + "node_modules/forwarded/index.js", + "node_modules/ipaddr.js/lib/ipaddr.js", + "node_modules/express/lib/request.js", + "node_modules/accepts/index.js", + "node_modules/negotiator/index.js", + "node_modules/negotiator/lib/charset.js", + "node_modules/negotiator/lib/encoding.js", + "node_modules/negotiator/lib/language.js", + "node_modules/negotiator/lib/mediaType.js", + "node_modules/mime-types/index.js", + "node_modules/mime-db/index.js", + "node_modules/type-is/index.js", + "node_modules/media-typer/index.js", + "node_modules/express/lib/response.js", + "node_modules/cookie-signature/index.js", + "node_modules/cookie/index.js", + "node_modules/vary/index.js", + "node_modules/body-parser/lib/types/json.js", + "node_modules/bytes/index.js", + "node_modules/body-parser/lib/read.js", + "node_modules/raw-body/index.js", + "node_modules/iconv-lite/lib/index.js", + "node_modules/safer-buffer/safer.js", + "node_modules/iconv-lite/lib/bom-handling.js", + "node_modules/iconv-lite/lib/streams.js", + "node_modules/iconv-lite/lib/extend-node.js", + "node_modules/body-parser/lib/types/raw.js", + "node_modules/serve-static/index.js", + "node_modules/body-parser/lib/types/text.js", + "node_modules/body-parser/lib/types/urlencoded.js" + ], + "functions": { + "12": "0:1:1:12:42", + "109": "1:1:1:12:1", + "112": "2:1:1:117:1", + "114": "3:1:1:158:1", + "117": "4:1:1:523:1", + "120": "5:1:1:80:1", + "125": "5:53:1:71:2", + "164": "5:54:3:64:4", + "167": "5:21:50:43:2", + "184": "5:26:3:28:4", + "198": "5:45:52:47:2", + "208": "4:105:1:129:2", + "211": "4:365:1:383:2", + "229": "4:389:1:391:2", + "238": "4:251:1:266:2", + "257": "4:135:1:146:2", + "266": "4:31:1:45:2", + "283": "4:152:1:163:2", + "304": "4:397:1:417:2", + "311": "4:73:1:81:2", + "337": "3:121:1:125:2", + "354": "6:1:1:61:1", + "360": "7:1:1:645:1", + "362": "8:1:1:332:1", + "365": "9:1:1:11:1", + "375": "10:1:1:249:1", + "380": "11:1:1:203:1", + "390": "12:1:1:153:1", + "415": "10:34:55:36:2", + "440": "10:156:1:158:2", + "445": "11:138:1:156:2", + "449": "10:139:1:147:2", + "463": "11:63:1:128:2", + "469": "11:176:1:189:2", + "480": "10:75:1:79:2", + "488": "11:44:1:53:2", + "508": "10:235:1:242:2", + "518": "13:1:1:61:1", + "521": "14:1:1:79:1", + "524": "15:1:1:197:1", + "529": "16:1:1:96:1", + "535": "17:1:1:159:1", + "543": "18:1:1:114:1", + "549": "18:60:1:77:2", + "556": "18:63:30:74:4", + "572": "19:1:1:70:1", + "580": "20:1:1:669:1", + "582": "21:1:1:217:1", + "586": "22:1:1:65:1", + "589": "23:1:1:182:1", + "591": "24:1:1:130:1", + "606": "25:1:1:70:1", + "611": "25:29:1:33:2", + "618": "25:30:43:32:4", + "639": "21:192:17:216:2", + "646": "26:1:1:24:1", + "655": "27:1:1:18:1", + "676": "20:513:31:519:2", + "681": "28:1:1:44:1", + "685": "29:1:1:48:1", + "689": "30:1:1:12:1", + "691": "31:1:1:288:1", + "693": "32:1:1:253:1", + "695": "33:1:1:24:1", + "709": "32:8:17:15:2", + "743": "34:1:1:258:1", + "758": "35:1:1:183:1", + "778": "36:1:1:304:1", + "781": "37:1:1:66:1", + "798": "37:7:1:11:2", + "807": "38:1:1:459:1", + "816": "39:1:1:223:1", + "824": "40:1:1:1134:1", + "826": "41:1:1:300:1", + "832": "42:1:1:10:1", + "840": "43:1:1:33:1", + "846": "41:118:1:126:2", + "855": "41:181:1:195:2", + "862": "41:264:1:288:2", + "866": "41:265:17:283:4", + "870": "43:24:1:32:2", + "878": "43:27:10:29:6", + "890": "41:38:1:40:2", + "902": "41:133:1:174:2", + "905": "41:295:1:299:2", + "913": "41:250:1:257:2", + "933": "41:202:1:243:2", + "953": "44:1:1:76:1", + "961": "45:1:1:132:1", + "970": "46:1:1:138:1", + "974": "47:1:1:109:1", + "986": "47:4:1:10:2", + "999": "47:21:25:38:2", + "1019": "47:69:25:73:2", + "1033": "48:1:1:163:1", + "1042": "49:1:1:163:1", + "1128": "50:1:1:328:1", + "1135": "51:1:1:91:1", + "1138": "52:1:1:674:1", + "1141": "52:1:2:673:2", + "1152": "52:56:18:152:4", + "1167": "52:57:5:69:6", + "1216": "52:199:18:385:4", + "1235": "52:200:5:222:6", + "1328": "36:271:1:279:2", + "1377": "7:472:17:485:2", + "1390": "53:1:1:526:1", + "1392": "54:1:1:239:1", + "1394": "55:1:1:83:1", + "1396": "56:1:1:170:1", + "1401": "57:1:1:185:1", + "1406": "58:1:1:180:1", + "1411": "59:1:1:295:1", + "1467": "60:1:1:189:1", + "1469": "61:1:1:12:1", + "1490": "60:154:1:188:2", + "1497": "60:158:27:187:4", + "1556": "62:1:1:267:1", + "1558": "63:1:1:271:1", + "1605": "53:519:1:525:2", + "1623": "64:1:1:1148:1", + "1640": "65:1:1:52:1", + "1651": "66:1:1:203:1", + "1661": "67:1:1:150:1", + "1707": "3:122:10:124:4", + "1710": "3:132:1:157:2", + "1716": "68:1:1:231:1", + "1718": "69:1:1:171:1", + "1735": "70:1:1:182:1", + "1738": "71:1:1:287:1", + "1742": "72:1:1:154:1", + "1745": "73:1:1:78:1", + "1778": "74:1:1:53:1", + "1824": "75:1:1:122:1", + "1852": "75:8:18:26:2", + "1864": "76:1:1:218:1", + "1868": "76:7:18:217:2", + "1892": "77:1:1:102:1", + "1901": "78:1:1:211:1", + "1917": "79:1:1:122:1", + "1929": "80:1:1:285:1", + "1945": "2:109:28:116:2", + "1951": "2:37:1:57:2", + "1955": "6:34:1:60:2", + "1963": "6:48:43:57:4", + "1986": "7:57:12:63:2", + "1993": "7:70:28:127:2", + "2001": "7:452:14:454:2", + "2005": "7:352:11:384:2", + "2010": "11:65:3:115:4", + "2023": "36:151:23:173:2", + "2036": "36:183:30:206:2", + "2050": "36:216:24:236:2", + "2059": "50:84:1:113:2", + "2071": "50:122:1:130:2", + "2078": "50:139:1:147:2", + "2114": "7:473:17:484:4", + "2122": "7:137:18:147:2", + "2129": "7:421:15:423:2", + "2139": "20:43:30:61:2", + "2160": "29:25:18:47:2", + "2163": "26:16:28:23:2", + "2170": "20:434:13:482:2", + "2176": "22:58:1:64:2", + "2180": "22:37:1:49:2", + "2201": "23:33:1:50:2", + "2215": "24:28:1:129:2", + "2270": "28:28:16:42:2", + "2275": "20:497:15:510:2", + "2278": "21:43:1:51:2", + "2299": "21:193:29:215:4", + "2320": "7:616:14:619:2", + "2331": "2:38:13:40:4", + "2335": "7:158:14:175:2", + "2344": "8:77:1:135:2", + "2353": "20:136:16:326:2", + "2361": "20:541:1:555:2", + "2375": "20:626:1:643:2", + "2402": "20:176:3:286:4", + "2411": "20:532:1:538:2", + "2415": "17:35:1:55:2", + "2421": "17:153:1:158:2", + "2428": "17:95:1:142:2", + "2452": "20:578:1:584:2", + "2456": "23:110:25:156:2", + "2473": "20:333:24:417:2", + "2482": "20:275:55:285:6", + "2485": "20:288:3:325:4", + "2494": "23:86:34:99:2", + "2500": "29:39:10:46:4", + "2513": "36:288:1:292:2", + "2517": "34:237:18:257:2", + "2520": "34:203:29:235:2", + "2559": "32:214:16:216:2", + "2596": "28:29:10:41:4", + "2631": "21:58:35:70:2", + "2650": "21:98:28:140:2", + "2662": "21:114:3:139:4", + "2673": "0:5:14:8:2", + "2677": "64:107:12:225:2", + "2689": "64:793:11:795:2", + "2695": "64:599:12:605:2", + "2704": "64:759:14:783:2", + "2727": "47:102:11:105:4", + "2744": "36:247:22:260:2", + "2751": "39:106:1:164:2", + "2767": "39:219:1:222:2", + "2797": "39:65:1:96:2", + "2827": "39:200:1:213:2", + "2853": "36:272:10:278:4", + "2858": "45:70:1:94:2", + "2862": "45:104:1:116:2", + "2891": "45:39:1:58:2", + "2915": "53:467:28:484:2", + "2932": "46:33:1:85:2", + "2965": "20:635:10:642:4", + "2974": "8:86:10:134:4", + "2979": "8:255:1:259:2", + "2990": "8:220:1:226:2", + "2995": "17:65:1:85:2", + "3004": "13:56:1:60:2", + "3017": "8:272:1:311:2", + "3020": "15:65:1:80:2", + "3039": "19:40:1:69:2", + "3046": "15:45:1:55:2", + "3051": "15:140:1:150:2", + "3058": "15:160:1:176:2", + "3062": "15:90:1:130:2", + "3065": "16:24:1:73:2", + "3085": "16:80:1:95:2", + "3095": "15:106:3:115:4", + "3106": "16:81:10:94:4", + "3115": "16:53:3:56:4", + "3118": "16:58:3:64:4", + "3130": "15:95:3:101:4", + "3137": "15:161:3:171:4", + "3149": "8:273:3:298:4", + "3152": "8:43:1:58:2", + "3157": "14:33:1:78:2", + "3172": "8:321:1:331:2", + "3197": "46:110:1:137:2" + }, + "calls": { + "123": "5:21:1:43:3", + "162": "5:45:1:47:3", + "166": "5:55:15:55:23", + "115": "3:14:17:14:47", + "210": "4:110:15:110:25", + "236": "4:111:14:111:40", + "256": "4:120:24:120:44", + "265": "4:145:10:145:43", + "282": "4:122:23:122:42", + "291": "4:162:10:162:43", + "302": "3:37:28:38:60", + "309": "4:402:14:402:46", + "318": "4:404:15:404:25", + "319": "4:405:14:405:40", + "336": "3:48:8:48:34", + "342": "3:59:8:59:33", + "346": "3:70:8:70:34", + "350": "3:81:8:81:40", + "439": "10:248:16:248:22", + "437": "10:248:1:248:23", + "447": "11:139:3:139:27", + "363": "8:14:13:14:45", + "467": "11:118:19:118:45", + "478": "11:119:21:119:40", + "487": "11:120:17:120:39", + "506": "11:124:5:124:24", + "548": "18:28:16:28:50", + "583": "21:16:13:16:53", + "593": "23:17:13:17:53", + "610": "25:22:18:22:41", + "648": "20:20:13:20:47", + "650": "20:21:17:21:43", + "708": "32:8:17:15:4", + "755": "7:21:13:21:52", + "759": "35:16:13:16:45", + "797": "37:27:1:27:30", + "819": "36:18:17:18:43", + "827": "41:15:17:15:47", + "845": "41:27:28:27:56", + "852": "41:28:30:28:81", + "857": "41:31:1:31:85", + "868": "41:267:16:267:44", + "889": "41:269:13:269:28", + "901": "41:271:21:271:72", + "904": "41:134:19:134:36", + "912": "41:167:3:167:35", + "932": "41:274:21:274:72", + "935": "41:203:19:203:36", + "937": "41:236:3:236:35", + "945": "41:286:27:287:46", + "948": "40:16:13:16:37", + "950": "40:17:17:17:40", + "985": "47:84:12:84:22", + "996": "47:87:1:87:37", + "1017": "47:90:21:90:39", + "1060": "40:183:29:187:37", + "1064": "40:197:31:202:39", + "1068": "40:213:30:218:39", + "1074": "40:234:29:235:36", + "1080": "40:237:29:238:36", + "1086": "40:248:31:257:41", + "1151": "52:56:18:152:7", + "1166": "52:99:22:99:44", + "1178": "52:100:20:100:50", + "1179": "52:101:20:101:44", + "1180": "52:102:20:102:46", + "1181": "52:103:19:103:43", + "1182": "52:104:26:104:51", + "1183": "52:105:20:105:43", + "1184": "52:105:50:105:75", + "1185": "52:105:83:105:109", + "1186": "52:106:19:106:43", + "1187": "52:106:51:106:75", + "1188": "52:106:83:106:109", + "1189": "52:106:117:106:144", + "1190": "52:106:152:106:178", + "1191": "52:106:186:106:210", + "1215": "52:199:18:385:7", + "1234": "52:312:21:312:55", + "1249": "52:313:19:313:58", + "1250": "52:314:19:314:58", + "1251": "52:315:18:315:52", + "1252": "52:316:21:316:60", + "1253": "52:317:20:317:59", + "1254": "52:318:17:318:56", + "1255": "52:319:17:319:59", + "1256": "52:320:16:320:55", + "1257": "52:321:16:321:55", + "1258": "52:322:19:322:62", + "1327": "36:35:16:35:52", + "1331": "36:46:17:46:52", + "1334": "36:70:19:71:57", + "1340": "36:114:30:115:74", + "1350": "7:27:17:27:43", + "1382": "7:512:11:512:76", + "1487": "60:40:1:40:48", + "1551": "53:17:17:17:43", + "1587": "53:150:23:151:55", + "1593": "53:167:22:168:53", + "1598": "53:184:23:185:55", + "1604": "53:306:1:324:3", + "1610": "53:335:1:337:3", + "1611": "53:349:1:352:3", + "1612": "53:366:1:375:3", + "1613": "53:392:1:403:3", + "1614": "53:412:1:414:3", + "1615": "53:427:1:450:3", + "1617": "53:454:27:456:41", + "1616": "53:454:1:456:42", + "1619": "53:467:1:484:3", + "1620": "53:495:1:497:3", + "1621": "53:506:1:509:3", + "1627": "64:17:17:17:43", + "1680": "64:518:16:519:44", + "1709": "3:123:12:123:28", + "1732": "68:18:13:18:49", + "1822": "72:144:9:144:36", + "1862": "72:148:5:148:36", + "1894": "77:14:13:14:48", + "1920": "79:15:13:15:49", + "1933": "80:18:13:18:55", + "1935": "80:19:17:19:47", + "1950": "0:3:13:3:22", + "1953": "2:42:3:42:44", + "1975": "2:43:3:43:27", + "1984": "2:55:3:55:13", + "1991": "7:62:3:62:30", + "1999": "7:74:3:74:30", + "2003": "7:453:10:453:33", + "2009": "7:358:3:358:40", + "2018": "7:75:3:75:27", + "2022": "7:366:27:366:43", + "2020": "7:366:7:366:44", + "2029": "7:76:3:76:23", + "2031": "7:77:3:77:39", + "2035": "7:369:35:369:58", + "2033": "7:369:7:369:59", + "2043": "7:78:3:78:34", + "2045": "7:79:3:79:33", + "2049": "7:372:34:372:51", + "2056": "36:235:10:235:38", + "2070": "50:112:23:112:49", + "2069": "50:112:10:112:50", + "2047": "7:372:7:372:52", + "2090": "7:87:3:87:35", + "2101": "7:114:3:114:25", + "2103": "7:115:3:115:38", + "2106": "7:116:3:116:46", + "2112": "0:5:1:8:3", + "2120": "7:479:5:479:22", + "2127": "7:140:22:140:60", + "2133": "7:422:18:422:35", + "2137": "7:141:15:141:45", + "2126": "7:139:20:142:7", + "2156": "7:144:28:144:55", + "2158": "7:476:14:476:28", + "2155": "7:144:22:144:56", + "2162": "29:26:14:26:32", + "2152": "7:144:5:144:57", + "2173": "20:454:19:454:57", + "2179": "22:60:12:60:37", + "2196": "20:468:5:468:55", + "2199": "20:470:17:474:11", + "2204": "23:38:3:38:24", + "2213": "23:45:17:45:55", + "2268": "7:145:22:145:43", + "2265": "7:145:5:145:44", + "2272": "7:481:17:481:41", + "2277": "20:498:15:498:30", + "2282": "21:47:3:47:24", + "2284": "20:500:15:504:33", + "2301": "21:194:19:194:49", + "2308": "21:205:7:205:40", + "2310": "21:207:19:207:41", + "2311": "23:35:12:35:40", + "2318": "0:11:1:11:17", + "2333": "2:39:5:39:31", + "2340": "7:163:10:163:25", + "2339": "7:162:26:165:5", + "2351": "7:174:3:174:32", + "2355": "20:139:3:139:50", + "2359": "20:142:19:142:40", + "2374": "20:157:14:157:60", + "2401": "20:174:3:174:9", + "2410": "20:207:16:207:32", + "2414": "20:534:12:534:25", + "2420": "17:45:7:45:25", + "2427": "17:51:12:51:26", + "2451": "20:220:15:220:38", + "2454": "20:580:12:580:29", + "2471": "20:275:5:285:7", + "2481": "20:341:12:341:18", + "2484": "20:284:7:284:54", + "2489": "20:318:5:318:64", + "2492": "20:323:7:323:43", + "2499": "23:95:5:95:23", + "2504": "29:41:17:41:30", + "2512": "29:42:19:42:40", + "2515": "36:289:10:291:5", + "2519": "34:238:19:238:46", + "2556": "34:225:58:225:88", + "2595": "29:45:5:45:11", + "2598": "28:30:9:30:36", + "2615": "28:40:5:40:11", + "2629": "20:244:24:244:53", + "2648": "20:281:16:281:52", + "2661": "21:112:3:112:9", + "2671": "21:137:7:137:43", + "2675": "0:6:5:6:29", + "2687": "64:144:12:144:36", + "2693": "64:145:9:145:26", + "2700": "64:601:7:601:24", + "2702": "64:604:10:604:38", + "2721": "64:771:23:771:64", + "2738": "64:166:12:166:36", + "2743": "64:170:32:170:57", + "2749": "36:253:16:253:39", + "2764": "39:129:13:129:48", + "2795": "36:259:10:259:36", + "2825": "39:91:38:91:64", + "2741": "64:170:7:170:58", + "2833": "64:175:16:175:34", + "2837": "64:176:23:176:39", + "2848": "64:194:5:194:36", + "2852": "64:200:17:200:40", + "2857": "36:277:12:277:30", + "2861": "45:76:17:76:32", + "2890": "45:89:7:89:24", + "2912": "64:201:7:201:29", + "2928": "53:478:15:478:30", + "2930": "53:479:24:479:48", + "2926": "53:477:12:480:7", + "2964": "20:260:14:260:30", + "2978": "8:92:17:92:33", + "2989": "8:115:54:115:74", + "2993": "8:222:12:222:34", + "3000": "17:75:7:75:25", + "3001": "17:81:12:81:26", + "2988": "8:115:44:115:75", + "3013": "8:118:5:118:32", + "3015": "8:126:9:126:25", + "3016": "8:133:5:133:41", + "3019": "8:300:7:300:22", + "3038": "8:306:3:306:14", + "3045": "8:309:3:309:25", + "3049": "15:46:7:46:22", + "3050": "15:52:3:52:32", + "3057": "15:145:35:145:54", + "3061": "15:146:5:146:42", + "3064": "15:104:22:104:63", + "3084": "16:40:16:40:41", + "3093": "15:119:5:119:25", + "3100": "15:114:16:114:61", + "3114": "16:93:5:93:31", + "3117": "16:54:5:54:14", + "3132": "15:96:5:96:19", + "3134": "15:97:5:97:22", + "3136": "15:100:5:100:20", + "3147": "15:169:7:169:25", + "3151": "8:275:16:275:43", + "3156": "8:44:14:44:33", + "3171": "8:282:5:282:29", + "3196": "46:60:19:60:44" + }, + "fun2fun": [ + [120, 125], [164, 167], [164, 198], [114, 208], [114, 304], [114, 337], [208, 211], [208, 238], [208, 257], [208, 283], [257, 266], [283, 266], [304, 311], [304, 211], [304, 238], [375, 440], [375, 445], [445, 449], [362, 463], [463, 469], [463, 480], [463, 488], [463, 508], [543, 549], [582, 463], [589, 463], [606, 611], [580, 463], [580, 208], [693, 709], [360, 463], [360, 208], [360, 304], [758, 463], [781, 798], [778, 208], [778, 1328], [778, 304], [826, 208], [826, 846], [826, 855], [826, 862], [866, 870], [866, 890], [866, 902], [866, 933], [902, 905], [902, 913], [933, 905], [933, 913], [862, 304], [824, 463], [824, 208], [824, 304], [974, 986], [974, 999], [974, 1019], [1141, 1152], [1141, 1216], [1152, 1167], [1216, 1235], [1467, 1490], [1390, 208], [1390, 304], [1390, 1605], [1623, 208], [1623, 304], [1707, 1710], [1716, 463], [1742, 1852], [1742, 1868], [1892, 463], [1917, 463], [1929, 463], [1929, 208], [12, 1951], [12, 2114], [12, 2320], [1951, 1955], [1951, 1986], [1986, 1993], [1993, 2001], [1993, 2005], [1993, 2010], [2001, 2005], [2005, 2010], [2005, 2023], [2005, 2005], [2005, 2036], [2005, 2050], [2050, 2059], [2059, 2071], [2059, 2078], [2114, 2122], [2114, 2005], [2114, 2275], [2122, 2129], [2122, 2139], [2122, 2114], [2122, 2160], [2122, 2170], [2122, 2270], [2129, 2005], [2160, 2163], [2170, 2176], [2170, 2010], [2170, 2201], [2176, 2180], [2201, 2010], [2201, 2215], [2201, 2201], [2275, 2278], [2275, 2201], [2278, 2010], [2299, 2176], [2299, 2010], [2299, 2201], [2331, 2335], [2335, 2114], [2335, 2344], [2335, 2353], [2353, 2010], [2353, 2361], [2353, 2375], [2353, 2402], [2402, 2411], [2402, 2452], [2402, 2473], [2402, 2631], [2402, 2965], [2411, 2415], [2415, 2421], [2415, 2428], [2452, 2456], [2473, 2482], [2482, 2485], [2482, 2494], [2485, 2010], [2485, 2494], [2494, 2500], [2494, 2596], [2494, 2650], [2494, 2673], [2500, 2415], [2500, 2513], [2500, 2402], [2513, 2517], [2517, 2520], [2520, 2559], [2596, 2129], [2596, 2402], [2650, 2662], [2662, 2494], [2673, 2677], [2677, 2689], [2677, 2695], [2677, 2744], [2677, 2704], [2677, 2114], [2677, 2853], [2695, 1019], [2695, 2704], [2704, 2727], [2744, 2751], [2744, 2797], [2751, 2767], [2797, 2827], [2853, 2858], [2858, 2862], [2858, 2891], [2915, 2689], [2915, 2932], [2974, 2979], [2974, 2990], [2974, 3004], [2974, 2010], [2974, 3017], [2990, 2995], [2995, 2421], [2995, 2428], [3017, 3020], [3017, 3039], [3017, 3046], [3046, 3020], [3046, 3051], [3051, 3058], [3051, 3062], [3062, 3065], [3062, 3095], [3065, 3085], [3095, 3065], [3106, 3115], [3115, 3118], [3130, 3118], [3130, 3137], [3137, 3149], [3149, 3152], [3149, 3172], [3152, 3157], [2932, 3197] + ], + "call2fun": [ + [123, 125], [162, 125], [166, 167], [166, 198], [115, 208], [210, 211], [236, 238], [256, 257], [265, 266], [282, 283], [291, 266], [302, 304], [309, 311], [318, 211], [319, 238], [336, 337], [342, 337], [346, 337], [350, 337], [439, 440], [437, 445], [447, 449], [363, 463], [467, 469], [478, 480], [487, 488], [506, 508], [548, 549], [583, 463], [593, 463], [610, 611], [648, 463], [650, 208], [708, 709], [755, 463], [759, 463], [797, 798], [819, 208], [827, 208], [845, 846], [852, 855], [857, 862], [868, 870], [889, 890], [901, 902], [904, 905], [912, 913], [932, 933], [935, 905], [937, 913], [945, 304], [948, 463], [950, 208], [985, 986], [996, 999], [1017, 1019], [1060, 304], [1064, 304], [1068, 304], [1074, 304], [1080, 304], [1086, 304], [1151, 1152], [1166, 1167], [1178, 1167], [1179, 1167], [1180, 1167], [1181, 1167], [1182, 1167], [1183, 1167], [1184, 1167], [1185, 1167], [1186, 1167], [1187, 1167], [1188, 1167], [1189, 1167], [1190, 1167], [1191, 1167], [1215, 1216], [1234, 1235], [1249, 1235], [1250, 1235], [1251, 1235], [1252, 1235], [1253, 1235], [1254, 1235], [1255, 1235], [1256, 1235], [1257, 1235], [1258, 1235], [1327, 1328], [1331, 1328], [1334, 304], [1340, 304], [1350, 208], [1382, 304], [1487, 1490], [1551, 208], [1587, 304], [1593, 304], [1598, 304], [1604, 1605], [1610, 1605], [1611, 1605], [1612, 1605], [1613, 1605], [1614, 1605], [1615, 1605], [1617, 304], [1616, 1605], [1619, 1605], [1620, 1605], [1621, 1605], [1627, 208], [1680, 304], [1709, 1710], [1732, 463], [1822, 1852], [1862, 1868], [1894, 463], [1920, 463], [1933, 463], [1935, 208], [1950, 1951], [1953, 1955], [1975, 1955], [1984, 1986], [1991, 1993], [1999, 2001], [2003, 2005], [2009, 2010], [2018, 2005], [2022, 2023], [2020, 2005], [2029, 2005], [2031, 2005], [2035, 2036], [2033, 2005], [2043, 2005], [2045, 2005], [2049, 2050], [2056, 2059], [2070, 2071], [2069, 2078], [2047, 2005], [2090, 2010], [2101, 2005], [2103, 2005], [2106, 2005], [2112, 2114], [2120, 2122], [2127, 2129], [2133, 2005], [2137, 2129], [2126, 2139], [2156, 2114], [2158, 2005], [2155, 2160], [2162, 2163], [2152, 2170], [2173, 2176], [2179, 2180], [2196, 2010], [2199, 2201], [2204, 2010], [2213, 2215], [2268, 2270], [2265, 2170], [2272, 2275], [2277, 2278], [2282, 2010], [2284, 2201], [2301, 2176], [2308, 2010], [2310, 2201], [2311, 2201], [2318, 2320], [2333, 2335], [2340, 2114], [2339, 2344], [2351, 2353], [2355, 2010], [2359, 2361], [2374, 2375], [2401, 2402], [2410, 2411], [2414, 2415], [2420, 2421], [2427, 2428], [2451, 2452], [2454, 2456], [2471, 2473], [2481, 2482], [2484, 2485], [2489, 2010], [2492, 2494], [2499, 2500], [2499, 2596], [2499, 2650], [2499, 2673], [2504, 2415], [2512, 2513], [2515, 2517], [2519, 2520], [2556, 2559], [2595, 2402], [2598, 2129], [2615, 2402], [2629, 2631], [2648, 2494], [2661, 2662], [2671, 2494], [2675, 2677], [2687, 2689], [2693, 2695], [2700, 1019], [2702, 2704], [2721, 2727], [2738, 2689], [2743, 2744], [2749, 2751], [2764, 2767], [2795, 2797], [2825, 2827], [2741, 2704], [2833, 2114], [2837, 2689], [2848, 2704], [2852, 2853], [2857, 2858], [2861, 2862], [2890, 2891], [2912, 2704], [2928, 2689], [2930, 2689], [2926, 2932], [2964, 2965], [2978, 2979], [2989, 2990], [2993, 2995], [3000, 2421], [3001, 2428], [2988, 3004], [3013, 2010], [3015, 2979], [3016, 3017], [3019, 3020], [3038, 3039], [3045, 3046], [3049, 3020], [3050, 3051], [3057, 3058], [3061, 3062], [3064, 3065], [3084, 3085], [3093, 3095], [3100, 3065], [3114, 3115], [3117, 3118], [3132, 3118], [3134, 3118], [3136, 3137], [3147, 3149], [3151, 3152], [3156, 3157], [3171, 3172], [3196, 3197] + ] +} diff --git a/tests/helloworld/jelly.test.ts b/tests/helloworld/jelly.test.ts new file mode 100644 index 0000000..1de9f33 --- /dev/null +++ b/tests/helloworld/jelly.test.ts @@ -0,0 +1,23 @@ +import {options, resetOptions} from "../../src/options"; +import logger from "../../src/misc/logger"; +import {runTest} from "../../src/testing/runtest"; + +beforeEach(() => { + resetOptions(); + logger.transports[0].level = options.loglevel = "error"; +}); + +test("tests/helloworld/app", async () => { + options.callgraphExternal = false; + await runTest("tests/helloworld", "app.js", { + soundness: "tests/helloworld/app.json", + functionInfos: 775, + moduleInfos: 94, + numberOfFunctionToFunctionEdges: 1367, + oneCalleeCalls: 894, + funFound: 181, + funTotal: 200, + callFound: 244, + callTotal: 267 + }); +}); diff --git a/tests/helloworld/package-lock.json b/tests/helloworld/package-lock.json new file mode 100644 index 0000000..8bd1292 --- /dev/null +++ b/tests/helloworld/package-lock.json @@ -0,0 +1,875 @@ +{ + "name": "helloworld", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "helloworld", + "version": "0.0.1", + "dependencies": { + "express": "^4.17.3" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + } + }, + "dependencies": { + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "requires": { + "mime-db": "1.51.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/tests/helloworld/package.json b/tests/helloworld/package.json new file mode 100644 index 0000000..7ee72d5 --- /dev/null +++ b/tests/helloworld/package.json @@ -0,0 +1,7 @@ +{ + "name": "helloworld", + "version": "0.0.1", + "dependencies": { + "express": "^4.17.3" + } +} diff --git a/tests/install.sh b/tests/install.sh new file mode 100755 index 0000000..b42dfe6 --- /dev/null +++ b/tests/install.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +(cd tests/helloworld; npm ci) +(cd tests/mochatest; npm ci) +(cd tests/vulnerabilities; npm ci) diff --git a/tests/micro/accessors.js b/tests/micro/accessors.js new file mode 100644 index 0000000..ef86e13 --- /dev/null +++ b/tests/micro/accessors.js @@ -0,0 +1,17 @@ +function f1() {} + +const obj = { + baz: undefined, + get foo() { + return this.baz; + }, + set bar(x) { + this.baz = x; + } +} + +obj.bar = f1; + +const t1 = obj.foo; +t1(); + diff --git a/tests/micro/accessors.json b/tests/micro/accessors.json new file mode 100644 index 0000000..d64d507 --- /dev/null +++ b/tests/micro/accessors.json @@ -0,0 +1,22 @@ +{ + "entries": ["accessors.js"], + "time": "Thu, 07 Apr 2022 21:26:23 GMT", + "files": [ + "accessors.js" + ], + "functions": { + "1": "0:1:1:18:1", + "2": "0:8:5:10:6", + "4": "0:5:5:7:6", + "7": "0:1:1:1:17" + }, + "calls": { + "6": "0:16:1:16:5" + }, + "fun2fun": [ + [1, 2], [1, 4], [1, 7] + ], + "call2fun": [ + [6, 7] + ] +} diff --git a/tests/micro/accessors2.js b/tests/micro/accessors2.js new file mode 100644 index 0000000..e9d9acd --- /dev/null +++ b/tests/micro/accessors2.js @@ -0,0 +1,22 @@ +const obj = { + baz: function() { + console.log("hello") + }, + get foo() { + this.baz(); + }, + + set s(x) { + this.bar = x; + } +} + +obj.foo; + +obj.s = function() { + console.log("olleh") +} +obj.bar(); + + + diff --git a/tests/micro/arguments.js b/tests/micro/arguments.js new file mode 100644 index 0000000..2b3550e --- /dev/null +++ b/tests/micro/arguments.js @@ -0,0 +1,17 @@ +function f1(x) { + console.log("0"); + if (!x) + return; + arguments[0](); + arguments[1](); + arguments[0] = () => {console.log("3")}; + x(); // TODO: assignments to arguments + arguments.callee(); // TODO: arguments.callee +} +f1(() => {console.log("1")}, () => {console.log("2")}); + +function f2() { + const f = () => arguments[0]; // arrow functions don't have their own 'arguments' + f()(); +} +f2(() => {console.log("4")}); diff --git a/tests/micro/arguments.json b/tests/micro/arguments.json new file mode 100644 index 0000000..9218a73 --- /dev/null +++ b/tests/micro/arguments.json @@ -0,0 +1,33 @@ +{ + "entries": ["arguments.js"], + "time": "Thu, 28 Jul 2022 21:48:06 GMT", + "files": [ + "arguments.js" + ], + "functions": { + "2": "0:1:1:18:1", + "4": "0:1:1:10:2", + "11": "0:11:4:11:28", + "18": "0:11:30:11:54", + "25": "0:7:20:7:44", + "33": "0:13:1:16:2", + "37": "0:14:15:14:33", + "40": "0:17:4:17:28" + }, + "calls": { + "3": "0:11:1:11:55", + "9": "0:5:5:5:19", + "16": "0:6:5:6:19", + "24": "0:8:5:8:8", + "30": "0:9:5:9:23", + "32": "0:17:1:17:29", + "36": "0:15:5:15:8", + "35": "0:15:5:15:10" + }, + "fun2fun": [ + [2, 4], [2, 33], [4, 11], [4, 18], [4, 25], [4, 4], [33, 37], [33, 40] + ], + "call2fun": [ + [3, 4], [9, 11], [16, 18], [24, 25], [30, 4], [32, 33], [36, 37], [35, 40] + ] +} diff --git a/tests/micro/arrays.js b/tests/micro/arrays.js new file mode 100644 index 0000000..5c65b15 --- /dev/null +++ b/tests/micro/arrays.js @@ -0,0 +1,13 @@ +var x = [,() => {}]; +x[42] = () => {}; +x[x] = () => {}; +var y = x[1]; +y(); +var z = x[x]; +z(); + +x.push(() => {}); +var t = x.pop(); +t(); +var t2 = x[3]; +if (t2) t2(); diff --git a/tests/micro/arrays.json b/tests/micro/arrays.json new file mode 100644 index 0000000..b35ab3d --- /dev/null +++ b/tests/micro/arrays.json @@ -0,0 +1,24 @@ +{ + "entries": ["arrays.js"], + "time": "Thu, 07 Jul 2022 20:58:22 GMT", + "files": [ + "arrays.js" + ], + "functions": { + "2": "0:1:1:14:1", + "7": "0:1:11:1:19", + "11": "0:3:8:3:16", + "18": "0:9:8:9:16" + }, + "calls": { + "6": "0:5:1:5:4", + "10": "0:7:1:7:4", + "17": "0:11:1:11:4" + }, + "fun2fun": [ + [2, 7], [2, 11], [2, 18] + ], + "call2fun": [ + [6, 7], [10, 11], [17, 18] + ] +} diff --git a/tests/micro/arrays2.js b/tests/micro/arrays2.js new file mode 100644 index 0000000..fa4a9ef --- /dev/null +++ b/tests/micro/arrays2.js @@ -0,0 +1,20 @@ +var x = [ + () => {console.log("1")}, + () => {console.log("2")}, +]; +x.push(() => {console.log("3")}); +// var a0 = x[0]; +// a0(); +// var t = 1; +// var a1 = x[t]; +// a1(); + +var y = x.map(function(element, index, array) { + element(); + array[2](); + this.p(); + return () => {console.log("4")}; + }, + {p: function() {console.log("5")}}); +var z = y[1]; +z(); \ No newline at end of file diff --git a/tests/micro/arrays2.json b/tests/micro/arrays2.json new file mode 100644 index 0000000..f8d2c0c --- /dev/null +++ b/tests/micro/arrays2.json @@ -0,0 +1,28 @@ +{ + "entries": ["arrays2.js"], + "time": "Tue, 23 Aug 2022 10:36:27 GMT", + "files": [ + "arrays2.js" + ], + "functions": { + "2": "0:1:1:20:5", + "7": "0:12:15:17:6", + "10": "0:2:5:2:29", + "17": "0:5:8:5:32", + "24": "0:18:9:18:38", + "29": "0:3:5:3:29", + "36": "0:16:16:16:40" + }, + "calls": { + "9": "0:13:9:13:18", + "15": "0:14:9:14:19", + "22": "0:15:9:15:17", + "35": "0:20:1:20:4" + }, + "fun2fun": [ + [7, 10], [7, 17], [7, 24], [7, 29], [2, 36] + ], + "call2fun": [ + [9, 10], [9, 29], [9, 17], [15, 17], [22, 24], [35, 36] + ] +} diff --git a/tests/micro/asyncawait.js b/tests/micro/asyncawait.js new file mode 100644 index 0000000..f96f1a8 --- /dev/null +++ b/tests/micro/asyncawait.js @@ -0,0 +1,57 @@ +(async function() { + + const p = new Promise((resolve, reject) => { + resolve(() => { + console.log("resolved"); + }); + }); + + const t1 = await p; + t1(); + + const t2 = await (()=>{console.log("value");}); // can await non-promise values + t2(); + + const f1 = async function() { + return ()=>{console.log("async1");}; // return value is wrapped into a promise + } + const f2 = await f1(); + f2(); + + const f3 = async function() { + return ()=>{console.log("async2");}; // return value is wrapped into a promise + } + f3().then(f4 => { + f4(); + }); + + const f5 = async function*() { + yield ()=>{console.log("async3a");}; // yield value is wrapped into a promise in an iterator + return ()=>{console.log("async3b");}; + } + const f6 = f5(); + f6.next().then(res => { + res.value(); + }); + f6.next().then(res => { + res.value(); + }); + + const f7 = async function*() { + yield* [()=>{console.log("async4a");}]; // yield* values are wrapped into promises in an iterator + return ()=>{console.log("async4b");}; + } + const f8 = f7(); // f8 is an AsyncGenerator + const p2 = f8.next(); // p is a promise + p2.then(res => { + res.value(); + }); + f8.next().then(res => { + res.value(); + }); + + for await (const q of f7()) { + q(); + } + +}()); diff --git a/tests/micro/asyncawait.json b/tests/micro/asyncawait.json new file mode 100644 index 0000000..c6ce709 --- /dev/null +++ b/tests/micro/asyncawait.json @@ -0,0 +1,46 @@ +{ + "entries": ["asyncawait.js"], + "time": "Thu, 24 Nov 2022 15:57:49 GMT", + "files": [ + "asyncawait.js" + ], + "functions": { + "2": "0:1:1:58:1", + "4": "0:1:2:57:2", + "8": "0:3:27:7:6", + "12": "0:4:17:6:10", + "18": "0:12:23:12:50", + "24": "0:15:16:17:6", + "27": "0:16:16:16:44", + "34": "0:21:16:23:6", + "41": "0:28:16:31:6", + "51": "0:40:16:43:6", + "60": "0:24:15:26:6", + "63": "0:22:16:22:44", + "68": "0:33:20:35:6", + "72": "0:29:15:29:44", + "77": "0:36:20:38:6", + "81": "0:30:16:30:45", + "86": "0:46:13:48:6", + "90": "0:41:17:41:46", + "96": "0:49:20:51:6", + "100": "0:42:16:42:45" + }, + "calls": { + "11": "0:10:5:10:9", + "17": "0:13:5:13:9", + "26": "0:19:5:19:9", + "62": "0:25:9:25:13", + "70": "0:34:9:34:20", + "79": "0:37:9:37:20", + "88": "0:47:9:47:20", + "95": "0:54:9:54:12", + "98": "0:50:9:50:20" + }, + "fun2fun": [ + [4, 12], [4, 18], [4, 27], [4, 90], [60, 63], [68, 72], [77, 81], [86, 90], [96, 100] + ], + "call2fun": [ + [11, 12], [17, 18], [26, 27], [62, 63], [70, 72], [79, 81], [88, 90], [95, 90], [98, 100] + ] +} diff --git a/tests/micro/classes.js b/tests/micro/classes.js new file mode 100644 index 0000000..2ca4ee1 --- /dev/null +++ b/tests/micro/classes.js @@ -0,0 +1,159 @@ +function f1() {} +f1(); + +const f2 = function() { return f1; }; +f2(); + +let f3 = () => {}; +f3(); + +function f4(x) { + return x(f1); +} + +f4(f4); + +var t1 = { }; +t1.f5 = () => {}; +t1.f6 = t1.f5; +t1.f6(); + +class C1 { + constructor() { + f1(); + } + f7(y) { + return y(); + } + f8 = () => {return f1;} +} + +let t2 = new C1; + +t2.f7(f2); +let t222 = t2.f8(); + +class C2 { + f8() {} +} +let t3 = new C2; +t3.f8(); + +let C3 = class { + constructor() { + + } + f9() {} +}; +let t4 = new C3; +t4.f9(); + +class C4 { + constructor(x) { + x(); + } +} + +class C5 extends C4 { + constructor(y) { + super(y); + } +} + +const t5 = new C5(f1); + +var F1 = function() {} +let t6 = new F1; + +class C6 { + static staticProperty = (f1(), function() {}); + static staticMethod() { + return f1(); + } + static { + f1(); + } + static { + f1(); + } +} + +C6.staticProperty(); +C6.staticMethod(); + +class C { + toString() { return "foo" }; +} + +let t7 = new C() + "bar"; // implicit call toString + +const obj = { + get property1() {}, + set property2(value) {}, + property3( parameters ) {}, + *generator4( parameters ) {}, + async property5( parameters ) {}, + async* generator6( parameters ) {}, + + get ["property7"]() {}, + set ["property8"](value) {}, + ["property9"]( parameters ) {}, + *["generator10"]( parameters ) {}, + async ["property11"]( parameters ) {}, + async* ["generator12"]( parameters ) {}, +}; + +function F10(x) { + this.f11 = x; +} + +let t8 = new F10(f1); +t8.f11(); + +let x12 = { + f13() {}, + f14: () => {} +} +x12.f13(); +x12.f13(); + +let C10 = class { + f9() {} +}; +let t40 = new C10; +t40.f9(); + +class C11 { + f16() {} +} +class C12 extends C11 { +} +let t41 = new C12; +t41.f16(); + + + +function A(x) { + this.f44 = x; +} + +A.prototype.s1 = function () { + return f2(); +} +A.prototype.s2 = function () { + return f2(); +} + +class D extends A { + constructor(y) { + super(y); + } + s1() { + return f2(); + } +} + +let d = new D(f2); +let t42 = d.s1(); +let t43 = d.s2(); +let t45 = d.f44(); diff --git a/tests/micro/classes.json b/tests/micro/classes.json new file mode 100644 index 0000000..502b5da --- /dev/null +++ b/tests/micro/classes.json @@ -0,0 +1,91 @@ +{ + "entries": ["classes.js"], + "time": "Thu, 07 Apr 2022 20:55:33 GMT", + "files": [ + "classes.js" + ], + "functions": { + "1": "0:1:1:160:1", + "3": "0:1:1:1:17", + "6": "0:4:12:4:37", + "9": "0:7:10:7:18", + "12": "0:10:1:12:2", + "16": "0:17:9:17:17", + "19": "0:28:5:29:2", + "21": "0:21:1:29:2", + "25": "0:25:5:27:6", + "29": "0:28:10:28:28", + "32": "0:36:1:38:2", + "34": "0:37:5:37:12", + "37": "0:42:10:47:2", + "40": "0:46:5:46:12", + "43": "0:57:1:61:2", + "45": "0:51:1:55:2", + "49": "0:65:10:65:23", + "51": "0:69:12:69:51", + "54": "0:73:5:75:6", + "57": "0:76:5:78:6", + "61": "0:69:36:69:49", + "64": "0:70:12:72:6", + "68": "0:84:1:86:2", + "69": "0:85:5:85:32", + "72": "0:106:1:108:2", + "76": "0:114:5:114:13", + "80": "0:120:11:122:2", + "82": "0:121:5:121:12", + "85": "0:129:1:130:2", + "86": "0:126:1:128:2", + "88": "0:127:5:127:13", + "91": "0:147:1:154:2", + "93": "0:136:1:138:2", + "96": "0:151:5:153:6", + "100": "0:143:18:145:2" + }, + "calls": { + "2": "0:2:1:2:5", + "5": "0:5:1:5:5", + "8": "0:8:1:8:5", + "11": "0:14:1:14:7", + "14": "0:11:12:11:17", + "15": "0:19:1:19:8", + "18": "0:31:10:31:16", + "23": "0:23:9:23:13", + "24": "0:33:1:33:10", + "27": "0:26:16:26:19", + "28": "0:34:12:34:19", + "31": "0:39:10:39:16", + "33": "0:40:1:40:8", + "36": "0:48:10:48:16", + "39": "0:49:1:49:8", + "42": "0:63:12:63:22", + "47": "0:53:9:53:12", + "48": "0:66:10:66:16", + "53": "0:69:30:69:34", + "56": "0:74:9:74:13", + "59": "0:77:9:77:13", + "60": "0:81:1:81:20", + "63": "0:82:1:82:18", + "66": "0:71:16:71:20", + "67": "0:88:10:88:17", + "71": "0:110:10:110:21", + "74": "0:111:1:111:9", + "75": "0:117:1:117:10", + "78": "0:118:1:118:10", + "79": "0:123:11:123:18", + "81": "0:124:1:124:9", + "84": "0:131:11:131:18", + "87": "0:132:1:132:10", + "90": "0:156:9:156:18", + "95": "0:157:11:157:17", + "98": "0:152:16:152:20", + "99": "0:158:11:158:17", + "102": "0:144:12:144:16", + "103": "0:159:11:159:18" + }, + "fun2fun": [ + [1, 3], [1, 6], [1, 9], [1, 12], [1, 16], [1, 19], [1, 21], [1, 25], [1, 29], [1, 32], [1, 34], [1, 37], [1, 40], [1, 43], [1, 49], [1, 51], [1, 54], [1, 57], [1, 61], [1, 64], [1, 68], [1, 69], [1, 72], [1, 76], [1, 80], [1, 82], [1, 85], [1, 88], [1, 91], [1, 96], [1, 100], [12, 12], [12, 3], [21, 3], [25, 6], [43, 45], [45, 3], [51, 3], [54, 3], [57, 3], [64, 3], [85, 86], [91, 93], [96, 6], [100, 6] + ], + "call2fun": [ + [2, 3], [5, 6], [8, 9], [11, 12], [14, 12], [14, 3], [15, 16], [18, 19], [18, 21], [23, 3], [24, 25], [27, 6], [28, 29], [31, 32], [33, 34], [36, 37], [39, 40], [42, 43], [47, 3], [48, 49], [53, 3], [56, 3], [59, 3], [60, 61], [63, 64], [66, 3], [67, 68], [71, 72], [74, 3], [75, 76], [78, 76], [79, 80], [81, 82], [84, 85], [87, 88], [90, 91], [95, 96], [98, 6], [99, 100], [102, 6], [103, 6] + ] +} diff --git a/tests/micro/client1.js b/tests/micro/client1.js new file mode 100644 index 0000000..7ca9713 --- /dev/null +++ b/tests/micro/client1.js @@ -0,0 +1,4 @@ +const lib1 = require('./lib1.js'); +const filter = lib1.filter; +console.log(filter(x => x % 2 === 0)([1, 2, 3])); +lib1.obj.foo = 87; diff --git a/tests/micro/client1.json b/tests/micro/client1.json new file mode 100644 index 0000000..baed2c7 --- /dev/null +++ b/tests/micro/client1.json @@ -0,0 +1,27 @@ +{ + "entries": ["client1.js"], + "time": "Tue, 26 Apr 2022 21:43:57 GMT", + "files": [ + "client1.js", + "lib1.js" + ], + "functions": { + "1": "0:1:1:5:1", + "3": "1:1:1:12:1", + "7": "1:1:25:10:2", + "9": "1:2:12:9:6", + "12": "0:3:20:3:36" + }, + "calls": { + "2": "0:1:14:1:34", + "6": "0:3:13:3:37", + "5": "0:3:13:3:48", + "11": "1:5:17:5:28" + }, + "fun2fun": [ + [1, 3], [1, 7], [1, 9], [9, 12] + ], + "call2fun": [ + [2, 3], [6, 7], [5, 9], [11, 12] + ] +} diff --git a/tests/micro/client1b.js b/tests/micro/client1b.js new file mode 100644 index 0000000..19dcad1 --- /dev/null +++ b/tests/micro/client1b.js @@ -0,0 +1,4 @@ +const lib1 = require('lib'); +const filter = lib1.filter; +console.log(filter(x => x % 2 === 0)([1, 2, 3])); +lib1.obj.foo = 87; diff --git a/tests/micro/client2.js b/tests/micro/client2.js new file mode 100644 index 0000000..70047f8 --- /dev/null +++ b/tests/micro/client2.js @@ -0,0 +1,4 @@ +const lib = require('./lib2'); +const arit = new lib.Arit(); + +console.log(arit.sum(1, 2)); diff --git a/tests/micro/client2.json b/tests/micro/client2.json new file mode 100644 index 0000000..1052e9d --- /dev/null +++ b/tests/micro/client2.json @@ -0,0 +1,24 @@ +{ + "entries": ["client2.js"], + "time": "Sat, 30 Jul 2022 19:25:03 GMT", + "files": [ + "client2.js", + "lib2.js" + ], + "functions": { + "2": "0:1:1:5:1", + "4": "1:1:1:6:1", + "13": "1:1:1:1:21", + "20": "1:2:22:2:37" + }, + "calls": { + "11": "0:2:14:2:28", + "18": "0:4:13:4:27" + }, + "fun2fun": [ + [2, 13], [2, 20] + ], + "call2fun": [ + [11, 13], [18, 20] + ] +} diff --git a/tests/micro/client3.js b/tests/micro/client3.js new file mode 100644 index 0000000..7d2f0c3 --- /dev/null +++ b/tests/micro/client3.js @@ -0,0 +1,3 @@ +const lib = require('./lib3'); + +const f = (0, lib.foo)(); diff --git a/tests/micro/client3.json b/tests/micro/client3.json new file mode 100644 index 0000000..3e9fc13 --- /dev/null +++ b/tests/micro/client3.json @@ -0,0 +1,22 @@ +{ + "entries": ["client3.js"], + "time": "Sat, 30 Jul 2022 19:36:33 GMT", + "files": [ + "client3.js", + "lib3.js" + ], + "functions": { + "2": "0:1:1:4:1", + "4": "1:1:1:4:1", + "9": "1:1:22:3:2" + }, + "calls": { + "7": "0:3:11:3:25" + }, + "fun2fun": [ + [2, 9] + ], + "call2fun": [ + [7, 9] + ] +} diff --git a/tests/micro/client4.js b/tests/micro/client4.js new file mode 100644 index 0000000..d2b3990 --- /dev/null +++ b/tests/micro/client4.js @@ -0,0 +1,9 @@ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +const timer_1 = __importDefault(require("./lib4")); +const lib3 = __importDefault(require("./lib3")); + +const timer = new timer_1.default(); + +console.log(`Total analysis time: ${timer.elapsed()}ms`); diff --git a/tests/micro/client4.json b/tests/micro/client4.json new file mode 100644 index 0000000..91e13ac --- /dev/null +++ b/tests/micro/client4.json @@ -0,0 +1,29 @@ +{ + "entries": ["client4.js"], + "time": "Sat, 30 Jul 2022 20:49:46 GMT", + "files": [ + "client4.js", + "lib4.js", + "lib3.js" + ], + "functions": { + "2": "0:1:1:10:1", + "8": "1:1:1:11:1", + "13": "0:1:57:3:2", + "19": "2:1:1:4:1", + "24": "1:2:1:9:2", + "35": "1:6:3:8:4" + }, + "calls": { + "6": "0:4:17:4:51", + "17": "0:5:14:5:48", + "22": "0:7:15:7:36", + "33": "0:9:37:9:52" + }, + "fun2fun": [ + [2, 13], [2, 24], [2, 35] + ], + "call2fun": [ + [6, 13], [17, 13], [22, 24], [33, 35] + ] +} diff --git a/tests/micro/client5.js b/tests/micro/client5.js new file mode 100644 index 0000000..f244829 --- /dev/null +++ b/tests/micro/client5.js @@ -0,0 +1,12 @@ +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); + +const lib5a = __importDefault(require('./lib5a')); +const lib5b = __importDefault(require('./lib5b')); + +console.log(lib5a.default); +lib5a.default(); +lib5b.default(); + diff --git a/tests/micro/client5.json b/tests/micro/client5.json new file mode 100644 index 0000000..bd3f83e --- /dev/null +++ b/tests/micro/client5.json @@ -0,0 +1,29 @@ +{ + "entries": ["client5.js"], + "time": "Sat, 30 Jul 2022 21:00:08 GMT", + "files": [ + "client5.js", + "lib5a.js", + "lib5b.js" + ], + "functions": { + "2": "0:1:1:13:1", + "11": "1:1:1:6:1", + "16": "0:1:57:3:2", + "22": "2:1:1:6:1", + "33": "1:5:19:5:36", + "37": "2:5:19:5:36" + }, + "calls": { + "9": "0:6:15:6:50", + "20": "0:7:15:7:50", + "31": "0:10:1:10:16", + "35": "0:11:1:11:16" + }, + "fun2fun": [ + [2, 16], [2, 33], [2, 37] + ], + "call2fun": [ + [9, 16], [20, 16], [31, 33], [35, 37] + ] +} diff --git a/tests/micro/client6.js b/tests/micro/client6.js new file mode 100644 index 0000000..530e4d1 --- /dev/null +++ b/tests/micro/client6.js @@ -0,0 +1,2 @@ +require("./lib6"); +require("./lib6"); diff --git a/tests/micro/client6.json b/tests/micro/client6.json new file mode 100644 index 0000000..bb46d8c --- /dev/null +++ b/tests/micro/client6.json @@ -0,0 +1,16 @@ +{ + "entries": ["client6.js"], + "time": "Sat, 30 Jul 2022 21:04:13 GMT", + "files": [ + "client6.js", + "lib6.js" + ], + "functions": { + "2": "0:1:1:3:1", + "4": "1:1:1:2:1" + }, + "calls": { + }, + "fun2fun": [], + "call2fun": [] +} diff --git a/tests/micro/client8.js b/tests/micro/client8.js new file mode 100644 index 0000000..1aac9da --- /dev/null +++ b/tests/micro/client8.js @@ -0,0 +1,3 @@ +const w = require('winston'); + +w.Logger(); \ No newline at end of file diff --git a/tests/micro/date.js b/tests/micro/date.js new file mode 100644 index 0000000..4caa733 --- /dev/null +++ b/tests/micro/date.js @@ -0,0 +1,3 @@ +var d = new Date(); +console.log(d.getDay()); +console.log(Date.now()); diff --git a/tests/micro/destructuring.js b/tests/micro/destructuring.js new file mode 100644 index 0000000..cf5c15f --- /dev/null +++ b/tests/micro/destructuring.js @@ -0,0 +1,45 @@ +var x1 = { + a: () => {console.log("1")}, + b: { + c: () => {console.log("2")}, + }, + get bar() { + return () => {console.log("3")}; + } +}; +var {a: y1 = () => {console.log("1b")}, ["a"]: y2, a: y3, b: {c: y4}, bar: y5, d: y6 = () => {console.log("1c")}} = x1; +y1(); +y2(); +y3(); +y4(); +y5(); +y6(); + +let c = { + set foo(q) { + console.log("4"); + q(); + } +}; +({a: c.foo} = x1); + +var x2 = [ + () => {console.log("5")}, + [ + () => {console.log("6")} + ] +]; +var [z1, [z2]] = x2; +z1(); +z2(); + +let d = {}; +[d.baz] = x2; +d.baz(); + +const [x, y] = new Set([() => {}, () => {}]); +x(); +y(); + +// const {a,...others} = {a:1,b:2,c:3}; +// const [a2,...others2] = [1,2,3]; diff --git a/tests/micro/destructuring.json b/tests/micro/destructuring.json new file mode 100644 index 0000000..e2fc25c --- /dev/null +++ b/tests/micro/destructuring.json @@ -0,0 +1,40 @@ +{ + "entries": ["destructuring.js"], + "time": "Thu, 28 Jul 2022 21:43:01 GMT", + "files": [ + "destructuring.js" + ], + "functions": { + "2": "0:1:1:46:1", + "3": "0:6:5:8:6", + "6": "0:2:8:2:32", + "14": "0:4:12:4:36", + "20": "0:7:16:7:40", + "26": "0:10:88:10:113", + "32": "0:19:5:22:6", + "39": "0:27:5:27:29", + "45": "0:29:9:29:33", + "56": "0:40:25:40:33", + "59": "0:40:35:40:43" + }, + "calls": { + "5": "0:11:1:11:5", + "11": "0:12:1:12:5", + "12": "0:13:1:13:5", + "13": "0:14:1:14:5", + "19": "0:15:1:15:5", + "25": "0:16:1:16:5", + "37": "0:21:9:21:12", + "38": "0:33:1:33:5", + "44": "0:34:1:34:5", + "51": "0:38:1:38:8", + "55": "0:41:1:41:4", + "58": "0:42:1:42:4" + }, + "fun2fun": [ + [2, 6], [2, 14], [2, 20], [2, 26], [2, 39], [2, 45], [2, 56], [2, 59], [32, 6] + ], + "call2fun": [ + [5, 6], [11, 6], [12, 6], [13, 14], [19, 20], [25, 26], [37, 6], [38, 39], [44, 45], [51, 39], [55, 56], [58, 59] + ] +} diff --git a/tests/micro/eval.js b/tests/micro/eval.js new file mode 100644 index 0000000..85a4700 --- /dev/null +++ b/tests/micro/eval.js @@ -0,0 +1,13 @@ +eval("console.log('HELLO')"); + +var x = new Function("console.log('WORLD')"); +x(); + +require("./lib1") + +function foo(x) { + return x + 1; +} + +console.log(eval("foo(2)")) +console.log(eval("(x => x + 1)(10)")) \ No newline at end of file diff --git a/tests/micro/eval.json b/tests/micro/eval.json new file mode 100644 index 0000000..8469bc9 --- /dev/null +++ b/tests/micro/eval.json @@ -0,0 +1,23 @@ +{ + "entries": ["eval.js"], + "basedir": "/home/amoeller/git/jelly/tests/micro", + "time": "Fri, 29 Apr 2022 19:31:44 GMT", + "files": [ + "eval.js", + "lib1.js" + ], + "functions": { + "1": "0:1:1:13:38", + "11": "1:1:1:12:1", + "15": "0:8:1:10:2" + }, + "calls": { + "10": "0:6:1:6:18" + }, + "fun2fun": [ + [1, 11] + ], + "call2fun": [ + [10, 11] + ] +} diff --git a/tests/micro/export1.mjs b/tests/micro/export1.mjs new file mode 100644 index 0000000..1a9a919 --- /dev/null +++ b/tests/micro/export1.mjs @@ -0,0 +1,26 @@ +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export + +export { default as function1, + function2 } from './export2.mjs'; + +function cube(x) { + return x * x * x; +} + +const foo = Math.PI + Math.SQRT2; + +var graph = { + options: { + color:'white', + thickness:'2px' + }, + draw: function() { + console.log('From graph draw function'); + } +} + +export { cube, foo, graph }; + +export default function cube2(x) { + return x * x * x; +} diff --git a/tests/micro/export10.mjs b/tests/micro/export10.mjs new file mode 100644 index 0000000..99df349 --- /dev/null +++ b/tests/micro/export10.mjs @@ -0,0 +1 @@ +export * from "./lib10.mjs"; diff --git a/tests/micro/export2.mjs b/tests/micro/export2.mjs new file mode 100644 index 0000000..0fbcd34 --- /dev/null +++ b/tests/micro/export2.mjs @@ -0,0 +1,9 @@ +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export + +export default function function1(x) { + return x * x * x; +} + +export function function2(x) { + return x * x; +} diff --git a/tests/micro/export3a.mjs b/tests/micro/export3a.mjs new file mode 100644 index 0000000..84083e7 --- /dev/null +++ b/tests/micro/export3a.mjs @@ -0,0 +1,3 @@ +let myFunction = function() {}; +let myVariable = 42; +export {myFunction, myVariable}; \ No newline at end of file diff --git a/tests/micro/export3b.mjs b/tests/micro/export3b.mjs new file mode 100644 index 0000000..b0a49d0 --- /dev/null +++ b/tests/micro/export3b.mjs @@ -0,0 +1,5 @@ +let myClass = class { + constructor() { + } +}; +export {myClass}; \ No newline at end of file diff --git a/tests/micro/export3c.mjs b/tests/micro/export3c.mjs new file mode 100644 index 0000000..889823a --- /dev/null +++ b/tests/micro/export3c.mjs @@ -0,0 +1,2 @@ +export { myFunction, myVariable } from './export3a.mjs'; +export { myClass } from './export3b.mjs'; \ No newline at end of file diff --git a/tests/micro/export8.mjs b/tests/micro/export8.mjs new file mode 100644 index 0000000..5f69348 --- /dev/null +++ b/tests/micro/export8.mjs @@ -0,0 +1,39 @@ +// Exporting individual features +export let name1, name2, nameN; // also var, const +export let name11 = 1, name12 = 2, name1N; // also var, const + +export function functionName(){} + +export class ClassName {} + +var name31, name32, name3N, variable1, variable2, name4N; + +// Export list +export { name31, name32, name3N }; + +// Renaming exports +export { variable1 as name41, variable2 as name42, name4N }; +//// writing to exports.name1 = name1, exportsname42 = variable2, etc. + +// Exporting destructured assignments with renaming +export const { name51, name52: bar } = o; +export const [ name61, name62 ] = array; +///// handled by existing desugaring + +// Default exports +//export default expression; +//export default function () { } // also class, function* +//export default function name71() { } // also class, function* +//export { name1 as default}; +////////// writing to exports.default = expression, etc. + +// Aggregating modules +export * from "foo"; // does not set the default export +////// imports "foo", then copies all properties with Object.key, forEach, defineProperty!!! + +//export * as name81 from "foo"; // ECMAScript® 2O20 +export { name91, name92, name9N } from "foo"; +//// imports "foo", exports its name91 property, etc. +export { import1 as name01, import2 as name02, name0N } from "foo"; +export { default } from "foo"; +/// imports "foo", exports its default propert \ No newline at end of file diff --git a/tests/micro/export9.mjs b/tests/micro/export9.mjs new file mode 100644 index 0000000..1b2ffa3 --- /dev/null +++ b/tests/micro/export9.mjs @@ -0,0 +1,5 @@ +export default class { + constructor(f) { + f(); + } +} diff --git a/tests/micro/fun.js b/tests/micro/fun.js new file mode 100644 index 0000000..c86895a --- /dev/null +++ b/tests/micro/fun.js @@ -0,0 +1,7 @@ +var x = {a: function() { console.log("1"); }}; +var f = function() {return this.a;}.bind(x); +f()(); + +function foo(b) {return b;} +var g = foo.call(null, () => { console.log("2"); }) +g() diff --git a/tests/micro/fun.json b/tests/micro/fun.json new file mode 100644 index 0000000..abe0fa8 --- /dev/null +++ b/tests/micro/fun.json @@ -0,0 +1,25 @@ +{ + "entries": ["fun.js"], + "time": "Sat, 30 Jul 2022 13:35:11 GMT", + "files": [ + "fun.js" + ], + "functions": { + "2": "0:1:1:8:1", + "7": "0:2:9:2:36", + "10": "0:1:13:1:45", + "17": "0:5:1:5:28", + "20": "0:6:24:6:51" + }, + "calls": { + "6": "0:3:1:3:4", + "5": "0:3:1:3:6", + "19": "0:7:1:7:4" + }, + "fun2fun": [ + [2, 7], [2, 10], [2, 20] + ], + "call2fun": [ + [6, 7], [5, 10], [19, 20] + ] +} diff --git a/tests/micro/generators.js b/tests/micro/generators.js new file mode 100644 index 0000000..82d74f7 --- /dev/null +++ b/tests/micro/generators.js @@ -0,0 +1,78 @@ +function* gen() { // return the function's Iterator object + yield () => {console.log("1")}; // write result value to the Iterator object + yield* gen2(); + yield* [() => {console.log("3")}, () => {console.log("4")}]; // like for-in... +} + +function* gen2() { + yield () => {console.log("2")}; +} + +const x = gen(); +const v1 = x.next(); +v1.value(); +for (const v of x) + v(); + +var q = [() => {console.log("5")}]; +const y = q.values(); +const v2 = y.next(); +v2.value(); + +function* gen3() { + return () => {console.log("6")}; // write result value to the Iterator object +} +const z = gen3(); +const v3 = z.next(); +v3.value(); + +function* gen4() { + const q = yield; // yield gives value from .next + q(); +} +const t = gen4(); +t.next(); +t.next(() => {console.log("7")}); + +function* gen5() { + yield () => {console.log("8")}; + return () => {console.log("9")}; +} +const u = gen5(); +u.next().value(); +u.next().value(); + +const obj = { + *gen6 () { + yield () => {console.log("10")}; + } +} +const e = obj.gen6() +e.next().value(); + +class Foo { + *gen7() { + yield () => {console.log("11")}; + } +} +const f = new Foo(); +const g = f.gen7(); +g.next().value(); + +function* gen8() { + yield* [() => {console.log("12")}, () => {console.log("13")}]; + return () => {console.log("14")}; +} +function* gen9() { + const t = yield* gen8(); // yield* gives returned value + t(); + return t; +} +const i1 = gen9(); +i1.next().value(); +i1.next().value(); +i1.next().value(); + +// TODO: +// x.return(() => {}); +// x.throw(() => {}); diff --git a/tests/micro/generators.json b/tests/micro/generators.json new file mode 100644 index 0000000..77d9dda --- /dev/null +++ b/tests/micro/generators.json @@ -0,0 +1,56 @@ +{ + "entries": ["generators.js"], + "time": "Tue, 26 Jul 2022 08:16:30 GMT", + "files": [ + "generators.js" + ], + "functions": { + "2": "0:1:1:79:1", + "6": "0:1:1:5:2", + "10": "0:2:11:2:35", + "16": "0:7:1:9:2", + "19": "0:8:11:8:35", + "24": "0:4:13:4:37", + "29": "0:4:39:4:63", + "40": "0:17:10:17:34", + "48": "0:22:1:24:2", + "52": "0:23:12:23:36", + "60": "0:29:1:32:2", + "65": "0:35:8:35:32", + "74": "0:37:1:40:2", + "77": "0:38:11:38:35", + "86": "0:39:12:39:36", + "96": "0:46:5:48:6", + "99": "0:47:15:47:40", + "105": "0:53:1:57:2", + "111": "0:54:5:56:6", + "114": "0:55:15:55:40", + "123": "0:66:1:70:2", + "126": "0:62:1:65:2", + "129": "0:63:13:63:38", + "138": "0:63:40:63:65", + "147": "0:64:12:64:37" + }, + "calls": { + "8": "0:13:1:13:11", + "18": "0:15:5:15:8", + "38": "0:20:1:20:11", + "50": "0:27:1:27:11", + "64": "0:31:5:31:8", + "71": "0:42:1:42:17", + "82": "0:43:1:43:17", + "93": "0:51:1:51:17", + "104": "0:58:11:58:20", + "108": "0:60:1:60:17", + "120": "0:72:1:72:18", + "134": "0:73:1:73:18", + "146": "0:68:5:68:8", + "143": "0:74:1:74:18" + }, + "fun2fun": [ + [2, 10], [2, 19], [2, 24], [2, 29], [2, 40], [2, 52], [2, 77], [2, 86], [2, 99], [2, 105], [2, 114], [2, 129], [2, 138], [2, 147], [60, 65], [123, 147] + ], + "call2fun": [ + [8, 10], [18, 19], [18, 24], [18, 29], [38, 40], [50, 52], [64, 65], [71, 77], [82, 86], [93, 99], [104, 105], [108, 114], [120, 129], [134, 138], [146, 147], [143, 147] + ] +} diff --git a/tests/micro/globals/lib1/lib.js b/tests/micro/globals/lib1/lib.js new file mode 100644 index 0000000..1abcfc0 --- /dev/null +++ b/tests/micro/globals/lib1/lib.js @@ -0,0 +1,5 @@ +global.foo = { + bar: function() {} +} + +globalThis.baz = () => {}; diff --git a/tests/micro/globals/package.json b/tests/micro/globals/package.json new file mode 100644 index 0000000..d1d7386 --- /dev/null +++ b/tests/micro/globals/package.json @@ -0,0 +1,4 @@ +{ + "name": "lib1", + "version": "0.1.2", + "main": "./lib1/lib.js"} \ No newline at end of file diff --git a/tests/micro/globals/sample/app.js b/tests/micro/globals/sample/app.js new file mode 100644 index 0000000..6fc9ec3 --- /dev/null +++ b/tests/micro/globals/sample/app.js @@ -0,0 +1,4 @@ +require('lib1') + +foo.bar(); +baz(); \ No newline at end of file diff --git a/tests/micro/globals/sample/package.json b/tests/micro/globals/sample/package.json new file mode 100644 index 0000000..76ed91b --- /dev/null +++ b/tests/micro/globals/sample/package.json @@ -0,0 +1,7 @@ +{ + "name": "app", + "version": "0.0.0", + "dependencies": { + "sample": "~0.1.2" + } +} \ No newline at end of file diff --git a/tests/micro/import1.mjs b/tests/micro/import1.mjs new file mode 100644 index 0000000..9a15216 --- /dev/null +++ b/tests/micro/import1.mjs @@ -0,0 +1,22 @@ +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export + +import cube2, { cube, foo, graph, function1, function2 } from './export1.mjs'; + +graph.options = { + color:'blue', + thickness:'3px' +}; + +graph.draw(); +console.log(cube(3)); +console.log(foo); + +console.log(cube2(3)); + +console.log(function1(2)); +console.log(function2(2)); + +console.log(import.meta); + +// see also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import + diff --git a/tests/micro/import10.mjs b/tests/micro/import10.mjs new file mode 100644 index 0000000..caf17f9 --- /dev/null +++ b/tests/micro/import10.mjs @@ -0,0 +1,2 @@ +import {foo} from "./export10.mjs" +foo(); diff --git a/tests/micro/import11.mjs b/tests/micro/import11.mjs new file mode 100644 index 0000000..f053941 --- /dev/null +++ b/tests/micro/import11.mjs @@ -0,0 +1,4 @@ +import { Subject } from 'rxjs/Subject'; + +Subject().take(1); +new Subject().take(1); diff --git a/tests/micro/import3.mjs b/tests/micro/import3.mjs new file mode 100644 index 0000000..7d9c9f9 --- /dev/null +++ b/tests/micro/import3.mjs @@ -0,0 +1,4 @@ +import { myFunction, myVariable, myClass } from './export3c.mjs' + +myFunction(); +console.log(myVariable, new myClass); diff --git a/tests/micro/import4.mjs b/tests/micro/import4.mjs new file mode 100644 index 0000000..dd734e3 --- /dev/null +++ b/tests/micro/import4.mjs @@ -0,0 +1,26 @@ +import defaultExport1 from "module-name1"; +import * as name1 from "module-name2"; +import { export1 } from "module-name3"; +import { export2 as alias1 } from "module-name4"; +import { export3, export4 } from "module-name5"; +import { export5, export6 as alias2 , export7 as alias3 } from "module-name6"; +import defaultExport2, { export8 , export9 } from "module-name7"; +import defaultExport3, * as name2 from "module-name8"; +import "module-name9"; +var p = import("module-name10"); + +var x1 = defaultExport1; +var x2 = defaultExport2; +var x3 = defaultExport3; +var x4 = name1; +var x5 = name2; +var x6 = export1; +var x7 = export3; +var x8 = export4; +var x9 = export5; +var x10 = export8; +var x11 = export9; +var x12 = alias1; +var x13 = alias2; +var x14 = alias3; +var x15 = p; diff --git a/tests/micro/import7.mjs b/tests/micro/import7.mjs new file mode 100644 index 0000000..4e54043 --- /dev/null +++ b/tests/micro/import7.mjs @@ -0,0 +1,3 @@ +import w from './lib7.mjs'; + +w.foo(); diff --git a/tests/micro/import9.mjs b/tests/micro/import9.mjs new file mode 100644 index 0000000..cb9697f --- /dev/null +++ b/tests/micro/import9.mjs @@ -0,0 +1,3 @@ +import c from "./export9.mjs" + +new c(() => {console.log("@")}); \ No newline at end of file diff --git a/tests/micro/iterators.js b/tests/micro/iterators.js new file mode 100644 index 0000000..dc5df29 --- /dev/null +++ b/tests/micro/iterators.js @@ -0,0 +1,99 @@ +// Array + +const array = [ + () => {}, + () => {} +]; +array.push(() => {}); +for (const f of array) + f(); +for (const f of array.values()) + f(); + +array.forEach((v, k, ss) => { + v(); + ss.includes(v); +}, {}); +const t9 = array.pop(); +t9(); +const t13 = array.at(0); +t13(); +array.fill(() => {}, 5, 10); + +array.every((val, idx, arr) => {}, {}); +array.some((val, idx, arr) => {}, {}); +const t10 = array.find((val, idx, arr) => {}, {}); +array.findIndex((val, idx, arr) => {}, {}); +const a10 = array.filter((val, idx, arr) => {}, {}); +a10.push(); +const a11 = array.map((val, idx, arr) => {}, {}); +a11.push(); +const t11 = array.reduce((previousValue, currentValue, currentIndex, arr) => { + currentValue(); + arr.includes(); + return previousValue; +}, {}); +console.log(t11); +const t12 = array.reduceRight((previousValue, currentValue, currentIndex, arr) => { + currentValue(); + arr.includes(); + return previousValue; +}, {}); +console.log(t12); +const a12 = array.sort((a, b) => {}); +a12.includes(); +const a14 = array.concat(() => {}, () => {}); +a14.includes(); +const a15 = array.flatMap((value, index, array) => {}, {}); +a15.includes(); +const a16 = array.slice(5, 7); +a16.includes(); +const a17 = array.splice(3, 2); +a17.includes(); +// TODO: array.keys(); +// TODO: array.copyWithin(); +// TODO: array.flat(); +// TODO: Array.from(); +// TODO: Array.of(); + +// Set + +const set = new Set; +set.add(() => {}); +set.add(() => {}); +for (const f of set) + f(); +for (const f of set.values()) + f(); +for (const [v1, v2] of set.entries()) { + v1(); + v2(); +} +set.forEach((v, k, ss) => { + v(); + k(); + ss.has(k); +}, {}); + +// Map + +const map = new Map; +map.set(() => {}, () => {}); +map.set(() => {}, () => {}); +for (const [k,v] of map) { + k(); + v(); +} +for (const [k,v] of map.entries()) { + k(); + v(); +} +for (const k of map.keys()) + k(); +for (const v of map.values()) + v(); +map.forEach((v, k, ss) => { + v(); + k(); + ss.has(k); +}, {}); diff --git a/tests/micro/iterators.json b/tests/micro/iterators.json new file mode 100644 index 0000000..e866da1 --- /dev/null +++ b/tests/micro/iterators.json @@ -0,0 +1,61 @@ +{ + "entries": ["iterators.js"], + "time": "Sun, 10 Jul 2022 14:53:10 GMT", + "files": [ + "iterators.js" + ], + "functions": { + "2": "0:1:1:100:1", + "6": "0:4:5:4:13", + "8": "0:5:5:5:13", + "10": "0:7:12:7:20", + "17": "0:13:15:16:2", + "32": "0:23:13:23:34", + "36": "0:24:12:24:33", + "40": "0:25:24:25:45", + "44": "0:26:17:26:38", + "48": "0:27:26:27:47", + "54": "0:29:23:29:44", + "60": "0:31:26:35:2", + "70": "0:37:31:41:2", + "80": "0:43:24:43:36", + "90": "0:47:27:47:54", + "109": "0:62:9:62:17", + "111": "0:63:9:63:17", + "122": "0:72:13:76:2", + "135": "0:81:9:81:17", + "138": "0:81:19:81:27", + "140": "0:82:9:82:17", + "142": "0:82:19:82:27", + "156": "0:95:13:99:2" + }, + "calls": { + "5": "0:9:5:9:8", + "14": "0:11:5:11:8", + "19": "0:14:5:14:8", + "24": "0:18:1:18:5", + "27": "0:20:1:20:6", + "62": "0:32:5:32:19", + "72": "0:38:5:38:19", + "108": "0:65:5:65:8", + "115": "0:67:5:67:8", + "118": "0:69:5:69:9", + "119": "0:70:5:70:9", + "124": "0:73:5:73:8", + "125": "0:74:5:74:8", + "134": "0:84:5:84:8", + "137": "0:85:5:85:8", + "146": "0:88:5:88:8", + "147": "0:89:5:89:8", + "150": "0:92:5:92:8", + "153": "0:94:5:94:8", + "158": "0:96:5:96:8", + "159": "0:97:5:97:8" + }, + "fun2fun": [ + [2, 6], [2, 8], [2, 10], [2, 109], [2, 111], [2, 135], [2, 138], [2, 140], [2, 142], [17, 6], [17, 8], [17, 10], [60, 6], [60, 8], [70, 8], [70, 6], [122, 109], [122, 111], [156, 138], [156, 135], [156, 142], [156, 140] + ], + "call2fun": [ + [5, 6], [5, 8], [5, 10], [14, 6], [14, 8], [14, 10], [19, 6], [19, 8], [19, 10], [24, 10], [27, 6], [62, 6], [62, 8], [72, 8], [72, 6], [108, 109], [108, 111], [115, 109], [115, 111], [118, 109], [118, 111], [119, 109], [119, 111], [124, 109], [124, 111], [125, 109], [125, 111], [134, 135], [134, 140], [137, 138], [137, 142], [146, 135], [146, 140], [147, 138], [147, 142], [150, 135], [150, 140], [153, 138], [153, 142], [158, 138], [158, 142], [159, 135], [159, 140] + ] +} diff --git a/tests/micro/jelly.test.ts b/tests/micro/jelly.test.ts new file mode 100644 index 0000000..7264cc2 --- /dev/null +++ b/tests/micro/jelly.test.ts @@ -0,0 +1,604 @@ +import {options, resetOptions} from "../../src/options"; +import logger from "../../src/misc/logger"; +import {runTest} from "../../src/testing/runtest"; + +beforeEach(() => { + resetOptions(); + logger.transports[0].level = options.loglevel = "error"; +}); + +test("tests/micro/classes", async () => { + await runTest("tests/micro", "classes.js", { + soundness: "tests/micro/classes.json", + functionInfos: 39, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 34, + oneCalleeCalls: 32, + funFound: 32, + funTotal: 45, + callFound: 36, + callTotal: 41 + }); +}); + +test("tests/micro/accessors", async () => { + await runTest("tests/micro", "accessors.js", { + soundness: "tests/micro/accessors.json", + functionInfos: 3, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 3, + oneCalleeCalls: 3, + funFound: 3, + funTotal: 3, + callFound: 1, + callTotal: 1 + }); +}); + +test("tests/micro/accessors2", async () => { + await runTest("tests/micro", "accessors2.js", { + functionInfos: 4, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 4, + oneCalleeCalls: 4 + }); +}); + +test("tests/micro/eval", async () => { + await runTest("tests/micro", "eval.js", { + soundness: "tests/micro/eval.json", + functionInfos: 3, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 0, + oneCalleeCalls: 0, + funFound: 1, + funTotal: 1, + callFound: 1, + callTotal: 1 + }); +}); + +test("tests/micro/client1", async () => { + await runTest("tests/micro", "client1.js", { + soundness: "tests/micro/client1.json", + functionInfos: 3, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 3, + oneCalleeCalls: 3, + funFound: 4, + funTotal: 4, + callFound: 4, + callTotal: 4 + }); +}); + +test("tests/micro/client1b", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "client1b.js", { + patterns: ["tests/micro/patterns.json"], + functionInfos: 1, + moduleInfos: 1, + matches: {total: 6} + }); +}); + +test("tests/micro/client2", async () => { + await runTest("tests/micro", "client2.js", { + soundness: "tests/micro/client2.json", + functionInfos: 3, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 2, + oneCalleeCalls: 2, + funFound: 2, + funTotal: 2, + callFound: 2, + callTotal: 2 + }); +}); + +test("tests/micro/client3", async () => { + await runTest("tests/micro", "client3.js", { + soundness: "tests/micro/client3.json", + functionInfos: 1, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 1, + oneCalleeCalls: 1, + funFound: 1, + funTotal: 1, + callFound: 1, + callTotal: 1 + }); +}); + +test("tests/micro/client4", async () => { + await runTest("tests/micro", "client4.js", { + soundness: "tests/micro/client4.json", + functionInfos: 4, + moduleInfos: 3, + numberOfFunctionToFunctionEdges: 3, + oneCalleeCalls: 4, + funFound: 3, + funTotal: 3, + callFound: 4, + callTotal: 4 + }); +}); + +test("tests/micro/client5", async () => { + await runTest("tests/micro", "client5.js", { + soundness: "tests/micro/client5.json", + functionInfos: 3, + moduleInfos: 3, + numberOfFunctionToFunctionEdges: 3, + oneCalleeCalls: 2, + funFound: 3, + funTotal: 3, + callFound: 4, + callTotal: 4 + }); +}); + +test("tests/micro/client6", async () => { + await runTest("tests/micro", "client6.js", { + soundness: "tests/micro/client6.json", + functionInfos: 0, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 0, + oneCalleeCalls: 0, + funFound: 0, + funTotal: 0, + callFound: 0, + callTotal: 0 + }); +}); + +test("tests/micro/client8", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "client8.js", { + patterns: ["tests/micro/patterns8.json"], + functionInfos: 0, + moduleInfos: 1, + matches: {total: 1} + }); +}); + +test("tests/micro/arrays", async () => { + await runTest("tests/micro", "arrays.js", { + soundness: "tests/micro/arrays.json", + functionInfos: 4, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 4, + oneCalleeCalls: 1, + funFound: 3, + funTotal: 3, + callFound: 3, + callTotal: 3 + }); +}); + +test("tests/micro/arrays2", async () => { + await runTest("tests/micro", "arrays2.js", { + soundness: "tests/micro/arrays2.json", + functionInfos: 6, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 6, + oneCalleeCalls: 4, + funFound: 5, + funTotal: 5, + callFound: 6, + callTotal: 6 + }); +}); + +test("tests/micro/iterators", async () => { + await runTest("tests/micro", "iterators.js", { + soundness: "tests/micro/iterators.json", + functionInfos: 25, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 41, + oneCalleeCalls: 13, + funFound: 22, + funTotal: 22, + callFound: 43, + callTotal: 43 + }); +}); + +test("tests/micro/more1", async () => { + await runTest("tests/micro", "more1.js", { + soundness: "tests/micro/more1.json", + functionInfos: 19, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 15, + oneCalleeCalls: 2, + funFound: 14, + funTotal: 20, + callFound: 20, + callTotal: 29 + }); +}); + +test("tests/micro/generators", async () => { + await runTest("tests/micro", "generators.js", { + soundness: "tests/micro/generators.json", + functionInfos: 23, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 26, + oneCalleeCalls: 14, + funFound: 15, + funTotal: 16, + callFound: 15, + callTotal: 16 + }); +}); + +test("tests/micro/arguments", async () => { + await runTest("tests/micro", "arguments.js", { + soundness: "tests/micro/arguments.json", + functionInfos: 7, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 7, + oneCalleeCalls: 6, + funFound: 7, + funTotal: 8, + callFound: 6, + callTotal: 8 + }); +}); + +test("tests/micro/destructuring", async () => { + await runTest("tests/micro", "destructuring.js", { + soundness: "tests/micro/destructuring.json", + functionInfos: 11, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 10, + oneCalleeCalls: 11, + funFound: 7, + funTotal: 9, + callFound: 10, + callTotal: 12 + }); +}); + +test("tests/micro/ts", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "ts.ts", { + patterns: ["tests/micro/ts-patterns.json"], + matches: {total: 1} + }); +}); + +test("tests/micro/globals", async () => { + await runTest("tests/micro/globals", ["sample/app.js", "lib1/lib.js"], { + functionInfos: 2, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 2, + oneCalleeCalls: 2 + }); +}); + +test("tests/micro/oneshot", async () => { + await runTest("tests/micro", "oneshot.js", { + soundness: "tests/micro/oneshot.json", + functionInfos: 2, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 1, + oneCalleeCalls: 1, + funFound: 1, + funTotal: 1, + callFound: 1, + callTotal: 1 + }); +}); + +test("tests/micro/low", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "low.ts", { + patterns: ["tests/micro/lowpatterns.json"], + functionInfos: 0, + moduleInfos: 1, + matches: {total: 3, low: 1} + }) +}); + +test("tests/micro/fun", async () => { + await runTest("tests/micro", "fun.js", { + soundness: "tests/micro/fun.json", + functionInfos: 4, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 1, + oneCalleeCalls: 1, + funFound: 1, + funTotal: 3, + callFound: 1, + callTotal: 3 + }); +}); + +test("tests/micro/obj", async () => { + await runTest("tests/micro", "obj.js", { + soundness: "tests/micro/obj.json", + functionInfos: 1, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 1, + oneCalleeCalls: 1, + funFound: 1, + funTotal: 1, + callFound: 1, + callTotal: 1 + }); +}); + +test("tests/micro/mix", async () => { + await runTest("tests/micro", "mix.js", { + soundness: "tests/micro/mix.json", + functionInfos: 3, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 3, + oneCalleeCalls: 3, + funFound: 3, + funTotal: 3, + callFound: 3, + callTotal: 3 + }); +}); + +test("tests/micro/templateliterals", async () => { + await runTest("tests/micro", "templateliterals.js", { + soundness: "tests/micro/templateliterals.json", + functionInfos: 5, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 5, + oneCalleeCalls: 5, + funFound: 4, + funTotal: 4, + callFound: 3, + callTotal: 4 + }); +}); + +test("tests/micro/rest", async () => { + await runTest("tests/micro", "rest.js", { + soundness: "tests/micro/rest.json", + functionInfos: 21, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 21, + oneCalleeCalls: 18, + funFound: 17, + funTotal: 19, + callFound: 21, + callTotal: 23 + }); +}); + +test("tests/micro/rest2", async () => { + await runTest("tests/micro", "rest2.js", { + soundness: "tests/micro/rest2.json", + functionInfos: 2, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 2, + oneCalleeCalls: 2, + funFound: 2, + funTotal: 2, + callFound: 2, + callTotal: 2 + }); +}); + +test("tests/micro/rxjs", async () => { + options.ignoreUnresolved = true; + await runTest("tests/micro", "rxjs1.js", { + patterns: ["tests/micro/rxjs.json"], + functionInfos: 1, + moduleInfos: 2, + matches: {total: 1} + }); +}); + +test("tests/micro/import1", async () => { + await runTest("tests/micro", "import1.mjs", { + functionInfos: 5, + moduleInfos: 3, + numberOfFunctionToFunctionEdges: 5, + oneCalleeCalls: 5 + }); +}); + +test("tests/micro/import3", async () => { + await runTest("tests/micro", "import3.mjs", { + functionInfos: 2, + moduleInfos: 4, + numberOfFunctionToFunctionEdges: 2, + oneCalleeCalls: 2 + }); +}); + +test("tests/micro/import7", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "import7.mjs", { + patterns: ["tests/micro/patterns7.json"], + functionInfos: 0, + moduleInfos: 2, + matches: {total: 2} + }); +}); + +test("tests/micro/import9", async () => { + await runTest("tests/micro", "import9.mjs", { + functionInfos: 2, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 2, + oneCalleeCalls: 2 + }); +}); + +test("tests/micro/import10", async () => { + await runTest("tests/micro", "import10.mjs", { + functionInfos: 1, + moduleInfos: 3, + numberOfFunctionToFunctionEdges: 1, + oneCalleeCalls: 1 + }); +}); + +test("tests/micro/import11", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "import11.mjs", { + patterns: ["tests/micro/patterns11.json"], + functionInfos: 0, + moduleInfos: 1, + matches: {total: 2} + }); +}); + +test("tests/micro/this", async () => { + await runTest("tests/micro", "this.js", { + functionInfos: 5, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 4, + oneCalleeCalls: 4 + }); +}); + + +test("tests/micro/prototypes", async () => { + await runTest("tests/micro", "prototypes.js", { + functionInfos: 2, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 1, + oneCalleeCalls: 1 + }); +}); + +test("tests/micro/match1", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match1.js", { + patterns: ["tests/micro/match1-patterns.json"], + matches: {total: 1} + }); +}); + +test("tests/micro/match2", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match2.js", { + patterns: ["tests/micro/match2-patterns.json"], + matches: {total: 1} + }); +}); + +test("tests/micro/match3", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match3.js", { + patterns: ["tests/micro/match3-patterns.json"], + matches: {total: 1} + }); +}); + +test("tests/micro/match4", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match4.js", { + patterns: ["tests/micro/match4-patterns.json"], + matches: {total: 1} + }); +}); + +test("tests/micro/match5", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match5.js", { + patterns: ["tests/micro/match5-patterns.json"], + matches: {total: 4} + }); +}); + +test("tests/micro/match6", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match6.js", { + patterns: ["tests/micro/match6-patterns.json"], + matches: {total: 1} + }); +}); + +test("tests/micro/match7", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match7.js", { + patterns: ["tests/micro/match7-patterns.json"], + matches: {total: 4, low: 0} + }); +}); + +test("tests/micro/match8", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match8.ts", { + patterns: ["tests/micro/match8-patterns.json"], + matches: {total: 1, low: 0} + }); +}); + +test("tests/micro/match9", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match9.ts", { + patterns: ["tests/micro/match9-patterns.json"], + matches: {total: 1, low: 0} + }); +}); + +test("tests/micro/match10", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match10.ts", { + patterns: ["tests/micro/match10-patterns.json"], + matches: {total: 1, low: 1} + }); +}); + +test("tests/micro/match11", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match11.ts", { + patterns: ["tests/micro/match11-patterns.json"], + matches: {total: 2, low: 1} // TODO: high confidence match with filter is maybe? + }); +}); + +test("tests/micro/match12", async () => { + options.ignoreDependencies = true; + await runTest("tests/micro", "match12.ts", { + patterns: ["tests/micro/match12-patterns.json"], + matches: {total: 1, low: 0} // FIXME: bad source location due to Babel transformation + }); +}); + +test("tests/micro/promises", async () => { + options.callgraphNative = false; + await runTest("tests/micro", "promises.js", { + soundness: "tests/micro/promises.json", + functionInfos: 40, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 25, + oneCalleeCalls: 14, + funFound: 24, + funTotal: 28, + callFound: 24, + callTotal: 28 + }); +}); + +test("tests/micro/asyncawait", async () => { + options.callgraphNative = false; + await runTest("tests/micro", "asyncawait.js", { + soundness: "tests/micro/asyncawait.json", + functionInfos: 19, + moduleInfos: 1, + numberOfFunctionToFunctionEdges: 19, + oneCalleeCalls: 10, + funFound: 9, + funTotal: 9, + callFound: 9, + callTotal: 9 + }); +}); + +test("tests/micro/jsx", async () => { + options.ignoreUnresolved = true; + await runTest("tests/micro", "jsx.js", { + apiUsageAccessPathPatternsAtNodes: 6 + }); +}); diff --git a/tests/micro/jsx.js b/tests/micro/jsx.js new file mode 100644 index 0000000..70ec9f4 --- /dev/null +++ b/tests/micro/jsx.js @@ -0,0 +1,3 @@ +import {Foo} from "foo"; +var x = {84/2}; +var y = baz ; diff --git a/tests/micro/lib1.js b/tests/micro/lib1.js new file mode 100644 index 0000000..e5df02c --- /dev/null +++ b/tests/micro/lib1.js @@ -0,0 +1,11 @@ +module.exports.filter = (iteratee) => { + return (arr) => { + const res = []; + for (var x of arr) { + if (iteratee(x)) + res.push(x); + } + return res; + }; +} +module.exports.obj = {foo: 17}; diff --git a/tests/micro/lib10.mjs b/tests/micro/lib10.mjs new file mode 100644 index 0000000..488de14 --- /dev/null +++ b/tests/micro/lib10.mjs @@ -0,0 +1,3 @@ +export function foo() { + console.log("foo"); +} diff --git a/tests/micro/lib2.js b/tests/micro/lib2.js new file mode 100644 index 0000000..b6013dc --- /dev/null +++ b/tests/micro/lib2.js @@ -0,0 +1,5 @@ +function Arit () { } +Arit.prototype.sum = (x, y) => x + y; +Arit.prototype.mul = (x, y) => x * y; + +module.exports.Arit = Arit; diff --git a/tests/micro/lib3.js b/tests/micro/lib3.js new file mode 100644 index 0000000..99a7b66 --- /dev/null +++ b/tests/micro/lib3.js @@ -0,0 +1,3 @@ +module.exports.foo = () => { + console.log("hello"); +}; diff --git a/tests/micro/lib4.js b/tests/micro/lib4.js new file mode 100644 index 0000000..0c973df --- /dev/null +++ b/tests/micro/lib4.js @@ -0,0 +1,10 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +class Timer { + constructor() { + this.startTime = new Date(); + } + elapsed() { + return new Date().getTime() - this.startTime.getTime(); + } +} +exports.default = Timer; diff --git a/tests/micro/lib5a.js b/tests/micro/lib5a.js new file mode 100644 index 0000000..65cbf1f --- /dev/null +++ b/tests/micro/lib5a.js @@ -0,0 +1,5 @@ +Object.defineProperty(exports, "__esModule", { + value: true +}); + +exports.default = function foo() {} diff --git a/tests/micro/lib5b.js b/tests/micro/lib5b.js new file mode 100644 index 0000000..c44f242 --- /dev/null +++ b/tests/micro/lib5b.js @@ -0,0 +1,5 @@ +Object.defineProperty(exports, "__esModule", { + value: true +}); + +exports.default = function bar() {} diff --git a/tests/micro/lib6.js b/tests/micro/lib6.js new file mode 100644 index 0000000..b6211f2 --- /dev/null +++ b/tests/micro/lib6.js @@ -0,0 +1 @@ +console.log("Hello!"); diff --git a/tests/micro/lib7.mjs b/tests/micro/lib7.mjs new file mode 100644 index 0000000..732def7 --- /dev/null +++ b/tests/micro/lib7.mjs @@ -0,0 +1,5 @@ +export default { + foo: function () { + console.log("hello!"); + } +} diff --git a/tests/micro/low.ts b/tests/micro/low.ts new file mode 100644 index 0000000..750348d --- /dev/null +++ b/tests/micro/low.ts @@ -0,0 +1,16 @@ +var x1; +x1 = require("foo"); +x1 = require("bar"); +x1 = {} +var y1 = x1.f.g; + +var x2; +x2 = require("foo"); +x2 = require("bar"); +var y2 = x2.f.g; + +var x3; +x3 = require("foo"); +x3.f = require("bar").f; +var y3 = x3.f.g; + diff --git a/tests/micro/lowpatterns.json b/tests/micro/lowpatterns.json new file mode 100644 index 0000000..a7b3189 --- /dev/null +++ b/tests/micro/lowpatterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "read {,}.f.g" + } +] diff --git a/tests/micro/match1-patterns.json b/tests/micro/match1-patterns.json new file mode 100644 index 0000000..ffdb07b --- /dev/null +++ b/tests/micro/match1-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "read ?**.params" + } +] \ No newline at end of file diff --git a/tests/micro/match1.js b/tests/micro/match1.js new file mode 100644 index 0000000..4249c78 --- /dev/null +++ b/tests/micro/match1.js @@ -0,0 +1,8 @@ +const express = require('express'); +function BitterServer(opts) { + this.app = express(); + this.app.get(/^\/(\d{4})\/(\d{2})\/(\d+)(-\d+)?\/(.*)$/, (function(_this) { + return function(req) { + req.params[0]; + }})(this)); +} diff --git a/tests/micro/match10-patterns.json b/tests/micro/match10-patterns.json new file mode 100644 index 0000000..c0a0b63 --- /dev/null +++ b/tests/micro/match10-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call ?**.toPromise base:Observable" + } +] \ No newline at end of file diff --git a/tests/micro/match10.ts b/tests/micro/match10.ts new file mode 100644 index 0000000..f152d77 --- /dev/null +++ b/tests/micro/match10.ts @@ -0,0 +1,7 @@ +import * as rx from "rxjs"; + +// if not export, then no argument values and therefore no matches are reported +// uncertain type match because rx.Observable isn't handled by convertType +export async function logObservable(rx: rx.Observable, unitMs: number): Promise { + return rx.toPromise(); +} \ No newline at end of file diff --git a/tests/micro/match11-patterns.json b/tests/micro/match11-patterns.json new file mode 100644 index 0000000..efca0bd --- /dev/null +++ b/tests/micro/match11-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "read ?**.fromPromise base:Observable" + } +] \ No newline at end of file diff --git a/tests/micro/match11.ts b/tests/micro/match11.ts new file mode 100644 index 0000000..8b34dbb --- /dev/null +++ b/tests/micro/match11.ts @@ -0,0 +1,7 @@ +import * as rx from "rxjs"; + +export function from(rx: rx.Observable) { + return rx.fromPromise; +} + +var x = rx.fromPromise; // high confidence match? diff --git a/tests/micro/match12-patterns.json b/tests/micro/match12-patterns.json new file mode 100644 index 0000000..62f586b --- /dev/null +++ b/tests/micro/match12-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "read .editor.IContentWidget" + } +] \ No newline at end of file diff --git a/tests/micro/match12.ts b/tests/micro/match12.ts new file mode 100644 index 0000000..20f7b91 --- /dev/null +++ b/tests/micro/match12.ts @@ -0,0 +1,2 @@ +import { editor } from 'monaco-editor'; +import IContentWidget = editor.IContentWidget; \ No newline at end of file diff --git a/tests/micro/match2-patterns.json b/tests/micro/match2-patterns.json new file mode 100644 index 0000000..8d3b115 --- /dev/null +++ b/tests/micro/match2-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "read ?**.routes" + } +] \ No newline at end of file diff --git a/tests/micro/match2.js b/tests/micro/match2.js new file mode 100644 index 0000000..24390df --- /dev/null +++ b/tests/micro/match2.js @@ -0,0 +1,10 @@ +const express = require("express"); +const serverObject = { }; +function addServer(serverName) { + const app = express(); + serverObject[serverName] = { + app: app, + }; +} +addServer('server1') +serverObject.server1.app.routes.get \ No newline at end of file diff --git a/tests/micro/match3-patterns.json b/tests/micro/match3-patterns.json new file mode 100644 index 0000000..76ee0ec --- /dev/null +++ b/tests/micro/match3-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call ?**.save" + } +] \ No newline at end of file diff --git a/tests/micro/match3.js b/tests/micro/match3.js new file mode 100644 index 0000000..bc867f3 --- /dev/null +++ b/tests/micro/match3.js @@ -0,0 +1,5 @@ +import mongoose from 'mongoose'; +const schema = mongoose.Schema(); +schema.method('delete', function (cb) { + return this.save(cb); +}) \ No newline at end of file diff --git a/tests/micro/match4-patterns.json b/tests/micro/match4-patterns.json new file mode 100644 index 0000000..f729f19 --- /dev/null +++ b/tests/micro/match4-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call ?**.mergeMap" + } +] \ No newline at end of file diff --git a/tests/micro/match4.js b/tests/micro/match4.js new file mode 100644 index 0000000..57c8894 --- /dev/null +++ b/tests/micro/match4.js @@ -0,0 +1,5 @@ +const { Observable } = require('rxjs'); + +Observable.interval(5) + .retryWhen(attempts => + attempts.mergeMap()); diff --git a/tests/micro/match5-patterns.json b/tests/micro/match5-patterns.json new file mode 100644 index 0000000..7ad8d0c --- /dev/null +++ b/tests/micro/match5-patterns.json @@ -0,0 +1,14 @@ +[ + { + "id": "1", + "pattern": "import rxjs/Rx" + }, + { + "id": "2", + "pattern": "import rxjs" + }, + { + "id": "3", + "pattern": "import rxjs/Subject" + } +] \ No newline at end of file diff --git a/tests/micro/match5.js b/tests/micro/match5.js new file mode 100644 index 0000000..44fe679 --- /dev/null +++ b/tests/micro/match5.js @@ -0,0 +1,3 @@ +import { Observable } from 'rxjs/Rx' +import { Foo } from 'rxjs' +import { Bar } from 'rxjs/Subject' \ No newline at end of file diff --git a/tests/micro/match6-patterns.json b/tests/micro/match6-patterns.json new file mode 100644 index 0000000..8cec16b --- /dev/null +++ b/tests/micro/match6-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call ?**.startWith" + } +] \ No newline at end of file diff --git a/tests/micro/match6.js b/tests/micro/match6.js new file mode 100644 index 0000000..52afe23 --- /dev/null +++ b/tests/micro/match6.js @@ -0,0 +1,9 @@ +import { EmptyObservable } from 'rxjs/observable/EmptyObservable' + +const wrappedEpic = {}; +const $$getObservable = 'foo'; +let lifecycle2 = EmptyObservable.create(); +wrappedEpic[$$getObservable] = () => lifecycle2; + +const lifecycle = wrappedEpic[$$getObservable](); +lifecycle.startWith(null); \ No newline at end of file diff --git a/tests/micro/match7-patterns.json b/tests/micro/match7-patterns.json new file mode 100644 index 0000000..c9f1e35 --- /dev/null +++ b/tests/micro/match7-patterns.json @@ -0,0 +1,14 @@ +[ + { + "id": "1", + "pattern": "read ?**.bar" + }, + { + "id": "2", + "pattern": "read ?**.bar" + }, + { + "id": "3", + "pattern": "read .bar" + } +] \ No newline at end of file diff --git a/tests/micro/match7.js b/tests/micro/match7.js new file mode 100644 index 0000000..630f19b --- /dev/null +++ b/tests/micro/match7.js @@ -0,0 +1,11 @@ +var foo = require("foo"); +foo.bar; +foo.a.b.c.bar; + +var baz = require("baz"); +baz.bar; + +var qux = require("qux"); +qux.bar; + +foo.a = qux.q.w.e.r; diff --git a/tests/micro/match8-patterns.json b/tests/micro/match8-patterns.json new file mode 100644 index 0000000..8f68287 --- /dev/null +++ b/tests/micro/match8-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "read ()?.json" + } +] \ No newline at end of file diff --git a/tests/micro/match8.ts b/tests/micro/match8.ts new file mode 100644 index 0000000..6071d6b --- /dev/null +++ b/tests/micro/match8.ts @@ -0,0 +1,4 @@ +(async function() { + const { default: fetch } = await import("node-fetch"); + await fetch('someUrl').json(); +}()) diff --git a/tests/micro/match9-patterns.json b/tests/micro/match9-patterns.json new file mode 100644 index 0000000..c0a0b63 --- /dev/null +++ b/tests/micro/match9-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call ?**.toPromise base:Observable" + } +] \ No newline at end of file diff --git a/tests/micro/match9.ts b/tests/micro/match9.ts new file mode 100644 index 0000000..34cb65f --- /dev/null +++ b/tests/micro/match9.ts @@ -0,0 +1,13 @@ +import Subject from "rxjs"; +class RestrictionMosaicHttp { + search() { + return new Subject() + } +} + +function assign(object: T): T { + return Object.assign({ __proto__: Object.getPrototypeOf(object) }, object); +} + +const restrictionMosaicRepository = assign(new RestrictionMosaicHttp("someURL")); +restrictionMosaicRepository.search().toPromise(); \ No newline at end of file diff --git a/tests/micro/mix.js b/tests/micro/mix.js new file mode 100644 index 0000000..31399a8 --- /dev/null +++ b/tests/micro/mix.js @@ -0,0 +1,18 @@ +eval("console.log(42)"); + +var x = new Map(); +x.set(1, function() {console.log("1")}); +var y = x.get(1); +y(); + +var z = new Function("console.log(87)"); +z(); + +var a = new Array(); +a.push(function() {console.log("2")}); +var b = a.pop(); +b() + +var c = Array.from([() => {console.log(3)}]); +c[0](); + diff --git a/tests/micro/mix.json b/tests/micro/mix.json new file mode 100644 index 0000000..4cbafaf --- /dev/null +++ b/tests/micro/mix.json @@ -0,0 +1,24 @@ +{ + "entries": ["mix.js"], + "time": "Sat, 30 Jul 2022 16:52:08 GMT", + "files": [ + "mix.js" + ], + "functions": { + "2": "0:1:1:19:1", + "12": "0:4:10:4:39", + "30": "0:12:8:12:37", + "38": "0:16:21:16:43" + }, + "calls": { + "11": "0:6:1:6:4", + "29": "0:14:1:14:4", + "36": "0:17:1:17:7" + }, + "fun2fun": [ + [2, 12], [2, 30], [2, 38] + ], + "call2fun": [ + [11, 12], [29, 30], [36, 38] + ] +} diff --git a/tests/micro/more1.js b/tests/micro/more1.js new file mode 100644 index 0000000..9955ceb --- /dev/null +++ b/tests/micro/more1.js @@ -0,0 +1,58 @@ +const a1 = [() => {console.log("1")}, () => {console.log("2")}]; + +var s1 = new Set(a1); +for (const f of s1) + f(); + +var m2 = new Map([[() => {console.log("3")}, () => {console.log("4")}], [() => {console.log("5")}, () => {console.log("6")}]]); +for (const [k,v] of m2) { + k(); + v(); +} + +var a3 = Array.from(a1) +for (const f of a3) // array-like + f(); + +var a4 = Array.from(s1) +for (const f of a4) // iterable + f(); + +const x = { + f: () => {console.log("8")} +}; + +x.f(); + +var a5 = Array.from(a1, function (element) { + element(); + this.f(); + return () => {console.log("7")}; +}, x); +for (const f of a5) + f(); + +var a6 = [[() => {console.log("8")}]]; +var a7 = [() => {console.log("9")}, [() => {console.log("10")}]]; +var a8 = a6.concat(a7, () => {console.log("11")}); +a8[0][0](); +a8[1](); +a8[2][0](); +a8[3](); + +var a10 = [() => {console.log("12")}, [() => {console.log("13")}], [[() => {console.log("14")}]]]; +var a11 = a10.flat(); +var a12 = a10.flat(2); +a11[0](); +a11[1](); +a11[2][0](); +a12[0](); +a12[1](); +a12[2](); + +var a13 = [() => {console.log("15")}, () => {console.log("16")}]; +var a14 = a13.flatMap(f => [f, f]); +a14[0](); +a14[1](); +a14[2](); +a14[3](); diff --git a/tests/micro/more1.json b/tests/micro/more1.json new file mode 100644 index 0000000..77db710 --- /dev/null +++ b/tests/micro/more1.json @@ -0,0 +1,60 @@ +{ + "entries": ["more1.js"], + "time": "Thu, 01 Sep 2022 18:46:16 GMT", + "files": [ + "more1.js" + ], + "functions": { + "2": "0:1:1:59:1", + "6": "0:1:13:1:37", + "11": "0:1:39:1:63", + "19": "0:7:20:7:44", + "25": "0:7:46:7:70", + "30": "0:7:74:7:98", + "35": "0:7:100:7:124", + "50": "0:22:8:22:32", + "58": "0:27:25:31:2", + "64": "0:30:12:30:36", + "74": "0:35:12:35:36", + "81": "0:36:11:36:35", + "89": "0:36:38:36:63", + "96": "0:37:24:37:49", + "107": "0:43:12:43:37", + "114": "0:43:40:43:65", + "122": "0:43:70:43:95", + "135": "0:54:23:54:34", + "139": "0:53:12:53:37", + "148": "0:53:39:53:64" + }, + "calls": { + "5": "0:5:5:5:8", + "18": "0:9:5:9:8", + "24": "0:10:5:10:8", + "43": "0:15:5:15:8", + "47": "0:19:5:19:8", + "48": "0:25:1:25:6", + "60": "0:28:5:28:14", + "61": "0:29:5:29:13", + "63": "0:33:5:33:8", + "71": "0:38:1:38:11", + "79": "0:39:1:39:8", + "86": "0:40:1:40:11", + "94": "0:41:1:41:8", + "105": "0:46:1:46:9", + "112": "0:47:1:47:9", + "119": "0:48:1:48:12", + "127": "0:49:1:49:9", + "129": "0:50:1:50:9", + "131": "0:51:1:51:9", + "137": "0:55:1:55:9", + "144": "0:56:1:56:9", + "146": "0:57:1:57:9", + "153": "0:58:1:58:9" + }, + "fun2fun": [ + [2, 6], [2, 11], [2, 19], [2, 25], [2, 30], [2, 35], [2, 50], [2, 64], [2, 74], [2, 81], [2, 89], [2, 96], [2, 107], [2, 114], [2, 122], [2, 139], [2, 148], [58, 6], [58, 50], [58, 11] + ], + "call2fun": [ + [5, 6], [5, 11], [18, 19], [18, 30], [24, 25], [24, 35], [43, 6], [43, 11], [47, 6], [47, 11], [48, 50], [60, 6], [60, 11], [61, 50], [63, 64], [71, 74], [79, 81], [86, 89], [94, 96], [105, 107], [112, 114], [119, 122], [127, 107], [129, 114], [131, 122], [137, 139], [144, 139], [146, 148], [153, 148] + ] +} diff --git a/tests/micro/node_modules/rxjs/Rx.js b/tests/micro/node_modules/rxjs/Rx.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/micro/node_modules/rxjs/package.json b/tests/micro/node_modules/rxjs/package.json new file mode 100644 index 0000000..f2d5105 --- /dev/null +++ b/tests/micro/node_modules/rxjs/package.json @@ -0,0 +1,5 @@ +{ + "name": "rxjs", + "version": "5.5.12", + "main": "./Rx.js" +} diff --git a/tests/micro/obj.js b/tests/micro/obj.js new file mode 100644 index 0000000..44f0e73 --- /dev/null +++ b/tests/micro/obj.js @@ -0,0 +1,9 @@ +const person = { + printIntroduction: function() { + console.log("1"); + } +}; + +const me = Object.create(person); + +me.printIntroduction(); \ No newline at end of file diff --git a/tests/micro/obj.json b/tests/micro/obj.json new file mode 100644 index 0000000..094248d --- /dev/null +++ b/tests/micro/obj.json @@ -0,0 +1,20 @@ +{ + "entries": ["obj.js"], + "time": "Sat, 30 Jul 2022 13:46:07 GMT", + "files": [ + "obj.js" + ], + "functions": { + "2": "0:1:1:9:24", + "8": "0:2:24:4:6" + }, + "calls": { + "6": "0:9:1:9:23" + }, + "fun2fun": [ + [2, 8] + ], + "call2fun": [ + [6, 8] + ] +} diff --git a/tests/micro/oneshot.js b/tests/micro/oneshot.js new file mode 100644 index 0000000..cec6146 --- /dev/null +++ b/tests/micro/oneshot.js @@ -0,0 +1,5 @@ +function f1() {} + +var t = ((( /*foo*/ (( ((function () { + return f1; +}))() )) /*bar*/ ))); diff --git a/tests/micro/oneshot.json b/tests/micro/oneshot.json new file mode 100644 index 0000000..cb45ce2 --- /dev/null +++ b/tests/micro/oneshot.json @@ -0,0 +1,20 @@ +{ + "entries": ["oneshot.js"], + "time": "Sat, 30 Jul 2022 13:24:46 GMT", + "files": [ + "oneshot.js" + ], + "functions": { + "2": "0:1:1:6:1", + "4": "0:3:28:5:2" + }, + "calls": { + "3": "0:3:9:5:23" + }, + "fun2fun": [ + [2, 4] + ], + "call2fun": [ + [3, 4] + ] +} diff --git a/tests/micro/package.json b/tests/micro/package.json new file mode 100644 index 0000000..48a71cc --- /dev/null +++ b/tests/micro/package.json @@ -0,0 +1,4 @@ +{ + "name": "micro", + "version": "0.0.1" +} diff --git a/tests/micro/patterns.json b/tests/micro/patterns.json new file mode 100644 index 0000000..8315d47 --- /dev/null +++ b/tests/micro/patterns.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "pattern": "import " + }, + { + "id": "2", + "pattern": "read .filter" + }, + { + "id": "3", + "pattern": "call .filter" + }, + { + "id": "4", + "pattern": "call .filter()" + }, + { + "id": "5", + "pattern": "read .obj" + }, + { + "id": "6", + "pattern": "write .obj.foo" + } +] diff --git a/tests/micro/patterns11.json b/tests/micro/patterns11.json new file mode 100644 index 0000000..49c8504 --- /dev/null +++ b/tests/micro/patterns11.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call ?**.take" + } +] \ No newline at end of file diff --git a/tests/micro/patterns7.json b/tests/micro/patterns7.json new file mode 100644 index 0000000..261fa19 --- /dev/null +++ b/tests/micro/patterns7.json @@ -0,0 +1,10 @@ +[ + { + "id": "1", + "pattern": "import " + }, + { + "id": "2", + "pattern": "read .foo" + } +] diff --git a/tests/micro/patterns8.json b/tests/micro/patterns8.json new file mode 100644 index 0000000..0c87499 --- /dev/null +++ b/tests/micro/patterns8.json @@ -0,0 +1,8 @@ +[ + { + "pattern": "read .Logger", + "id": "1", + "changelogDescription": "winston.Logger has been replaced with winston.createLogger.", + "changelogId": "1" + } +] \ No newline at end of file diff --git a/tests/micro/promises.js b/tests/micro/promises.js new file mode 100644 index 0000000..94bef1b --- /dev/null +++ b/tests/micro/promises.js @@ -0,0 +1,172 @@ +const p2 = new Promise((resolve, reject) => { + resolve(() => { // fulfilled value + console.log("p2resolve"); + }); +}); + +for (const round of [1,2,3,4]) { + + const p1 = new Promise((resolve, reject) => { // must be called with 'new' + switch (round) { + case 1: + resolve(() => { // fulfillment value + console.log("resolve1"); + }); + break; + case 2: + reject(() => { // rejection value + console.log("reject2"); + }); + break; + case 3: + throw () => { // similar to reject + console.log("throw30"); + }; + case 4: + resolve(p2); // if a resolve value (but not a reject value!) is itself a promise, it is inserted in the chain + break; + } + // return value of executor is ignored + }); + + p1.then(a => { // if not a function, the identity function is used + console.log("then3"); + a(); + // if returning a promise, the result promise obtains its fulfilled/rejected value from that promise + // if throws, thrown value becomes rejected value of result promise + return () => { // returned value becomes fulfilled value of result promise + console.log("thenreturn10"); + }; + }, b => { + console.log("else4"); + b(); + return () => { + console.log("elsereturn11"); + }; + }) // .then returns a new chained promise + .then(a => { + console.log("chainedthen5"); + a(); + }, b => { + console.log("chainedelse6"); + b(); + }) + + + p1.finally(() => { // called when the promise is settled + console.log("finally20"); + }).catch(c => { + console.log("catch50"); + c(); + }); + + p1.then(a => { // one promise can have multiple handlers + console.log("anotherthen7"); + a(); + return p2; // if a handler returns a promise, the return value of then will be fulfilled/rejected based on the eventual state of that promise + }, b => { + console.log("anotherelse8"); + b(); + return () => { + console.log("anotherelsereturn41"); + }; + }).then(aa => { + console.log("anotherthen37"); + aa(); + }, bb => { + console.log("anotherelse38"); + bb(); + }); + + p1.catch(cc => { + console.log("catch22"); + cc(); + return () => { + console.log("catchreturn23"); + }; + }).then(dd => { + console.log("anotherthen24"); + dd(); + }); +} + +Promise.resolve(() => {console.log("promiseresolve1");}).then( + v => { + v(); + }, + r => { + r(); + }, +); + +Promise.reject(() => {console.log("promisereject1");}).then( + v => { + v(); + }, + r => { + r(); + }, +); + +Promise.resolve(p2).then( + v => { + v(); + }, + r => { + r(); + }, +); + +const p3 = new Promise((resolve, reject) => { + reject(() => { + console.log("p3reject"); + }); +}); +Promise.all([p2]).then( + va => { + va[0](); + }, + ra => { + ra(); + }, +); +Promise.all([p2, p3]).catch( + ra => { + ra(); + }, +); +Promise.allSettled([p2, p3]).then( + va => { + va[0].value(); + va[1].reason(); + } +); +Promise.any([p2, p3]).then( + va => { + va(); + }, + ra => { + ra(); + }, +); +Promise.race([p2, p3]).then( + va => { + va(); + }, + ra => { + ra(); + }, +); + +// const aThenable = { +// then(onFulfilled, onRejected) { +// onFulfilled({ +// // The thenable is fulfilled with another thenable +// then(onFulfilled, onRejected) { +// onFulfilled(42); +// }, +// }); +// }, +// }; +// +// Promise.resolve(aThenable); diff --git a/tests/micro/promises.json b/tests/micro/promises.json new file mode 100644 index 0000000..29ca28f --- /dev/null +++ b/tests/micro/promises.json @@ -0,0 +1,68 @@ +{ + "entries": ["promises.js"], + "time": "Thu, 24 Nov 2022 11:47:04 GMT", + "files": [ + "promises.js" + ], + "functions": { + "2": "0:1:1:173:1", + "5": "0:1:24:5:2", + "10": "0:9:28:30:6", + "52": "0:120:24:124:2", + "80": "0:32:13:40:6", + "86": "0:12:25:14:18", + "91": "0:56:16:58:6", + "96": "0:63:13:67:6", + "102": "0:40:8:46:6", + "108": "0:17:24:19:18", + "113": "0:67:8:73:6", + "119": "0:81:14:87:6", + "125": "0:22:23:24:18", + "130": "0:94:5:96:6", + "133": "0:93:17:93:56", + "138": "0:106:5:108:6", + "141": "0:102:16:102:54", + "146": "0:112:5:114:6", + "149": "0:2:13:4:6", + "154": "0:47:15:50:10", + "160": "0:37:16:39:10", + "165": "0:87:13:90:6", + "171": "0:43:16:45:10", + "176": "0:73:13:76:6", + "182": "0:70:16:72:10", + "187": "0:84:16:86:10", + "192": "0:126:5:128:6", + "196": "0:134:5:136:6", + "199": "0:121:12:123:6", + "204": "0:139:5:142:6", + "212": "0:145:5:147:6", + "215": "0:153:5:155:6", + "218": "0:58:14:61:6" + }, + "calls": { + "85": "0:34:9:34:12", + "101": "0:65:9:65:12", + "107": "0:42:9:42:12", + "118": "0:69:9:69:12", + "124": "0:83:9:83:13", + "132": "0:95:9:95:12", + "140": "0:107:9:107:12", + "148": "0:113:9:113:12", + "159": "0:49:13:49:16", + "170": "0:89:9:89:13", + "181": "0:75:9:75:13", + "194": "0:127:9:127:16", + "198": "0:135:9:135:13", + "206": "0:140:9:140:22", + "209": "0:141:9:141:23", + "214": "0:146:9:146:13", + "217": "0:154:9:154:13", + "223": "0:60:9:60:12" + }, + "fun2fun": [ + [80, 86], [80, 149], [96, 86], [96, 149], [102, 108], [102, 125], [113, 108], [113, 125], [119, 108], [119, 125], [130, 133], [138, 141], [146, 149], [154, 160], [154, 171], [165, 86], [165, 187], [165, 149], [176, 182], [176, 149], [192, 149], [196, 199], [204, 149], [204, 199], [212, 149], [215, 149], [218, 108], [218, 125] + ], + "call2fun": [ + [85, 86], [85, 149], [101, 86], [101, 149], [107, 108], [107, 125], [118, 108], [118, 125], [124, 108], [124, 125], [132, 133], [140, 141], [148, 149], [159, 160], [159, 171], [170, 86], [170, 187], [170, 149], [181, 182], [181, 149], [194, 149], [198, 199], [206, 149], [209, 199], [214, 149], [217, 149], [223, 108], [223, 125] + ] +} diff --git a/tests/micro/prototypes.js b/tests/micro/prototypes.js new file mode 100644 index 0000000..b7690d2 --- /dev/null +++ b/tests/micro/prototypes.js @@ -0,0 +1,8 @@ +function C() {} + +C.prototype = { + foo: function () {} +} + +var v = new C(); +v.foo(); diff --git a/tests/micro/regexp.js b/tests/micro/regexp.js new file mode 100644 index 0000000..9d22a68 --- /dev/null +++ b/tests/micro/regexp.js @@ -0,0 +1,7 @@ +var r1 = RegExp("ab+c"); +r1.test("ab") + +var r2 = /a*b*/; +var a = r2.exec("aaabbb"); +a.find((v) => {console.log("!");return true}); + diff --git a/tests/micro/rest.js b/tests/micro/rest.js new file mode 100644 index 0000000..d987af3 --- /dev/null +++ b/tests/micro/rest.js @@ -0,0 +1,78 @@ +var arr = [ + () => {console.log("1")}, + () => {console.log("2")}, + () => {console.log("3")}, + () => {console.log("4")} +]; +arr[Math.floor(Math.random())] = () => {console.log("5")}; + +var [a0, a1, ...arest] = arr; +a0(); +a1(); +var a2 = arest[0]; +var a3 = arest[1]; +var a4 = arest[2]; +a2(); +a3(); +if (a4) + a4(); + +var [c0, ...[c1, ...crest]] = arr; +c0(); +c1(); +var c2 = crest[0]; +var c3 = crest[1]; +var c4 = crest[2]; +c2(); +c3(); +if (c4) + c4(); + +function f1(b0, b1, ...rest) { + b0(); + b1(); + rest[0](); + rest[1](); + if (rest[2]) + rest[2](); +} +f1( + () => {console.log("11")}, + () => {console.log("12")}, + () => {console.log("13")}, + () => {console.log("14")} +) + +function f2(d0, ...[d1, ...drest]) { + d0(); + d1(); + drest[0](); + drest[1](); + if (drest[2]) + drest[2](); +} +f2( + () => {console.log("21")}, + () => {console.log("22")}, + () => {console.log("23")}, + () => {console.log("24")} +) + +var obj = { + e1: () => {console.log("31")}, + e2: () => {console.log("32")}, + e3: () => {console.log("33")}, + e4: () => {console.log("34")} +}; +obj["e" + (obj ? 1 : 2)] = () => {console.log("35")}; + +var {e1, e2: ee2, ...erest} = obj; +e1(); +ee2(); +erest.e3(); + +function f3({e1: eee1, ...eerest}) { + eee1(); + eerest.e4(); +} +f3(obj); diff --git a/tests/micro/rest.json b/tests/micro/rest.json new file mode 100644 index 0000000..8287bea --- /dev/null +++ b/tests/micro/rest.json @@ -0,0 +1,60 @@ +{ + "entries": ["rest.js"], + "time": "Fri, 05 Aug 2022 07:39:50 GMT", + "files": [ + "rest.js" + ], + "functions": { + "2": "0:1:1:79:1", + "11": "0:7:34:7:58", + "17": "0:3:5:3:29", + "26": "0:4:5:4:29", + "32": "0:5:5:5:29", + "45": "0:31:1:38:2", + "48": "0:40:5:40:30", + "54": "0:41:5:41:30", + "61": "0:42:5:42:30", + "68": "0:43:5:43:30", + "75": "0:46:1:53:2", + "78": "0:55:5:55:30", + "84": "0:56:5:56:30", + "91": "0:57:5:57:30", + "98": "0:58:5:58:30", + "107": "0:67:28:67:53", + "113": "0:63:9:63:34", + "120": "0:64:9:64:34", + "126": "0:74:1:77:2", + "131": "0:65:9:65:34" + }, + "calls": { + "10": "0:10:1:10:5", + "16": "0:11:1:11:5", + "25": "0:15:1:15:5", + "31": "0:16:1:16:5", + "37": "0:21:1:21:5", + "38": "0:22:1:22:5", + "42": "0:26:1:26:5", + "43": "0:27:1:27:5", + "44": "0:39:1:44:2", + "47": "0:32:5:32:9", + "53": "0:33:5:33:9", + "59": "0:34:5:34:14", + "66": "0:35:5:35:14", + "77": "0:47:5:47:9", + "83": "0:48:5:48:9", + "89": "0:49:5:49:15", + "96": "0:50:5:50:15", + "106": "0:70:1:70:5", + "112": "0:71:1:71:6", + "118": "0:72:1:72:11", + "125": "0:78:1:78:8", + "128": "0:75:5:75:11", + "129": "0:76:5:76:16" + }, + "fun2fun": [ + [2, 11], [2, 17], [2, 26], [2, 32], [2, 45], [2, 107], [2, 113], [2, 120], [2, 126], [45, 48], [45, 54], [45, 61], [45, 68], [75, 78], [75, 84], [75, 91], [75, 98], [126, 107], [126, 131] + ], + "call2fun": [ + [10, 11], [16, 17], [25, 26], [31, 32], [37, 11], [38, 17], [42, 26], [43, 32], [44, 45], [47, 48], [53, 54], [59, 61], [66, 68], [77, 78], [83, 84], [89, 91], [96, 98], [106, 107], [112, 113], [118, 120], [125, 126], [128, 107], [129, 131] + ] +} diff --git a/tests/micro/rest2.js b/tests/micro/rest2.js new file mode 100644 index 0000000..c8635b1 --- /dev/null +++ b/tests/micro/rest2.js @@ -0,0 +1,7 @@ +function f(...args) { + args[0](); +} +function g() { + console.log("here"); +} +f(g); diff --git a/tests/micro/rest2.json b/tests/micro/rest2.json new file mode 100644 index 0000000..d997d90 --- /dev/null +++ b/tests/micro/rest2.json @@ -0,0 +1,22 @@ +{ + "entries": ["rest2.js"], + "time": "Sun, 22 Jan 2023 12:57:13 GMT", + "files": [ + "rest2.js" + ], + "functions": { + "2": "0:1:1:8:1", + "4": "0:1:1:3:2", + "8": "0:4:1:6:2" + }, + "calls": { + "3": "0:7:1:7:5", + "6": "0:2:5:2:14" + }, + "fun2fun": [ + [2, 4], [4, 8] + ], + "call2fun": [ + [3, 4], [6, 8] + ] +} diff --git a/tests/micro/rxjs.json b/tests/micro/rxjs.json new file mode 100644 index 0000000..b7a71cc --- /dev/null +++ b/tests/micro/rxjs.json @@ -0,0 +1,8 @@ +[ + { + "pattern": "read ?**.{do,catch,switch,finally}", + "id": "103a", + "changelogDescription": "Operator renames: do -> tap, catch -> catchError, switch -> switchAll, finally -> finalize, throw -> throwError\n", + "changelogId": "23" + } +] diff --git a/tests/micro/rxjs1.js b/tests/micro/rxjs1.js new file mode 100644 index 0000000..2c5c823 --- /dev/null +++ b/tests/micro/rxjs1.js @@ -0,0 +1,3 @@ +import { default as renderToString } from './rxjs2'; + +var x = renderToString().catch(); diff --git a/tests/micro/rxjs2.js b/tests/micro/rxjs2.js new file mode 100644 index 0000000..50eae37 --- /dev/null +++ b/tests/micro/rxjs2.js @@ -0,0 +1,6 @@ +import { Observable } from 'rxjs/Observable'; + +export default function renderToString() { + return Observable.defer(); +} + diff --git a/tests/micro/spread.js b/tests/micro/spread.js new file mode 100644 index 0000000..24432be --- /dev/null +++ b/tests/micro/spread.js @@ -0,0 +1,35 @@ +function f(a1, a2, a3) { + a1(); + a2(); + a3(); +} +const xs = [ + () => {console.log("1")}, + () => {console.log("2")} + +]; +f(...xs, () => {console.log("3")}) // spread in arguments (values from iterable) + +const q = { p1: () => {console.log("10")}, ...xs, p2: () => {console.log("11")} }; // spread in object (properties of object) +q.p1(); +q.p2(); +q[0](); +q[1](); + +const w = [() => {console.log("20")}, ...xs, () => {console.log("21")}]; // spread in array (values from iterable) +w[0](); +w[1](); +w[2](); +w[3](); + +const q2 = {...q}; // spread in object (properties of object) +q2.p1(); +q2.p2(); +q2[0](); +q2[1](); + +const w2 = [...w.values()]; // spread in array (values from iterable) +w2[0](); +w2[1](); +w2[2](); +w2[3](); diff --git a/tests/micro/spread.json b/tests/micro/spread.json new file mode 100644 index 0000000..a60b9a5 --- /dev/null +++ b/tests/micro/spread.json @@ -0,0 +1,46 @@ +{ + "entries": ["spread.js"], + "time": "Sat, 06 Aug 2022 15:06:59 GMT", + "files": [ + "spread.js" + ], + "functions": { + "2": "0:1:1:36:1", + "4": "0:1:1:5:2", + "7": "0:7:5:7:29", + "13": "0:8:5:8:29", + "19": "0:11:10:11:34", + "26": "0:13:17:13:42", + "33": "0:13:55:13:80", + "44": "0:19:12:19:37", + "55": "0:19:46:19:71" + }, + "calls": { + "3": "0:11:1:11:35", + "6": "0:2:5:2:9", + "12": "0:3:5:3:9", + "18": "0:4:5:4:9", + "24": "0:14:1:14:7", + "31": "0:15:1:15:7", + "38": "0:16:1:16:7", + "40": "0:17:1:17:7", + "42": "0:20:1:20:7", + "49": "0:21:1:21:7", + "51": "0:22:1:22:7", + "53": "0:23:1:23:7", + "60": "0:26:1:26:8", + "62": "0:27:1:27:8", + "64": "0:28:1:28:8", + "66": "0:29:1:29:8", + "70": "0:32:1:32:8", + "72": "0:33:1:33:8", + "74": "0:34:1:34:8", + "76": "0:35:1:35:8" + }, + "fun2fun": [ + [2, 4], [2, 26], [2, 33], [2, 7], [2, 13], [2, 44], [2, 55], [4, 7], [4, 13], [4, 19] + ], + "call2fun": [ + [3, 4], [6, 7], [12, 13], [18, 19], [24, 26], [31, 33], [38, 7], [40, 13], [42, 44], [49, 7], [51, 13], [53, 55], [60, 26], [62, 33], [64, 7], [66, 13], [70, 44], [72, 7], [74, 13], [76, 55] + ] +} diff --git a/tests/micro/templateliterals.js b/tests/micro/templateliterals.js new file mode 100644 index 0000000..75023c6 --- /dev/null +++ b/tests/micro/templateliterals.js @@ -0,0 +1,7 @@ +function fun(strings, p1, p2) { + p1(); + p2(); + return () => {console.log("3")}; +} +const x = fun`foo${ () => {console.log("1")} }bar${ () => {console.log("2")} }baz`; +x(); diff --git a/tests/micro/templateliterals.json b/tests/micro/templateliterals.json new file mode 100644 index 0000000..8250f5f --- /dev/null +++ b/tests/micro/templateliterals.json @@ -0,0 +1,26 @@ +{ + "entries": ["templateliterals.js"], + "time": "Wed, 03 Aug 2022 19:16:30 GMT", + "files": [ + "templateliterals.js" + ], + "functions": { + "2": "0:1:1:8:1", + "4": "0:1:1:5:2", + "7": "0:6:21:6:45", + "13": "0:6:53:6:77", + "19": "0:4:12:4:36" + }, + "calls": { + "3": "0:6:11:6:82", + "6": "0:2:5:2:9", + "12": "0:3:5:3:9", + "18": "0:7:1:7:4" + }, + "fun2fun": [ + [2, 4], [2, 19], [4, 7], [4, 13] + ], + "call2fun": [ + [3, 4], [6, 7], [12, 13], [18, 19] + ] +} diff --git a/tests/micro/this.js b/tests/micro/this.js new file mode 100644 index 0000000..01089cc --- /dev/null +++ b/tests/micro/this.js @@ -0,0 +1,19 @@ +var x = { + p: function() { + return this.q; + }, + q: function() { + console.log("1"); + } +} +var t = x.p() +t(); + +function f() {} +f.g = function() { + console.log("2"); +} +f.h = function() { + this.g(); +} +f.h(); diff --git a/tests/micro/ts-patterns.json b/tests/micro/ts-patterns.json new file mode 100644 index 0000000..00e3d4f --- /dev/null +++ b/tests/micro/ts-patterns.json @@ -0,0 +1,6 @@ +[ + { + "id": "1", + "pattern": "call .foo 0:Foo 1:string 2:\"bar\" 3:function 4:string 5:42 6:number 7:number" + } +] \ No newline at end of file diff --git a/tests/micro/ts.ts b/tests/micro/ts.ts new file mode 100644 index 0000000..a73329b --- /dev/null +++ b/tests/micro/ts.ts @@ -0,0 +1,9 @@ +import {foo, Foo} from "lib"; + +const a: Foo = new Foo; +const b = "bar"; +const c = () => {}; +const d = "baz" as string; +const e = 42; +const f: number = 117; +foo(a, b, b, c, d, e, e, f); diff --git a/tests/mochatest/jelly.test.ts b/tests/mochatest/jelly.test.ts new file mode 100644 index 0000000..230ebf8 --- /dev/null +++ b/tests/mochatest/jelly.test.ts @@ -0,0 +1,23 @@ +import {options, resetOptions} from "../../src/options"; +import logger from "../../src/misc/logger"; +import {runTest} from "../../src/testing/runtest"; + +beforeEach(() => { + resetOptions(); + logger.transports[0].level = options.loglevel = "error"; +}); + +test("tests/mochatest", async () => { + options.callgraphExternal = false; + await runTest("tests/mochatest", "test.js", { + soundness: "tests/mochatest/test.json", + functionInfos: 5, + moduleInfos: 2, + numberOfFunctionToFunctionEdges: 3, + oneCalleeCalls: 3, + funFound: 3, + funTotal: 3, + callFound: 3, + callTotal: 3 + }); +}); diff --git a/tests/mochatest/mylib.js b/tests/mochatest/mylib.js new file mode 100644 index 0000000..4d7586c --- /dev/null +++ b/tests/mochatest/mylib.js @@ -0,0 +1,3 @@ +module.exports.plus = (x, y) => x + y; + +module.exports.apply = (f, x, y) => f(x, y); diff --git a/tests/mochatest/package-lock.json b/tests/mochatest/package-lock.json new file mode 100644 index 0000000..85d1019 --- /dev/null +++ b/tests/mochatest/package-lock.json @@ -0,0 +1,1607 @@ +{ + "name": "mochatest", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "mochatest", + "version": "0.0.1", + "devDependencies": { + "mocha": "^9.2.2" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/tests/mochatest/package.json b/tests/mochatest/package.json new file mode 100644 index 0000000..21f3fa6 --- /dev/null +++ b/tests/mochatest/package.json @@ -0,0 +1,10 @@ +{ + "name": "mochatest", + "version": "0.0.1", + "scripts": { + "test": "mocha" + }, + "devDependencies": { + "mocha": "^9.2.2" + } +} diff --git a/tests/mochatest/test.js b/tests/mochatest/test.js new file mode 100644 index 0000000..bb46150 --- /dev/null +++ b/tests/mochatest/test.js @@ -0,0 +1,11 @@ +var mylib = require('./mylib'); +var assert = require('assert'); + +describe("mylib", function() { + it('plus should return a number', function() { + assert.ok(typeof mylib.plus(4, 2) === 'number'); + }); + it('apply should apply', function() { + assert.ok(mylib.apply(mylib.plus, 4, 2) === 6); + }); +}); diff --git a/tests/mochatest/test.json b/tests/mochatest/test.json new file mode 100644 index 0000000..1cf5a9a --- /dev/null +++ b/tests/mochatest/test.json @@ -0,0 +1,28 @@ +{ + "entries": ["node_modules/.bin/mocha"], + "time": "Thu, 29 Sep 2022 19:07:41 GMT", + "files": [ + "test.js", + "mylib.js" + ], + "functions": { + "8544": "0:1:1:12:1", + "8546": "1:1:1:4:1", + "8652": "0:4:19:11:2", + "9403": "0:5:39:7:6", + "9410": "1:1:23:1:38", + "9514": "0:8:30:10:6", + "9522": "1:3:24:3:44" + }, + "calls": { + "9408": "0:6:26:6:42", + "9519": "0:9:19:9:48", + "9523": "1:3:37:3:44" + }, + "fun2fun": [ + [9403, 9410], [9514, 9522], [9522, 9410] + ], + "call2fun": [ + [9408, 9410], [9519, 9522], [9523, 9410] + ] +} diff --git a/tests/vulnerabilities/package-lock.json b/tests/vulnerabilities/package-lock.json new file mode 100644 index 0000000..c8e94c0 --- /dev/null +++ b/tests/vulnerabilities/package-lock.json @@ -0,0 +1,2331 @@ +{ + "name": "@jsfix/vulnerabilities", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@jsfix/vulnerabilities", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "node-etsy-client": "^0.2.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dependencies": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-etsy-client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-etsy-client/-/node-etsy-client-0.2.0.tgz", + "integrity": "sha512-yMjHXbTfbgWfAVfthdknxqx0HHnGI2riGDjINdTODhBeVZC7vPADOBGW8bFFMi2Pk0iTa1efDUZMRWdKS+ZrJA==", + "dependencies": { + "chai": "^4.2.0", + "mocha": "^8.2.1", + "node-fetch": "^2.6.1", + "query-string": "^6.13.7", + "winston": "^3.3.3" + }, + "engines": { + "node": "12" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/query-string": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "dependencies": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==" + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" + }, + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "requires": { + "argparse": "^2.0.1" + } + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "requires": { + "chalk": "^4.0.0" + } + }, + "logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "requires": { + "get-func-name": "^2.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==" + }, + "node-etsy-client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-etsy-client/-/node-etsy-client-0.2.0.tgz", + "integrity": "sha512-yMjHXbTfbgWfAVfthdknxqx0HHnGI2riGDjINdTODhBeVZC7vPADOBGW8bFFMi2Pk0iTa1efDUZMRWdKS+ZrJA==", + "requires": { + "chai": "^4.2.0", + "mocha": "^8.2.1", + "node-fetch": "^2.6.1", + "query-string": "^6.13.7", + "winston": "^3.3.3" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "query-string": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", + "requires": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "requires": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + } + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/tests/vulnerabilities/package.json b/tests/vulnerabilities/package.json new file mode 100644 index 0000000..4cba68c --- /dev/null +++ b/tests/vulnerabilities/package.json @@ -0,0 +1,7 @@ +{ + "name": "tests-vulnerabilities", + "version": "1.0.0", + "dependencies": { + "node-etsy-client": "^0.2.0" + } +} diff --git a/tests/vulnerabilities/sample.js b/tests/vulnerabilities/sample.js new file mode 100644 index 0000000..ef03fe5 --- /dev/null +++ b/tests/vulnerabilities/sample.js @@ -0,0 +1,8 @@ +var EtsyClient = require('node-etsy-client'); +async function doIt() { + var client = new EtsyClient(); + var shops = await client.findAllShops({'shop_name':'mony', limit:10}); + console.log(shops); +} +if (false) + doIt(); diff --git a/tests/vulnerabilities/sample.json b/tests/vulnerabilities/sample.json new file mode 100644 index 0000000..d7d72f7 --- /dev/null +++ b/tests/vulnerabilities/sample.json @@ -0,0 +1,165 @@ +[ + { + "osv": { + "schema_version": "1.3.0", + "id": "GHSA-xw22-wv29-3299", + "modified": "2021-04-02T17:05:21Z", + "published": "2021-04-06T17:29:52Z", + "aliases": [ + "CVE-2021-21421" + ], + "summary": "ApiKey secret could be revelated on network issue", + "details": "### Impact\n_What kind of vulnerability is it? Who is impacted?_\nApplications that are using node-etsy-client and reporting client error to the end user will offer api key value too\n\n### Patches\n_Has the problem been patched? What versions should users upgrade to?_\n\ncreharmony/node-etsy-client#18 fixes this issue. This is fixed in [node-etsy-client v0.3.0](https://github.com/creharmony/node-etsy-client/tree/v0.3.0) and later.\n\n### Workarounds\n_Is there a way for users to fix or remediate the vulnerability without upgrading?_\n\nDo not report or log etsy client error if you are using version <= v0.2.0\n\nUpdate your version of node-etsy-client\n\n### References\n_Are there any links users can visit to find out more?_\n\n- https://github.com/creharmony/node-etsy-client/issues/17 : On connect error secret appears in error #17\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue in [github.com/creharmony/node-etsy-client/issues](https://github.com/creharmony/node-etsy-client/issues/)", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H" + } + ], + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "node-etsy-client" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "0.3.0" + } + ] + } + ], + "database_specific": { + "last_known_affected_version_range": "<= 0.2.0" + } + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/creharmony/node-etsy-client/security/advisories/GHSA-xw22-wv29-3299" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-21421" + }, + { + "type": "WEB", + "url": "https://github.com/creharmony/node-etsy-client/commit/b4beb8ef080366c1a87dbf9e163051a446acaa7d" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-200", + "CWE-209" + ], + "severity": "HIGH", + "github_reviewed": true + } + }, + "location": { + "link": "https://registry.npmjs.org/node-etsy-client/-/node-etsy-client-0.2.0.tgz", + "file": "src/EtsyClient.js", + "line": 94, + "code": "fetch(`${this.apiUrl}${endpoint}?${getQueryString}`)" + } + }, + { + "osv": { + "id": "GHSA-test-42", + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "decode-uri-component" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "0.3.0" + } + ] + } + ] + } + ] + }, + "location": { + "file": "index.js" + } + }, + { + "osv": { + "id": "GHSA-test-117", + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "webidl-conversions" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "5.3.0" + } + ] + } + ] + } + ] + }, + "location": { + "file": "lib/index.js", + "line": 23, + "code": "werBound = typeOpts.unsign" + } + }, + { + "osv": { + "id": "GHSA-test-87", + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "tr46" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "5.3.0" + } + ] + } + ] + } + ] + }, + "location": { + "file": "index.js", + "line": 15, + "code": "Math.floor((start + end) / 2)" + }, + "patterns": [".findStatus"] + } +] \ No newline at end of file diff --git a/tsconfig-build.json b/tsconfig-build.json new file mode 100644 index 0000000..9a90d3c --- /dev/null +++ b/tsconfig-build.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "files": [], + "compilerOptions": { + "rootDir": "./src", + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7aaaf2a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,68 @@ +{ + "include": ["src/**/*", "tests/**/*", "./jest.config.ts"], + "files": ["node_modules/jest-expect-message/types/index.d.ts"], + "compilerOptions": { + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./lib", /* Redirect output structure to the directory. */ + "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "composite": true, /* Enable project compilation */ + "tsBuildInfoFile": "./tmp/tsbuildinfo", /* Specify file to store incremental compilation information */ + "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + //"noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + //"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + "paths": {"*": ["./typings/*"]}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/typings/callgraph.d.ts b/typings/callgraph.d.ts new file mode 100644 index 0000000..0617b1d --- /dev/null +++ b/typings/callgraph.d.ts @@ -0,0 +1,63 @@ +/** + * Call graph created either statically or dynamically. + * + * Each source location has the format "::::". + * Each function index, function location, call index and call location is assumed to be unique. + * The edge arrays are assumed not to contain duplicate pairs. + * + * Be aware that some call edges may or may not be included depending on how the call graph is created: + * - implicit calls to getters/setters and toString/valueOf + * - calls to/from native functions, including 'eval', and event handlers + * - calls to 'require' and import declarations + */ +import {SourceLocationJSON} from "../src/misc/util"; + +export type CallGraph = { // TODO: represent special call edges separately from ordinary call edges? + + /** + * Entry files (relative to basedir). + */ + entries?: Array, + + /** + * Time stamp of creation. + */ + time?: string, + + /** + * Array of files (relative to basedir). + * The position in the array defines the file index. + */ + files: Array, + + /** + * Indices and source locations of functions. + */ + functions: { + [index: number]: SourceLocationJSON; + }, + + /** + * Indices and source locations of calls. + */ + calls: { + [index: number]: SourceLocationJSON; + }, + + /** + * Caller-callee edges, function to function. + */ + fun2fun: Array<[number, number]>; + + /** + * Caller-callee edges, call site to function. + */ + call2fun: Array<[number, number]>; + + /** + * Source locations of functions that should be ignored when comparing call graphs. + * This is used for skipping spurious functions that appear in dynamic call graph construction + * but are not known to be spurious until running static call graph construction. + */ + ignore?: Array; +} diff --git a/typings/diagnostics.d.ts b/typings/diagnostics.d.ts new file mode 100644 index 0000000..accc869 --- /dev/null +++ b/typings/diagnostics.d.ts @@ -0,0 +1,26 @@ +export interface AnalysisDiagnostics { + + packages: number; + + modules: number; + + functions: number; + + functionToFunctionEdges: number; + + iterations: number; + + uniqueTokens: number; + + time: number; // set when analysis is completed + + cpuTime: number; // set when analysis is completed + + aborted: boolean; + + timeout: boolean; + + codeSize: number; + + maxMemoryUsage: number; +} \ No newline at end of file diff --git a/typings/ipc.d.ts b/typings/ipc.d.ts new file mode 100644 index 0000000..3fae385 --- /dev/null +++ b/typings/ipc.d.ts @@ -0,0 +1,233 @@ +import {options} from "../src/options"; +import {AnalysisDiagnostics} from "diagnostics"; +import {AccessPathPatternStringToNodes} from "../src/patternmatching/apiusage"; +import {PatternMatchesJSON} from "../src/patternmatching/patternmatcher"; +import {SemanticPatch} from "tapir"; +import {CallGraph} from "callgraph"; +import {LibraryUsageJSON} from "../src/typescript/typeinferrer"; + +export interface Message { + seq?: number; + type: "request" | "response"; +} + +export interface Request extends Message { + type: "request"; + command: RequestCommands; + arguments?: any; +} + +export interface Response extends Message { + type: "response"; + seq: number; + command?: RequestCommands; + request_seq?: number; + success: boolean; + message?: string; + body?: any; +} + +export interface SuccessResponse extends Response { + success: true; +} + +export type RequestCommands = + "options" | + "expandpaths" | + "files" | + "analyze" | + "abort" | + "diagnostics" | + "callgraph" | + "htmlcallgraph" | + "htmldataflowgraph" | + "reachablepackages" | + "typescript" | + "tslibraryusage" | + "apiusage" | + "patternfiles" | + "patterns" | + "patternmatch" | + "clear" | + "reset" | + "exit"; + +/** + * Set the given options. + * Returns simple response. + */ +export interface OptionsRequest extends Request { + command: "options"; + arguments: Partial; +} + +/** + * Request expansion of list of files and/or directories. + * Returns ExpandFilesResponse. + */ +export interface ExpandPathsRequest extends Request { + command: "expandpaths"; + arguments: Array | string; +} + +export interface ExpandPathsResponse extends SuccessResponse { + body: Array; +} + +/** + * Select files. + * Returns simple response when analysis is completed. + */ +export interface FilesRequest extends Request { + command: "files"; + arguments: Array; +} + +/** + * Start analysis. + * Returns simple response when analysis is completed. + */ +export interface AnalyzeRequest extends Request { + command: "analyze"; +} + +/** + * Abort analysis if running. + * Returns simple response. + */ +export interface AbortRequest extends Request { + command: "abort"; +} + +/** + * Clear results etc. from last analysis. + * Returns simple response. + */ +export interface ClearRequest extends Request { + command: "clear"; +} + +/** + * Run TypeScript parser on the files selected in the last analysis. + * Returns simple response. + */ +export interface TypeScriptRequest extends Request { + command: "typescript"; +} + +/** + * Request analysis diagnostics (during or after analysis). + * Returns DiagnosticsResponse. + */ +export interface DiagnosticsRequest extends Request { + command: "diagnostics"; +} + +export interface DiagnosticsResponse extends SuccessResponse { + body: AnalysisDiagnostics; +} + +/** + * Run API usage analysis (after analysis). + * Returns ApiUsageResponse. + */ +export interface ApiUsageRequest extends Request { + command: "apiusage"; +} + +export interface ApiUsageResponse extends SuccessResponse { + body: AccessPathPatternStringToNodes; +} + +/** + * Set patterns from files (before analysis). + * Returns simple response. + */ +export interface PatternFilesRequest extends Request { + command: "patternfiles"; + arguments: Array; +} + +/** + * Set patterns (before analysis). + * Returns simple response. + */ +export interface PatternsRequest extends Request { + command: "patterns"; + arguments: Array; +} + +/** + * Perform pattern matching (after analysis). + * Returns PatternMatchResponse. + */ +export interface PatternMatchRequest extends Request { + command: "patternmatch"; +} + +export interface PatternMatchResponse extends SuccessResponse { + body: PatternMatchesJSON; +} + +/** + * Request call graph (after analysis). + * Returns CallGraphResponse. + */ +export interface CallGraphRequest extends Request { + command: "callgraph"; +} + +export interface CallGraphResponse extends SuccessResponse { + body: CallGraph; +} + +/** + * Request HTML call graph (after analysis). + * Returns simple response. + */ +export interface HTMLCallGraphRequest extends Request { + command: "htmlcallgraph"; +} + +/** + * Request HTML data-flow graph (after analysis). + * Returns simple response. + */ +export interface HTMLDataFlowGraphRequest extends Request { + command: "htmldataflowgraph"; +} + +/** + * Request TypeScript library usage (after TypeScript parsing). + * Returns TSLibraryUsageResponse. + */ +export interface TSLibraryUsageRequest extends Request { + command: "tslibraryusage"; +} + +export interface TSLibraryUsageResponse extends SuccessResponse { + body: LibraryUsageJSON; +} + +/** + * Request list of reachable packages (after analysis). + * Returns ReachablePackagesResponse. + */ +export interface ReachablePackagesRequest extends Request { + command: "reachablepackages"; +} + +export interface ReachablePackagesResponse extends SuccessResponse { + body: Array<{ + name: string; + version?: string; + }>; +} + +/** + * Resets options and clear analysis results etc. + * Returns simple response. + */ +export interface ResetRequest extends Request { + command: "reset"; +} diff --git a/typings/jalangi.d.ts b/typings/jalangi.d.ts new file mode 100644 index 0000000..a06da4d --- /dev/null +++ b/typings/jalangi.d.ts @@ -0,0 +1,316 @@ +export type IID = number; + +export type GIID = string; + +export interface SourceCodePointer { + line: number; + column: number; +} + +export interface CodeSnippetLocation { + start: SourceCodePointer; + end: SourceCodePointer; +} + +export interface SourceObject { + name: string, + loc: CodeSnippetLocation, + range?: [number, number], + internal?: boolean, + eval?: string +} + +export interface Jalangi { + + smap: { [key: number]: any }; + + initParams?: any; + + sid: IID; + + getGlobalIID(iid: IID): GIID; + + iidToLocation(iid: IID | GIID): string; + + iidToSourceObject(iid: IID): SourceObject; + + analysis?: JalangiAnalysis; + + smemory?: { + + getShadowObject( + obj: object, + prop: string, + isGetField: boolean + ): { owner: object, isProperty: boolean }; + + getShadowFrame(name: string): object; + + getIDFromShadowObjectOrFrame(obj: object): number | void; + + getActualObjectOrFunctionFromShadowObjectOrFrame(obj: object): any; + + getFrame(name: string): object; + + getShadowObjectOfObject(val: object): object | void; + + }; + +} + +export interface JalangiAnalysis { + + invokeFunPre?( + iid: IID, + f: Function, + base: object, + args: any[], + isConstructor: boolean, + isMethod: boolean, + functionIid: IID, + functionSid: IID + ): { f: Function, base: object, args: any[], skip: boolean } | void; + + invokeFun?( + iid: IID, + f: Function, + base: any, + args: any[], + result: any, + isConstructor: boolean, + isMethod: boolean, + functionIid: IID, + functionSid: IID + ): { result: any } | void; // note: not called if returned with exception! + + literal?( + iid: IID, + val: any, + hasGetterSetter: boolean + ): { result: any } | void; + + forinObject?( + iid: IID, + val: any + ): { result: any } | void; + + declare?( + iid: IID, + name: string, + val: any, + isArgument: boolean, + argumentIndex: number, + isCatchParam: boolean + ): { result: any } | void; + + getFieldPre?( + iid: IID, + base: any, + offset: string | any, + isComputed: boolean, + isOpAssign: boolean, + isMethodCall: boolean + ): { base: any, offset: any, skip: boolean } | void; + + getField?( + iid: IID, + base: any, + offset: string | any, + val: any, + isComputed: boolean, + isOpAssign: boolean, + isMethodCall: boolean + ): { result: any } | void; + + putFieldPre?( + iid: IID, + base: any, + offset: string | any, + val: any, + isComputed: boolean, + isOpAssign: boolean + ): { base: any, offset: any, val: any, skip: boolean } | void; + + putField?( + iid: IID, + base: any, + offset: string | any, + val: any, + isComputed: boolean, + isOpAssign: boolean + ): { result: any } | void; + + read?( + iid: IID, + name: string, + val: any, + isGlobal: boolean, + isScriptLocal: boolean, + ): { result: any } | void; + + write?( + iid: IID, + name: string, + val: any, + lhs: any, + isGlobal: any, + isScriptLocal: any + ): { result: any } | void; + + _return?( + iid: IID, + val: any + ): { result: any } | void; + + _throw?( + iid: IID, + val: any + ): { result: any } | void; + + _with?( + iid: IID, + val: any + ): { result: any } | void; + + functionEnter?( + iid: IID, + f: Function, + dis: any, + args: any[] + ): void; + + functionExit?( + iid: IID, + returnVal: any, + wrappedExceptionVal: { exception: any } | undefined + ): { returnVal: any, wrappedExceptionVal: any, isBacktrack: boolean } | void; + + binaryPre?( + iid: IID, + op: string, + left: any, + right: any, + isOpAssign: boolean, + isSwitchCaseComparison: boolean, + isComputed: boolean + ): { op: string, left: any, right: any, skip: boolean } | void; + + binary?( + iid: IID, + op: string, + left: any, + right: any, + result: any, + isOpAssign: boolean, + isSwitchCaseComparison: boolean, + isComputed: boolean + ): { result: any } | void; + + unaryPre?( + iid: IID, + op: string, + left: any + ): { op: string, left: any, skip: boolean } | void; + + unary?( + iid: IID, + op: string, + left: any, + result: any + ): { result: any } | void; + + conditional?( + iid: IID, + result: any + ): { result: any } | void; + + instrumentCodePre?( + iid: IID, + code: any, + isDirect: boolean + ): { code: any, skip: boolean } | void; + + instrumentCode?( + iid: IID, + newCode: any, + newAst: object, + isDirect: boolean + ): { result: any } | void; + + endExpression?(iid: IID): void; + + endExecution?(): void; + + runInstrumentedFunctionBody?( + iid: IID, + f: Function, + functionIid: IID, + functionSid: IID + ): boolean; + + onReady?(cb: Function): void; + + newSource?( + sourceInfo: { name: string, internal: boolean, eval?: string }, + source: string + ): void; + + evalPre?( + iid: IID, + str: string + ): void; + + evalPost?( + iid: IID, + str: string + ): void; + + evalFunctionPre?( + iid: IID, + func: Function, + receiver: object, + args: any, + ): void; + + evalFunctionPost?( + iid: IID, + func: Function, + receiver: object, + args: any, + ret: any + ): void; + + builtinEnter?( + name: string, + f: Function, + dis: any, + args: any, + ): void; + + builtinExit?( + name: string, + returnVal: any + ): { returnVal: any } | void; + + asyncFunctionEnter?( + iid: IID + ): void; + + asyncFunctionExit?( + iid: IID, + returnVal: any, + wrappedException: any + ): void; + + awaitPre?( + iid: IID, + valAwaited: any + ): void; + + awaitPost?( + iid: IID, + valAwaited: any, + result: any, + rejected: boolean + ): void; +} \ No newline at end of file diff --git a/typings/osv.d.ts b/typings/osv.d.ts new file mode 100644 index 0000000..d93073b --- /dev/null +++ b/typings/osv.d.ts @@ -0,0 +1,76 @@ +/** + * Open Source Vulnerability format. + * From https://github.com/renovatebot/osv-offline/blob/main/packages/osv-offline-db/src/lib/osv.ts. + * Fixed camelcase to underscores, added last_affected. + */ +export interface OpenSourceVulnerability { + affected?: Affected[]; + aliases?: string[]; + credits?: Credit[]; + database_specific?: { [key: string]: unknown }; + details?: string; + id: string; + modified: Date; + published?: Date; + references?: Reference[]; + related?: string[]; + schema_version?: string; + severity?: Severity[]; + summary?: string; + withdrawn?: Date; +} + +export interface Affected { + database_specific?: { [key: string]: unknown }; + ecosystem_specific?: { [key: string]: unknown }; + package?: Package; + ranges?: Range[]; + versions?: string[]; +} + +export interface Package { + ecosystem: string; + name: string; + purl?: string; +} + +export interface Range { + events: Event[]; + repo?: string; + type: RangeType; +} + +export interface Event { + introduced?: string; + fixed?: string; + limit?: string; + last_affected?: string; +} + +export type RangeType = 'ECOSYSTEM' | 'GIT' | 'SEMVER'; + +export interface Credit { + contact?: string[]; + name: string; +} + +export interface Reference { + type: ReferenceType; + url: string; +} + +export type ReferenceType = + |'ADVISORY' + |'ARTICLE' + |'FIX' + |'GIT' + |'PACKAGE' + |'REPORT' + |'WEB'; + +export interface Severity { + score: string; + type: SeverityType; +} + +export type SeverityType = 'CVSS_V3'; diff --git a/typings/tapir.d.ts b/typings/tapir.d.ts new file mode 100644 index 0000000..6fcbe85 --- /dev/null +++ b/typings/tapir.d.ts @@ -0,0 +1,97 @@ +// mono:tapir/src/pattern-finder/patch-description-types.ts + +export interface LibraryPatchDescriptionTypes { + [index: string]: ClientPatchType; +} + +export interface ClientPatchType { + excludedFolders?: string[]; + excludedFiles?: string[]; + includedFiles?: string[]; // new + patches: PatchType[]; + repo: RepoType; + install?: boolean; +} + +export interface PatchType { + file: string; + lineNumber: number; + classification: string; + truePositive?: boolean; +} + +export interface RepoType { + gitCommit: string; + gitURL: string; +} + +// mono:tapir/src/pattern-finder/pattern-language.ts + +export interface PatternWrapper { + pattern: string; + question?: string; + id: string; + changelogId?: string; + changelogDescription?: string; + deprecation?: boolean; + benign?: boolean; +} + +// mono:types/semantic-patches.d.ts + +export type SemanticPatch = { + version: number, + semanticPatch: { + detectionPattern: string; + primaryTemplate?: any; // not used here + objectModifiers?: any; // not used here + alternativeTemplate?: any; // not used here + suggestedFixDescription?: string; + transformationQuestion?: string; + unknownAccessPathQuestion?: string; + extraQuestion?: string; + expectedToFail?: boolean; + } + semanticPatchId: string, + breakingChangeId: string, + enabled: boolean, + comment: string +}; + +// new version of mono:types/semantic-patches.d.ts + +export interface SemanticPatchNew { + detectionPattern: string; + primaryTemplate?: any; // not used here + objectModifiers?: any; // not used here + alternativeTemplate?: any; // not used here + suggestedFixDescription?: string; + transformationQuestion?: string; + unknownAccessPathQuestion?: string; + extraQuestion?: string; + expectedToFail?: boolean; +} + +export type RepoMatches = [ + { + repo: { + gitURL: string, + gitCommit: string + }, + matches: [Match] + } +]; + +export type Match = { + file: string, + semanticPatchId: string; + semanticPatchVersion: number; + loc: string; + highConfidence: boolean, + questions: [{ + matchId: string, + answer: "yes" | "no" | null, + type: string, + text: string + }] +}; diff --git a/typings/vulnerabilities.d.ts b/typings/vulnerabilities.d.ts new file mode 100644 index 0000000..848dfea --- /dev/null +++ b/typings/vulnerabilities.d.ts @@ -0,0 +1,15 @@ +import {OpenSourceVulnerability} from "./osv"; + +/** + * OSV entry, optionally augmented by code location or access path. + */ +export interface Vulnerability { + osv: OpenSourceVulnerability; + location?: { + link?: string; + file: string; + line?: number; + code?: string; + } + patterns?: Array; +} \ No newline at end of file diff --git a/unsound.md b/unsound.md new file mode 100644 index 0000000..f2197fe --- /dev/null +++ b/unsound.md @@ -0,0 +1,47 @@ +# Known sources of unsoundness + +- dynamic property read/write (warnings) + - MemberExpression, OptionalMemberExpression + - ObjectProperty, ObjectMethod, ClassProperty, ClassMethod, ClassPrivateMethod, ClassAccessorProperty, ClassPrivateProperty + +- dynamic require/import + + +- prototype inheritance, super, extends (partly modeled...) + + +- ECMAScript standard library + - Function.prototype.{apply,call,bind} + - Object.{defineProperty, defineProperties, ...} + - Promise + - thenables (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables) + - 'throw' in promise handlers converting exceptions to rejections, rejected promises at 'await' converting rejections to exceptions + - AggregateError in Promises.any + - Array: + - 'from' with multiple arguments (mapFn and thisArg ignored) + - 'flat' with unknown depth (recursive flattening ignored) + - 'flatMap' (return value ignored) + - Symbol + - Proxy, Reflect, ... + + +- Node.js standard library (callbacks, events) + + +- PrivateName +- BindExpression (nonstandard) + + +- SpreadElement (warnings) + + +- ExportNamespaceSpecifier, ImportAttribute (warnings) + + +- CatchClause (ObjectTokens are widened at ThrowStatement but other tokens are ignored) + + +- assignment to 'arguments[...]' and arguments.callee (see arguments.js) +- user-defined iterators (standard iterators are supported) +- symbols (incl. "well-known symbols" like [Symbol.iterator] used for iterables) +- events