From 530dafe69055deb18554282b36ccd75fb0d203bd Mon Sep 17 00:00:00 2001 From: Nathan Poirier Date: Wed, 21 Oct 2026 02:28:16 +0200 Subject: [PATCH 1/2] Modernize tooling, tests and docs No change to the published behavior, except a type fix: LineReader.readLine() returns `string | undefined`. - Tooling: mise, pnpm, TypeScript, Vitest, Biome, GitHub Actions. - Tests checked against real JVMs. - New README and CONTRIBUTING.md. --- .config/mise.lock | 130 + .config/mise.toml | 5 + .gitattributes | 4 +- .github/dependabot.yml | 19 + .github/workflows/ci.yml | 106 + .github/workflows/latest-jdks.yml | 35 + .github/workflows/typedoc.yml | 27 - .gitignore | 147 +- .prettierrc.js | 14 - .travis.yml | 16 - CONTRIBUTING.md | 41 + README.md | 125 +- biome.jsonc | 49 + jest.config.js | 13 - package.json | 39 +- pnpm-lock.yaml | 978 +++++ pnpm-workspace.yaml | 2 + src/java-props.ts | 9 +- src/node-java-props.ts | 14 +- src/typedoc.d.ts | 1 - src/utils.ts | 47 +- test/api.test.ts | 64 - test/cases/parse-cases.mts | 202 + test/cases/parse-expected.json | 2619 ++++++++++++ test/cases/parse-snapshot.json | 869 ++++ test/compat/api-snapshot.json | 34 + test/compat/check-esm.mjs | 28 + test/compat/check-package.mjs | 41 + test/compat/check-types.mjs | 64 + test/compat/run-smoke.js | 133 + test/compat/types/consumer.ts | 42 + test/compat/types/stub/node/index.d.ts | 7 + test/data/testData1.dos.properties | 35 - test/data/testData1.properties | 35 - test/data/testData1.properties-result.json | 20 - test/data/testData2.dos.properties | 4 - test/data/testData2.properties | 4 - test/data/testData2.properties-result.json | 4 - test/data/testData3.dos.properties | 29 - test/data/testData3.properties-result.json | 4 - test/fixtures/README.md | 11 + .../backslashes.expected.json} | 0 .../backslashes.properties} | 0 .../fixtures/mixed-line-endings.expected.json | 10 + test/fixtures/mixed-line-endings.properties | 14 + test/fixtures/multiline.crlf.properties | 34 + test/fixtures/multiline.expected.json | 18 + test/fixtures/multiline.properties | 34 + .../syntax.expected.json} | 0 .../syntax.properties} | 0 test/java-props.test.ts | 169 - test/node-java-props.test.ts | 21 - test/support/data.mts | 33 + test/support/load-result.mts | 33 + test/unit/api.test.ts | 34 + test/unit/java-props.test.ts | 86 + test/unit/node-java-props.test.ts | 65 + test/unit/parse-cases.test.ts | 82 + test/unit/properties.test.ts | 36 + test/unit/types.test-d.ts | 44 + test/unit/utils.test.ts | 176 + tools/gen-parse-expected.mts | 95 + tools/java-reference/JavaReference.java | 77 + tools/java-reference/reference.mts | 101 + tsconfig.build.json | 16 + tsconfig.json | 26 +- tslint.json | 101 - typedoc.json | 9 - vitest.config.mts | 13 + yarn.lock | 3610 ----------------- 70 files changed, 6590 insertions(+), 4417 deletions(-) create mode 100644 .config/mise.lock create mode 100644 .config/mise.toml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/latest-jdks.yml delete mode 100644 .github/workflows/typedoc.yml delete mode 100644 .prettierrc.js delete mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 biome.jsonc delete mode 100644 jest.config.js create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml delete mode 100644 src/typedoc.d.ts delete mode 100644 test/api.test.ts create mode 100644 test/cases/parse-cases.mts create mode 100644 test/cases/parse-expected.json create mode 100644 test/cases/parse-snapshot.json create mode 100644 test/compat/api-snapshot.json create mode 100644 test/compat/check-esm.mjs create mode 100644 test/compat/check-package.mjs create mode 100644 test/compat/check-types.mjs create mode 100644 test/compat/run-smoke.js create mode 100644 test/compat/types/consumer.ts create mode 100644 test/compat/types/stub/node/index.d.ts delete mode 100644 test/data/testData1.dos.properties delete mode 100644 test/data/testData1.properties delete mode 100644 test/data/testData1.properties-result.json delete mode 100644 test/data/testData2.dos.properties delete mode 100644 test/data/testData2.properties delete mode 100644 test/data/testData2.properties-result.json delete mode 100644 test/data/testData3.dos.properties delete mode 100644 test/data/testData3.properties-result.json create mode 100644 test/fixtures/README.md rename test/{data/testData4.properties-result.json => fixtures/backslashes.expected.json} (100%) rename test/{data/testData4.properties => fixtures/backslashes.properties} (100%) create mode 100644 test/fixtures/mixed-line-endings.expected.json create mode 100644 test/fixtures/mixed-line-endings.properties create mode 100644 test/fixtures/multiline.crlf.properties create mode 100644 test/fixtures/multiline.expected.json create mode 100644 test/fixtures/multiline.properties rename test/{data/testData0.properties-result.json => fixtures/syntax.expected.json} (100%) rename test/{data/testData0.properties => fixtures/syntax.properties} (100%) delete mode 100644 test/java-props.test.ts delete mode 100644 test/node-java-props.test.ts create mode 100644 test/support/data.mts create mode 100644 test/support/load-result.mts create mode 100644 test/unit/api.test.ts create mode 100644 test/unit/java-props.test.ts create mode 100644 test/unit/node-java-props.test.ts create mode 100644 test/unit/parse-cases.test.ts create mode 100644 test/unit/properties.test.ts create mode 100644 test/unit/types.test-d.ts create mode 100644 test/unit/utils.test.ts create mode 100644 tools/gen-parse-expected.mts create mode 100644 tools/java-reference/JavaReference.java create mode 100644 tools/java-reference/reference.mts create mode 100644 tsconfig.build.json delete mode 100644 tslint.json delete mode 100644 typedoc.json create mode 100644 vitest.config.mts delete mode 100644 yarn.lock diff --git a/.config/mise.lock b/.config/mise.lock new file mode 100644 index 0000000..d3617ba --- /dev/null +++ b/.config/mise.lock @@ -0,0 +1,130 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + +lockfile_version = 3 + +[[tools.biome]] +version = "2.5.15" +backend = "aqua:biomejs/biome" +specifiers = ["2"] + +[tools.biome."platforms.linux-arm64"] +checksum = "sha256:a56bcd73ddbfdb0f57a82bb67b7ea698eed4027aff602b95aa18fafe919d59e0" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-linux-arm64" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227632" +provenance = "github-attestations" + +[tools.biome."platforms.linux-arm64-musl"] +checksum = "sha256:a56bcd73ddbfdb0f57a82bb67b7ea698eed4027aff602b95aa18fafe919d59e0" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-linux-arm64" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227632" +provenance = "github-attestations" + +[tools.biome."platforms.linux-x64"] +checksum = "sha256:5d867a0b2ccea1755b7e508b01d836a8c64e31a1e4d11ec24b43b6b4eec8b3b6" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-linux-x64" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227626" +provenance = "github-attestations" + +[tools.biome."platforms.linux-x64-musl"] +checksum = "sha256:5d867a0b2ccea1755b7e508b01d836a8c64e31a1e4d11ec24b43b6b4eec8b3b6" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-linux-x64" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227626" +provenance = "github-attestations" + +[tools.biome."platforms.macos-arm64"] +checksum = "sha256:3636796d8c78bfcfe0d45946dc93e21e12d27a49eba7401e18af1ea854d25503" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-darwin-arm64" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227628" +provenance = "github-attestations" + +[tools.biome."platforms.macos-x64"] +checksum = "sha256:86fc618563d19f373ac24eb5b8f1e41bd753a506d5df1f198c152ec365141490" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-darwin-x64" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227623" +provenance = "github-attestations" + +[tools.biome."platforms.windows-x64"] +checksum = "sha256:f2beadee753b78fe76081158123538be886a31b5e67a34863e42535f3f491926" +url = "https://github.com/biomejs/biome/releases/download/%40biomejs/biome%402.5.15/biome-win32-x64.exe" +url_api = "https://api.github.com/repos/biomejs/biome/releases/assets/601227624" +provenance = "github-attestations" + +[[tools.node]] +version = "26.12.0" +backend = "core:node" +specifiers = ["26"] + +[tools.node."platforms.linux-arm64"] +checksum = "sha256:ec53c915c3fdd8786c5685abd25ae3fe9fba0692307f9811d7caaadea1bcaf03" +url = "https://nodejs.org/dist/v26.12.0/node-v26.12.0-linux-arm64.tar.gz" + +[tools.node."platforms.linux-arm64-musl"] +checksum = "sha256:2e70b6d5adbae94546c4d7e01de7b64c50bf8d3dbfaad2bb0774942e9da902b8" +url = "https://unofficial-builds.nodejs.org/download/release/v26.12.0/node-v26.12.0-linux-arm64-musl.tar.gz" + +[tools.node."platforms.linux-x64"] +checksum = "sha256:d9f88af72ad34f7ddf8e67289829f1698f54b6254f7d4f54b7453117ff1a3d2c" +url = "https://nodejs.org/dist/v26.12.0/node-v26.12.0-linux-x64.tar.gz" + +[tools.node."platforms.linux-x64-musl"] +checksum = "sha256:48ba1cdf21e684f13f38b6295f43bfea951f1ffd008b4cf93361c966b33f6938" +url = "https://unofficial-builds.nodejs.org/download/release/v26.12.0/node-v26.12.0-linux-x64-musl.tar.gz" + +[tools.node."platforms.macos-arm64"] +checksum = "sha256:2a154d4a5458930e0a2d6a1ecf64eddf5a2b3e1c13636b609c2ecb042e8c4553" +url = "https://nodejs.org/dist/v26.12.0/node-v26.12.0-darwin-arm64.tar.gz" + +[tools.node."platforms.macos-x64"] +checksum = "sha256:878ecede191c7abc1bff01d3fb81f7ed81aa1161040dba18776b8ff8593614b1" +url = "https://nodejs.org/dist/v26.12.0/node-v26.12.0-darwin-x64.tar.gz" + +[tools.node."platforms.windows-x64"] +checksum = "sha256:50efaf01a08c520b4bbe499c09a8c5a5c370002c419c0d9da2c6815d3066a453" +url = "https://nodejs.org/dist/v26.12.0/node-v26.12.0-win-x64.zip" + +[[tools.pnpm]] +version = "12.13.0" +backend = "aqua:pnpm/pnpm" +specifiers = ["12"] + +[tools.pnpm."platforms.linux-arm64"] +checksum = "sha256:1e1b4d7ddef2577d7b50444a30b76bf4f62dcb45039ef0c4454a377384a36618" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-linux-arm64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319805" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-arm64-musl"] +checksum = "sha256:948d410fe403f1cd87d70d618480dc8b97429cd2287c6fb9777ec77f17a851c3" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-linux-arm64-musl.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319802" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-x64"] +checksum = "sha256:7a796532f2b8801e7fab92389cb02c7ada0cdfbaaded5d291052d83e52c06047" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-linux-x64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319815" +provenance = "github-attestations" + +[tools.pnpm."platforms.linux-x64-musl"] +checksum = "sha256:402f3df5c7ae51271a13f34bfbdb97ef5aaab0ae359d8ef825574307dc59db13" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-linux-x64-musl.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319800" +provenance = "github-attestations" + +[tools.pnpm."platforms.macos-arm64"] +checksum = "sha256:697961ea2f337fff60bd59ed25991d6165826751bc75ee4b7fad6923b7584c6e" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-darwin-arm64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319811" +provenance = "github-attestations" + +[tools.pnpm."platforms.macos-x64"] +checksum = "sha256:9803f36f1b7fe08a0e968776699fb10715b29e15a4b92710c25c0c9b5519ffcf" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-darwin-x64.tar.gz" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319810" +provenance = "github-attestations" + +[tools.pnpm."platforms.windows-x64"] +checksum = "sha256:7e235180c70c0fc6f7805af1a19a3c277d7c30ce2db17045f5dd430e37ee582e" +url = "https://github.com/pnpm/pnpm/releases/download/v12.13.0/pnpm-win32-x64.zip" +url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/636319814" +provenance = "github-attestations" diff --git a/.config/mise.toml b/.config/mise.toml new file mode 100644 index 0000000..108516e --- /dev/null +++ b/.config/mise.toml @@ -0,0 +1,5 @@ +# Development tools (the published package itself supports Node.js >= 6). +[tools] +node = "26" +pnpm = "12" +biome = "2" diff --git a/.gitattributes b/.gitattributes index 9db71a9..474cb10 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ -/test/data/*.properties binary +* text=auto eol=lf +# Test fixtures: byte-exact (some use CRLF / mixed line endings on purpose) +/test/fixtures/*.properties binary diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..afcfa47 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + cooldown: + default-days: 7 # like minimumReleaseAge in pnpm-workspace.yaml + groups: + dev-dependencies: + patterns: ['*'] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + github-actions: + patterns: ['*'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e19112a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + test: + name: Lint, test, build (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: jdx/mise-action@v5 + - run: pnpm install --frozen-lockfile + - run: pnpm run lint + - run: pnpm test + - run: pnpm run build + - run: node test/compat/check-package.mjs + - if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + retention-days: 7 + + # The built package must keep working on every supported Node.js version + compat-node: + name: Node.js ${{ matrix.node }} + needs: test + strategy: + fail-fast: false + matrix: + node: [6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + - uses: actions/setup-node@v7 + with: + node-version: ${{ matrix.node }} + - run: node test/compat/run-smoke.js + - if: matrix.node >= 12 + run: node test/compat/check-esm.mjs + + # The type declarations must keep working with every supported TypeScript version + compat-types: + name: TypeScript ${{ matrix.typescript }} + needs: test + strategy: + fail-fast: false + matrix: + typescript: ['2.9', '3.9', '4.9', '5.9', '6.0', latest] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist/ + - uses: jdx/mise-action@v5 + - run: node test/compat/check-types.mjs ${{ matrix.typescript }} + + # Expected results must match real JVMs, for each Properties.load() era (see LOAD_ERAS in test/support/load-result.mts) + java-reference: + name: Java reference + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - {id: jdk8, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '8'}} + - {id: jdk11_0_4, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '11.0.4'}} + - {id: jdk11_0_5, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '11.0.5'}} + - {id: jdk11, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '11'}} + - {id: jdk12, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '12'}} + - {id: jdk13, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '13'}} + - {id: jdk17, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '17'}} + - {id: jdk21, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '21'}} + - {id: jdk25, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '25'}} + - uses: jdx/mise-action@v5 + - run: node tools/gen-parse-expected.mts --check # no dependency to install + env: + JAVA_REFERENCE_JDKS: >- + 8=${{ steps.jdk8.outputs.path }}/bin/java, + 11.0.4=${{ steps.jdk11_0_4.outputs.path }}/bin/java, + 11.0.5=${{ steps.jdk11_0_5.outputs.path }}/bin/java, + 11=${{ steps.jdk11.outputs.path }}/bin/java, + 12=${{ steps.jdk12.outputs.path }}/bin/java, + 13=${{ steps.jdk13.outputs.path }}/bin/java, + 17=${{ steps.jdk17.outputs.path }}/bin/java, + 21=${{ steps.jdk21.outputs.path }}/bin/java, + 25=${{ steps.jdk25.outputs.path }}/bin/java diff --git a/.github/workflows/latest-jdks.yml b/.github/workflows/latest-jdks.yml new file mode 100644 index 0000000..25e6647 --- /dev/null +++ b/.github/workflows/latest-jdks.yml @@ -0,0 +1,35 @@ +name: Latest JDKs + +# A new Java release could change Properties.load() (last change: JDK 13). This checks the committed Java results +# against the latest GA and early-access JDKs: a failure means a new behavior era (see LOAD_ERAS). +on: + schedule: + - cron: '49 1 20 * *' # monthly + workflow_dispatch: + +permissions: + contents: read + +jobs: + java-reference: + name: Java reference (${{ matrix.jdk }}) + strategy: + fail-fast: false + matrix: + jdk: [ga, ea] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - if: matrix.jdk == 'ga' + uses: oracle-actions/setup-java@v1 + - if: matrix.jdk == 'ea' + uses: oracle-actions/setup-java@v1 + with: {website: jdk.java.net, release: EA, version: stable} + - run: | + "$JAVA_HOME/bin/java" -version + echo "JAVA_REFERENCE_JDKS=${{ matrix.jdk }}=$JAVA_HOME/bin/java" >> "$GITHUB_ENV" + # javac for the reference program (--release 8), independent of the JDK under test + - uses: actions/setup-java@v6 + with: {distribution: zulu, java-version: '25'} + - uses: jdx/mise-action@v5 + - run: node tools/gen-parse-expected.mts --check # no dependency to install diff --git a/.github/workflows/typedoc.yml b/.github/workflows/typedoc.yml deleted file mode 100644 index 37709e1..0000000 --- a/.github/workflows/typedoc.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: TypeDoc - -on: - push: - branches: - - master - -jobs: - typedoc: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Use Node.js v16.x - uses: actions/setup-node@v2 - with: - node-version: v16.x - - name: Generate - run: | - npm i -g typedoc - yarn install --frozen-lockfile - typedoc - - name: Deploy - uses: JamesIves/github-pages-deploy-action@4.1.5 - with: - branch: typedoc - folder: ./typedoc diff --git a/.gitignore b/.gitignore index d771194..f14183c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,143 +1,16 @@ -### App -/dist -/typedoc - -### Node -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories +# Dependencies and outputs node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' +/dist/ +/coverage/ +/tools/java-reference/build/ *.tgz +*.log -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test -.env*.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -.next-* - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# gyp build -/build/ +# Local files, not shared +.local/ +*.local.* -### IDE -# JetBrains IDE +# Editors and OS .idea/ -*.iml -*.iws -*.ipr - -# Eclipse -.settings/ -.classpath -.project - -# NetBeans -nbproject/ -nbactions.xml -nb-configuration.xml - -# VS Code .vscode/ -*.code-workspace -.history/ +.DS_Store diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 5a93b9f..0000000 --- a/.prettierrc.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - trailingComma: 'es5', - printWidth: 120, - tabWidth: 4, - semi: true, - singleQuote: true, - bracketSpacing: false, - overrides: [ - { - files: ['*.json', '*.yml'], - options: {tabWidth: 2}, - }, - ], -}; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5766015..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: node_js -node_js: - - '16' - - '14' - - '12' - - '10' - - '8' - - '6' -install: - - npm install -g yarn - - npm install -g codecov - - yarn install -script: - - yarn run test - - '[[ "$(node -v)" == v6.* ]] && echo skip || codecov' - - yarn run build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ed0342c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing + +The goal is to keep a simple project without unnecessary features: please open an issue to discuss a new feature +before working on it. + +## Development + +Requirements: Node.js, [pnpm](https://pnpm.io/) and [Biome](https://biomejs.dev/). Their versions are pinned in +`.config/mise.toml`: with [mise](https://mise.jdx.dev/), `mise install` installs them. + +```bash +mise install # optional: installs the pinned tools +pnpm install +pnpm test # unit tests (with coverage) +pnpm test -u # update the snapshots (test/**/*-snapshot.json) after an intended change +pnpm run lint # Biome + type-check +pnpm run test:compat # build, then check the package (test/compat/run-smoke.js also runs on Node.js >= 6) +``` + +## Java reference + +Java is the reference: the Java results of the edge cases (`test/cases/parse-expected.json`) and of the fixtures +(`test/fixtures`) are generated and checked by running real JVMs, one or more per behavior era of `Properties.load()`. +It needs `javac` from a JDK 9+ on the `PATH`, and the JDKs to compare: + +```bash +# JDKs to use (default: java from PATH): name=/path/to/bin/java,... +export JAVA_REFERENCE_JDKS="8=/opt/jdk8/bin/java,11.0.4=/opt/jdk-11.0.4/bin/java,25=/opt/jdk25/bin/java" +pnpm run reference:gen # regenerate +pnpm run reference:check # verify +``` + +A monthly CI job (`.github/workflows/latest-jdks.yml`) also checks the latest GA and early-access JDKs, to catch a +behavior change in a new Java release. + +## Compatibility + +Existing users must never break: the runtime behavior, the exports (including `java-props/dist/*` deep imports) and +the supported environments (Node.js >= 6, ES2015, TypeScript >= 2.9) are checked in CI. + +Releases follow [SemVer](https://semver.org/). diff --git a/README.md b/README.md index 0389ca2..dd0da9b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ # java-props [![npm version](https://img.shields.io/npm/v/java-props.svg)](https://www.npmjs.com/package/java-props) -![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg) -[![Build Status](https://app.travis-ci.com/nathan818fr/node-java-props.svg?branch=master)](https://app.travis-ci.com/nathan818fr/node-java-props) -[![codecov](https://codecov.io/gh/nathan818fr/node-java-props/branch/master/graph/badge.svg)](https://codecov.io/gh/nathan818fr/node-java-props) +[![CI](https://github.com/nathan818fr/node-java-props/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/nathan818fr/node-java-props/actions/workflows/ci.yml) +[![license](https://img.shields.io/npm/l/java-props.svg)](LICENSE) -Read/Parse Java .properties files (using EXACTLY the same [specification](https://docs.oracle.com/javase/10/docs/api/java/util/Properties.html#load%28java.io.Reader%29)) in Javascript (browser) and Node.js. +Read and write Java `.properties` files in JavaScript (Node.js and browsers). The parser implements the syntax of +Java's [`Properties.load()`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Properties.html#load(java.io.Reader)). -This module provides its own TypeScript declarations (.d.ts). +- Full syntax: `=`, `:` or whitespace separators, `#` and `!` comments, continuation lines, escapes and `\uXXXX`, + LF, CRLF and CR line endings +- [Edge cases](test/cases/parse-cases.mts) checked against real JVMs, from Java 8 to 25 +- Any key is safe, including `__proto__`; malformed `\uXXXX` escapes throw, like in Java +- No dependencies, TypeScript declarations included +- Node.js >= 6, browsers (ES2015), CommonJS and ESM, TypeScript >= 2.9 ## Installation @@ -15,70 +20,96 @@ This module provides its own TypeScript declarations (.d.ts). npm install java-props ``` -## Example +## Usage + +```javascript +import {parse, parseFile, stringify} from 'java-props'; +// or: const {parse, parseFile, stringify} = require('java-props'); +``` + +### Read a file ```properties -# file.properties -a = Hello World -b : Node.js\u00AE -c value -d=foo\ - bar +# config.properties +app.name = My App +app.title : Caf\u00e9 \u2615 +app.greeting Hello, \ + World! +db.url=jdbc:postgresql://localhost:5432/app +install.dir = C:\\Program Files\\App +key\ with\ spaces = value +! this is a comment too ``` ```javascript -const javaProps = require('java-props'); - -javaProps - .parseFile('./file.properties') - .then((props) => { - console.log(props); - // { a: 'Hello World', b: 'Node.js®', c: 'value', d: 'foobar' } - }) - .catch((err) => { - console.error(err); - }); +const props = await parseFile('config.properties'); +// { +// 'app.name': 'My App', +// 'app.title': 'Café ☕', +// 'app.greeting': 'Hello, World!', +// 'db.url': 'jdbc:postgresql://localhost:5432/app', +// 'install.dir': 'C:\\Program Files\\App', +// 'key with spaces': 'value' +// } ``` -## Documentation +### Parse a string -For a detailed API reference, see: -[node-java-props.nathan818.fr](https://node-java-props.nathan818.fr/modules.html) +```javascript +parse('a = 1\nb : 2'); +// { a: '1', b: '2' } +``` -## Building +### Stringify -This project uses TypeScript. To create javascript sources run: +```javascript +stringify({'key with spaces': 'Café ☕', 'line.break': 'a\nb'}); +``` -```sh -yarn run build +```properties +key\ with\ spaces: Caf\u00E9 \u2615 +line.break: a\nb ``` -## Testing +## API -To run the test suite, first install the dependencies, then run `yarn test`: +### `parse(str)` -```bash -yarn install -yarn test -``` +Parses a .properties string and returns its properties, as an object with a `null` prototype (any key is safe, +including `__proto__`). Throws an `Error` on malformed `\uXXXX` escapes (where Java throws an +`IllegalArgumentException`). -## Contributing +### `parseFile(path, encoding?)` + +_Node.js only._ Reads and parses a .properties file, and returns a `Promise` of its properties. `path` is a file path +(`string`, `Buffer` or `URL`) or a file descriptor. `encoding` defaults to `'utf8'`; use `'latin1'` for files written by +Java's `Properties.store(OutputStream)`. -Contributions are welcome. +### `stringify(props)` -The goal is to keep a simple project without unnecessary (non essential) features. -It is recommended to open an issue before introducing new features to discuss them. +Converts an object of string values (own enumerable properties only) to a .properties string, which Java reads back as +the same keys and values. -## Versioning +
+Low-level functions (java-props/dist/utils) -We use [SemVer](http://semver.org/) for versioning. -For the versions available, see the [tags on this repository](https://github.com/nathan818fr/node-java-props/tags). +The building blocks of `parse` and `stringify`, for finer parsing (e.g. to keep the line order or the raw key/value +text): -## Authors +- `rawParse(str, consumeFn)`: calls `consumeFn({line, sepStart, valueStart})` for each logical line + (the key is `line.substring(0, sepStart)` and the value `line.substring(valueStart)`, both still escaped). +- `rawParseLine(line)`: the key/value boundaries of one logical line. +- `new LineReader(str).readLine()`: the next logical line, or `undefined` at the end. +- `decodeLine(str)` / `encodeLine(str, isKey?)`: decodes / escapes a key or a value. + +
+ +The default export (`javaProps.default`) and `convertLine` (alias of `decodeLine`) are deprecated. + +## Contributing -- [Nathan Poirier](https://github.com/nathan818fr) <nathan@poirier.io> +Contributions are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md). ## License -This project is licensed under the MIT License. -See the [LICENSE](https://github.com/nathan818fr/node-java-props/blob/master/LICENSE) file for details. +[MIT](LICENSE) diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000..d80030b --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,49 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json", + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "files": { + // Also skips git-ignored files. Generated files keep their JSON.stringify formatting. + "includes": ["**", "!test/**/*-snapshot.json", "!test/cases/parse-expected.json"] + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 120, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "bracketSpacing": false, + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "always" + } + }, + "json": { "formatter": { "indentWidth": 2 } }, + "linter": { + "rules": { + "preset": "recommended", + "style": { "useTemplate": "off" } + } + }, + "assist": { "actions": { "source": { "organizeImports": "off" } } }, + "overrides": [ + { + // Code for Node.js >= 6 (check-esm.mjs: >= 12): no `node:` imports (Node >= 14.18), no `Object.hasOwn` (ES2022). + // The emitted .d.ts is read by TypeScript >= 2.9: no inline `type` imports (TS >= 4.5). + "includes": ["src/**", "test/compat/run-smoke.js", "test/compat/check-esm.mjs"], + "linter": { + "rules": { + "style": { "useNodejsImportProtocol": "off", "useImportType": "off" }, + "suspicious": { "noPrototypeBuiltins": "off" } + } + } + }, + { + // Fixture compiled by old TypeScript versions (2.9+): old syntax and unused variables on purpose. + "includes": ["test/compat/types/**"], + "linter": { "enabled": false } + } + ] +} diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index f690be0..0000000 --- a/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - globals: { - 'ts-jest': { - tsConfig: 'tsconfig.json', - }, - }, - moduleFileExtensions: ['ts', 'js'], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - }, - testMatch: ['**/test/**/*.test.(ts|js)'], - testEnvironment: 'node', -}; diff --git a/package.json b/package.json index 39d8041..a6986c5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "java-props", "version": "2.3.1", - "description": "Read Java .properties files (using the same specification), without useless additional features.", + "description": "Read and write Java .properties files, with the syntax of Java's Properties.load().", "author": "Nathan Poirier ", "license": "MIT", "keywords": [ @@ -13,33 +13,32 @@ ], "repository": { "type": "git", - "url": "https://github.com/nathan818fr/node-java-props.git" + "url": "git+https://github.com/nathan818fr/node-java-props.git" }, "files": [ "dist/**" ], + "type": "commonjs", "main": "dist/node-java-props.js", "browser": "dist/java-props.js", + "sideEffects": false, "scripts": { - "clean": "rimraf ./dist", - "build": "yarn run clean && yarn run build:ts", - "build:ts": "tsc", - "lint": "tslint -c ./tslint.json -p ./tsconfig.json", - "format": "prettier -w ./src \"./test/**.ts\" \"*.md\" \"*.json\" \"*.js\" \"*.yml\"", - "test": "jest --coverage --verbose", - "test:watch": "yarn run test -- --watchAll", - "release": "yarn run lint && yarn run build && np" + "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"", + "build": "pnpm run clean && tsc -p tsconfig.build.json", + "lint": "biome check && tsc -p tsconfig.json", + "format": "biome check --write", + "test": "vitest run --coverage", + "test:watch": "vitest", + "pretest:compat": "pnpm run build", + "test:compat": "node test/compat/run-smoke.js && node test/compat/check-esm.mjs && node test/compat/check-package.mjs", + "reference:gen": "node tools/gen-parse-expected.mts", + "reference:check": "node tools/gen-parse-expected.mts --check" }, "devDependencies": { - "@types/jest": "^24.0.18", - "@types/node": "^12.7.11", - "jest": "^24.9.0", - "rimraf": "^3.0.2", - "ts-jest": "^24.3.0", - "tslint": "^5.20.1", - "typescript": "^3.9.10" - }, - "resolutions": { - "stack-utils": "1.0.2" + "@types/node": "^26.6.5", + "@vitest/coverage-v8": "^5.0.4", + "fast-check": "^4.10.2", + "typescript": "^7.0.2", + "vitest": "^5.0.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..615531b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,978 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^26.6.5 + version: 26.6.5 + '@vitest/coverage-v8': + specifier: ^5.0.4 + version: 5.0.4(vitest@5.0.4) + fast-check: + specifier: ^4.10.2 + version: 4.10.2 + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vitest: + specifier: ^5.0.4 + version: 5.0.4(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(vite@8.3.4(@types/node@26.6.5)) + +packages: + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@oxc-project/types@0.153.0': + resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} + + '@rolldown/binding-android-arm-eabi@1.2.13': + resolution: {integrity: sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.13': + resolution: {integrity: sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.13': + resolution: {integrity: sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.13': + resolution: {integrity: sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.13': + resolution: {integrity: sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.13': + resolution: {integrity: sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.13': + resolution: {integrity: sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.13': + resolution: {integrity: sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.13': + resolution: {integrity: sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.13': + resolution: {integrity: sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.13': + resolution: {integrity: sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.13': + resolution: {integrity: sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.13': + resolution: {integrity: sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.13': + resolution: {integrity: sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.13': + resolution: {integrity: sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@26.6.5': + resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@vitest/coverage-v8@5.0.4': + resolution: {integrity: sha512-P5YcQY8Y6RmoE9xRXI38rT8pssbcubEzqtYTM2XQoxOXMDn04nRaTI1SM8qCl9/NfW3ag9GlzdSXu7IwO5Ft8A==} + peerDependencies: + '@vitest/browser': 5.0.4 + vitest: 5.0.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/istanbul-lib-coverage@1.0.2': + resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} + engines: {node: '>=22'} + + '@vitest/istanbul-lib-report@1.0.2': + resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} + engines: {node: '>=22'} + + '@vitest/mocker@5.0.4': + resolution: {integrity: sha512-tqAlrYWce4iUxhr3ncNohejtCTOcHeE8XWrSNZY3OhPoDn01yeGv+gnaBy64ATp7o6pb03eIbBAjzmibpc9vIQ==} + peerDependencies: + msw: ^2.4.9 || ^3.0.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/spy@5.0.4': + resolution: {integrity: sha512-MZKk/gSkm2q+ICobMxCpH1mA+cCAWm8WV7UhxlqGjq/sJ3AGc1BqFOs5CRVXxH78wQtme9q/NAAgBquj4AcyMw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-check@4.10.2: + resolution: {integrity: sha512-iK2f+YrcmoeGqk6fA0ea2bptcu/itMIm4NfEozq6N25+aG6h7s5HZbB/k1aV7b5w5sFLMCbbtRUsTVR+BgC3xw==} + engines: {node: '>=12.17.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} + + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} + engines: {node: '>=12.20.0'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.30: + resolution: {integrity: sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==} + engines: {node: ^10 || ^12 || >=14} + + pure-rand@8.4.2: + resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} + + rolldown@1.2.13: + resolution: {integrity: sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + tinybench@6.2.2: + resolution: {integrity: sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==} + engines: {node: '>=20.0.0'} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + vite@8.3.4: + resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@5.0.4: + resolution: {integrity: sha512-z0WxZRV2Sg48wF0FSgMq3TFG8JS38/Qr96vZVZdX6+Em9raQTvnXQvdTFJe2m3rfyJnEHeHQ/snceIdi8D1pVg==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.4 + '@vitest/browser-preview': 5.0.4 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.4 + '@vitest/coverage-v8': 5.0.4 + '@vitest/ui': 5.0.4 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + +snapshots: + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@oxc-project/types@0.153.0': {} + + '@rolldown/binding-android-arm-eabi@1.2.13': + optional: true + + '@rolldown/binding-android-arm64@1.2.13': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.13': + optional: true + + '@rolldown/binding-darwin-x64@1.2.13': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.13': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.13': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.13': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.13': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.13': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.13': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.13': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.13': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.13': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.13': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.13': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@26.6.5': + dependencies: + undici-types: 8.9.0 + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@vitest/coverage-v8@5.0.4(vitest@5.0.4)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-report': 1.0.2 + ast-v8-to-istanbul: 1.0.7 + magicast: 0.5.5 + obug: 3.0.0 + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.0.4(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(vite@8.3.4(@types/node@26.6.5)) + + '@vitest/istanbul-lib-coverage@1.0.2': {} + + '@vitest/istanbul-lib-report@1.0.2': + dependencies: + '@vitest/istanbul-lib-coverage': 1.0.2 + + '@vitest/mocker@5.0.4(vite@8.3.4(@types/node@26.6.5))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.4(@types/node@26.6.5) + + '@vitest/spy@5.0.4': {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.7: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + chai@6.3.0: {} + + detect-libc@2.1.2: {} + + es-module-lexer@2.3.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.4.0: {} + + fast-check@4.10.2: + dependencies: + pure-rand: 8.4.2 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + js-tokens@10.0.0: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + magic-string@1.4.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.5.5: + dependencies: + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + source-map-js: 1.2.2 + + nanoid@3.3.20: {} + + obug@3.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.30: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + pure-rand@8.4.2: {} + + rolldown@1.2.13: + dependencies: + '@oxc-project/types': 0.153.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.13 + '@rolldown/binding-android-arm64': 1.2.13 + '@rolldown/binding-darwin-arm64': 1.2.13 + '@rolldown/binding-darwin-x64': 1.2.13 + '@rolldown/binding-freebsd-x64': 1.2.13 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.13 + '@rolldown/binding-linux-arm64-gnu': 1.2.13 + '@rolldown/binding-linux-arm64-musl': 1.2.13 + '@rolldown/binding-linux-ppc64-gnu': 1.2.13 + '@rolldown/binding-linux-s390x-gnu': 1.2.13 + '@rolldown/binding-linux-x64-gnu': 1.2.13 + '@rolldown/binding-linux-x64-musl': 1.2.13 + '@rolldown/binding-openharmony-arm64': 1.2.13 + '@rolldown/binding-win32-arm64-msvc': 1.2.13 + '@rolldown/binding-win32-x64-msvc': 1.2.13 + + source-map-js@1.2.2: {} + + std-env@4.3.0: {} + + tinybench@6.2.2: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.2.0: {} + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + undici-types@8.9.0: {} + + vite@8.3.4(@types/node@26.6.5): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.30 + rolldown: 1.2.13 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.5 + fsevents: 2.3.3 + + vitest@5.0.4(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(vite@8.3.4(@types/node@26.6.5)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@26.6.5)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@26.6.5) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 26.6.5 + '@vitest/coverage-v8': 5.0.4(vitest@5.0.4) + transitivePeerDependencies: + - msw + + why-is-node-running@3.2.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..1562b6f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +# pnpm settings (single-package repository) +minimumReleaseAge: 10080 # minutes (7 days): don't install freshly published versions diff --git a/src/java-props.ts b/src/java-props.ts index 22e502a..41b1c2a 100644 --- a/src/java-props.ts +++ b/src/java-props.ts @@ -48,10 +48,11 @@ export function stringify(props: Properties): string { return str; } -/** - * @deprecated - */ -export default { +const _default = { parse, stringify, }; +/** + * @deprecated + */ +export default _default; diff --git a/src/node-java-props.ts b/src/node-java-props.ts index 2512b7e..2085833 100644 --- a/src/node-java-props.ts +++ b/src/node-java-props.ts @@ -1,3 +1,4 @@ +/// import fs from 'fs'; import {Properties, parse, stringify} from './java-props'; @@ -33,7 +34,8 @@ export * from './java-props'; */ export function parseFile(path: string | Buffer | URL | number, encoding?: string): Promise { return new Promise((resolve, reject) => { - fs.readFile(path, {encoding: encoding || 'utf8', flag: 'r'}, (err, data) => { + // `encoding` accepts any string: unknown encodings reject + fs.readFile(path, {encoding: (encoding || 'utf8') as BufferEncoding, flag: 'r'}, (err, data) => { if (err) { return reject(err); } @@ -41,18 +43,18 @@ export function parseFile(path: string | Buffer | URL | number, encoding?: strin const res = parse(data); return resolve(res); } catch (err) { - /* istanbul ignore next */ return reject(err); } }); }); } -/** - * @deprecated - */ -export default { +const _default = { parse, parseFile, stringify, }; +/** + * @deprecated + */ +export default _default; diff --git a/src/typedoc.d.ts b/src/typedoc.d.ts deleted file mode 100644 index 1caa89f..0000000 --- a/src/typedoc.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {Properties, parse, parseFile, stringify} from './node-java-props'; diff --git a/src/utils.ts b/src/utils.ts index 7733b4d..cfebec8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,20 @@ +/* + * Port of the JDK 8 implementation of java.util.Properties: + * - rawParse / rawParseLine: Properties.load0() + * - LineReader: Properties.LineReader.readLine() + * - decodeLine: Properties.loadConvert() + * - encodeLine: Properties.saveConvert() + * + * Everything exported here is public API (`java-props/dist/utils`). + */ + const DECODE_PATTERN = /(?:\\u(.{0,4})|\\(.?))/g; const UNICODE_PATTERN = /^[0-9a-fA-F]{4}$/; +// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters must be escaped const ENCODE_PATTERN = /(?:[\u0000-\u001F\\\u007F-\uFFFF])/g; -const ENCODE_KEY_PATTERN = /(?:[\u0000-\u0020!#:=\\\u007F-\uFFFF])/g; // ENCODE_PATTERN with separators + comments +// biome-ignore lint/suspicious/noControlCharactersInRegex: ENCODE_PATTERN + separators (=: and whitespace) + comment chars (#!) +const ENCODE_KEY_PATTERN = /(?:[\u0000-\u0020!#:=\\\u007F-\uFFFF])/g; export interface ParsedLine { line: string; @@ -10,14 +22,20 @@ export interface ParsedLine { valueStart: number; } +/** + * Splits a .properties string into logical lines, and calls `consumeFn` with the key/value boundaries of each one. + */ export function rawParse(str: string, consumeFn: (res: ParsedLine) => void): void { const lr = new LineReader(str); - let line; - while ((line = lr.readLine()) !== undefined) { + for (let line = lr.readLine(); line !== undefined; line = lr.readLine()) { consumeFn(rawParseLine(line)); } } +/** + * Finds the key/value boundaries of a logical line (as returned by {@link LineReader.readLine}). + * The key is `line.substring(0, sepStart)` and the value is `line.substring(valueStart)`, both still escaped. + */ export function rawParseLine(line: string): ParsedLine { let keyLen = 0; let valueStart = line.length; @@ -58,6 +76,11 @@ export function rawParseLine(line: string): ParsedLine { return {line, sepStart: keyLen, valueStart}; } +/** + * Decodes the escape sequences of a key or a value (`\t`, `\uXXXX`, `\=`, ...). + * + * @throws Error on malformed `\uXXXX` sequences + */ export function decodeLine(line: string): string { return line.replace(DECODE_PATTERN, (_, unicode, char) => { if (unicode !== undefined) { @@ -80,6 +103,9 @@ export function decodeLine(line: string): string { }); } +/** + * Escapes a value, or a key if `isKey` is true (keys also escape spaces, separators and comment chars). + */ export function encodeLine(line: string, isKey?: boolean): string { let str = line.replace(isKey ? ENCODE_KEY_PATTERN : ENCODE_PATTERN, (c) => { if (c === '\t') { @@ -93,11 +119,8 @@ export function encodeLine(line: string, isKey?: boolean): string { } else if (c >= ' ' && c <= '~') { return '\\' + c; } else { - const code = c.charCodeAt(0); - if (code < 16) return '\\u000' + code.toString(16).toUpperCase(); - if (code < 256) return '\\u00' + code.toString(16).toUpperCase(); - if (code < 4096) return '\\u0' + code.toString(16).toUpperCase(); - return '\\u' + code.toString(16).toUpperCase(); + const hex = c.charCodeAt(0).toString(16).toUpperCase(); + return '\\u' + '0000'.substring(hex.length) + hex; } }); if (!isKey) { @@ -114,6 +137,9 @@ export function encodeLine(line: string, isKey?: boolean): string { */ export const convertLine = decodeLine; +/** + * Reads logical lines: skips comments, blank lines and leading whitespace, and joins continuation lines. + */ export class LineReader { public readonly str: string; public readonly strLen: number; @@ -124,7 +150,10 @@ export class LineReader { this.strLen = str.length; } - public readLine(): string { + /** + * @return The next logical line, or `undefined` at the end of the input + */ + public readLine(): string | undefined { let skipWhiteSpace = true; let commentLine = false; let newLine = true; diff --git a/test/api.test.ts b/test/api.test.ts deleted file mode 100644 index 2d5992b..0000000 --- a/test/api.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Definitions - */ - -const apis: any = {}; - -// common -import CommonJavaProps from '../src/java-props'; -import * as CommonJavaPropsW from '../src/java-props'; - -const CommonJavaPropsR = require('../src/java-props'); - -apis.common = { - modules: [CommonJavaProps, CommonJavaPropsW, CommonJavaPropsR], - methods: ['parse', 'stringify'], -}; - -// node -import NodeJavaProps from '../src/node-java-props'; -import * as NodeJavaPropsW from '../src/node-java-props'; - -const NodeJavaPropsR = require('../src/node-java-props'); - -apis.node = { - parent: apis.common, - modules: [NodeJavaProps, NodeJavaPropsW, NodeJavaPropsR], - methods: ['parseFile'], -}; - -/* - * Tests - */ - -const testExport = (method: string, modules: any[], _first?: any) => { - let first = _first; - it('export ' + method, () => { - for (const module of modules) { - expect(module[method]).toBeDefined(); - if (first !== undefined) { - expect(module[method]).toBe(first); - } else { - first = module[method]; - } - } - }); -}; - -for (const apiName in apis) { - if (!apis.hasOwnProperty(apiName)) { - continue; - } - const api = apis[apiName]; - - describe(apiName + ' api', () => { - if (api.parent) { - for (const method of api.parent.methods) { - testExport(method, api.modules, api.parent.modules[0][method]); - } - } - for (const method of api.methods) { - testExport(method, api.modules); - } - }); -} diff --git a/test/cases/parse-cases.mts b/test/cases/parse-cases.mts new file mode 100644 index 0000000..d3f2498 --- /dev/null +++ b/test/cases/parse-cases.mts @@ -0,0 +1,202 @@ +// Edge cases for parse(). Java results: parse-expected.json (`pnpm run reference:gen`), ours: parse-snapshot.json. +// Inputs are JS strings: '\\' is a single backslash in the .properties content. + +export interface ParseCase { + name: string; + input: string; +} + +const cases: ParseCase[] = []; +const add = (group: string, entries: Record) => { + for (const [name, input] of Object.entries(entries)) cases.push({name: `${group}: ${name}`, input}); +}; + +add('empty', { + 'empty string': '', + 'only LF': '\n', + 'only CR': '\r', + 'only CRLF': '\r\n', + 'only whitespace': ' \t\f ', + 'whitespace lines': ' \n\t\n\f\r\n', +}); + +add('line terminators', { + LF: 'a=1\nb=2\n', + CR: 'a=1\rb=2\r', + CRLF: 'a=1\r\nb=2\r\n', + LFCR: 'a=1\n\rb=2\n\r', + mixed: 'a=1\nb=2\rc=3\r\nd=4', + 'no final terminator': 'a=1\nb=2', + 'blank lines between': 'a=1\n\n\r\n\r\rb=2', + 'U+2028 and U+2029 are not terminators': 'a=1\u2028b=2\u2029c=3\n', + 'NEL (U+0085) is not a terminator': 'a=1\u0085b=2', + 'vertical tab is not whitespace': '\u000Ba=1', +}); + +add('separators', { + equals: 'key=value', + colon: 'key:value', + space: 'key value', + tab: 'key\tvalue', + 'form feed': 'key\fvalue', + 'spaces around equals': 'key = value', + 'spaces around colon': 'key \t: \fvalue', + 'equals then colon': 'key=:value', + 'colon then equals': 'key:=value', + 'two equals': 'key==value', + 'space then two equals': 'key = = value', + 'space then colon then space': 'key : value', + 'whitespace only separator, then equals in value': 'key value=x', + 'no separator (key only)': 'key', + 'key only with trailing spaces': 'key ', + 'key then separator, no value': 'key=', + 'key then separator and spaces, no value': 'key = ', + 'empty key (equals)': '=value', + 'empty key (colon)': ':value', + 'empty key, empty value': '=', + 'leading whitespace before empty key': ' =value', + 'escaped separator in key': 'a\\=b=c', + 'escaped colon in key': 'a\\:b:c', + 'escaped space in key': 'a\\ b c', + 'escaped tab in key': 'a\\\tb c', + 'double backslash before separator': 'a\\\\=b', + 'triple backslash before separator': 'a\\\\\\=b=c', + 'separator chars in value': 'k=a=b:c d', + 'value with leading escaped space': 'k=\\ v', + 'value trailing whitespace kept': 'k=v \t\f', +}); + +add('comments', { + hash: '# comment\nk=v', + bang: '! comment\nk=v', + 'indented hash': ' # comment\nk=v', + 'indented bang with tab': '\t! comment\nk=v', + 'comment at EOF without terminator': 'k=v\n# comment', + 'hash inside key is not a comment': 'a#b=c', + 'hash inside value': 'k=v # not a comment', + 'escaped hash key': '\\#k=v', + 'comment ending with backslash does not continue': '# comment \\\nk=v', + 'comment ending with two backslashes': '# comment \\\\\nk=v', + 'comment ending with backslash then CRLF': '# comment \\\r\nk=v', + 'comment ending with backslash at EOF': 'k=v\n# comment \\', + 'only comments': '#a\n!b\n # c', + 'comment with CR terminator': '# c\rk=v', +}); + +add('continuation', { + simple: 'k=a\\\nb', + 'leading whitespace of next line skipped': 'k=a\\\n \t\fb', + CRLF: 'k=a\\\r\nb', + CR: 'k=a\\\rb', + 'LFCR is two terminators': 'k=a\\\n\rb=c', + multiple: 'k=a\\\nb\\\nc', + 'in key': 'ke\\\ny=v', + 'in separator area': 'k\\\n = v', + 'escaped backslash at EOL is not a continuation': 'k=a\\\\\nb=c', + 'three backslashes at EOL': 'k=a\\\\\\\nb', + 'to an empty line': 'k=a\\\n\nb=c', + 'to a whitespace-only line': 'k=a\\\n \nb=c', + 'to a comment-looking line': 'k=a\\\n# not a comment', + 'to a bang-looking line': 'k=a\\\n!x', + 'at EOF': 'k=v\\', + 'at EOF after LF': 'k=v\\\n', + 'twice at EOF': 'k=v\\\n\\\n', + 'continued line starting with escaped space': 'k=a\\\n\\ b', + 'CR then LF on continued line start': 'k=a\\\r\n\r\nb=c', +}); + +add('lone continuation backslash', { + alone: '\\', + 'alone with LF': '\\\n', + 'alone with CRLF': '\\\r\n', + 'after property': 'a=1\n\\', + 'after property, with LF': 'a=1\n\\\n', + indented: ' \\', + 'followed by whitespace line': '\\\n ', + 'followed by empty line': '\\\n\n', + 'followed by property': '\\\nk=v', +}); + +// Comment or key? Changed by JDK-8224202 (see LOAD_ERAS in test/support/load-result.mts). +add('empty continuation then comment char', { + hash: '\\\n#foo=bar\nbaz=1', + 'bang with indentation': ' \\\n!x=1', + 'hash after CRLF': '\\\r\n#c\r\nk=v', + 'hash with leading whitespace on next line': '\\\n #c\nk=v', + 'double continuation then hash': '\\\n\\\n#c\nk=v', +}); + +// JDK 8 keeps state from comment lines (fixed by JDK-8224202), java-props counts backslashes differently. +add('comment ending with backslash, then backslashes', { + 'comment ending with backslash, then backslash-only line': '#comment \\\n\\\\', + 'bang comment, CRLF, lone backslash': '!\\\r\n\\', + 'comment backslash, then key with trailing escaped backslash': '#\\\nk=\\\\', + 'comment with 3 backslashes, then backslashes': '#\\\\\\\n\\\\\\\\', +}); + +// JDK 8 reads stale chars of its reused line buffer (partly fixed by JDK-8224202, then by JDK-8224240). +add('stale line buffer', { + 'lone backslash decode after comment': '#\\v\\\r\n\t\\', + 'truncated unicode after longer line': 'abcdef12\na=\\u00', + 'truncated unicode after longer non-hex line': 'abcdef1z\na=\\u00', + 'truncated unicode in key after longer line': 'xxxxxx0041\n\\u00', +}); + +add('escapes', { + 'standard escapes': 'k=\\t\\n\\r\\f', + 'unknown escapes drop the backslash': 'k=\\a\\b\\c\\d\\e\\g\\z\\0\\9', + 'escaped backslash': 'k=\\\\', + 'escaped quote chars': 'k=\\"\\\'', + 'escapes in key': '\\t\\n\\r\\f\\a=v', + 'escaped separators in value': 'k=\\=\\:\\ \\#\\!', + 'backslash before non-ASCII': 'k=\\é\\❤', + 'backslash before U+2028': 'k=\\\u2028', + 'backslash before NUL': 'k=\\\u0000', +}); + +add('unicode escapes', { + 'lowercase hex': 'k=\\u00e9', + 'uppercase hex': 'k=\\u00E9', + 'mixed case hex': 'k=\\uAbCd', + 'in key': '\\u0041=v', + NUL: 'k=\\u0000', + 'surrogate pair': 'k=\\uD83D\\uDC51', + 'lone high surrogate': 'k=\\uD83D', + 'lone low surrogate': 'k=\\uDC51', + 'escaped separator via unicode is not a separator': 'k\\u003Dx=v', + 'unicode escaped backslash': 'k=\\u005Cn', + 'followed by more chars': 'k=\\u00410', + 'uppercase U is not an escape': 'k=\\U0041', + 'escaped u': 'k=\\\\u0041', + 'malformed: non-hex': 'k=\\u00g1', + 'malformed: too short at end': 'k=\\u12', + 'malformed: empty at end': 'k=\\u', + 'malformed: in key': '\\uZZZZ=v', + 'malformed: short then separator': 'k\\u12=v', + 'malformed: spans continuation': 'k=\\u00\\\n41', + 'malformed: plus sign': 'k=\\u+123', + 'malformed: fullwidth digits': 'k=\\u\uFF10\uFF10\uFF14\uFF11', +}); + +add('raw characters', { + 'non-ASCII key and value': 'clé=valeur é ❤', + 'emoji (surrogate pair)': 'k=👑', + 'lone surrogate': 'k=\uD83D', + 'NUL char': 'k=a\u0000b', + 'control chars': 'k=\u0001\u001F\u007F', + 'BOM is not stripped': '\uFEFFk=v', + 'BOM on second line': 'a=1\n\uFEFFk=v', + 'non-breaking space is not whitespace': '\u00A0k=v', + 'fullwidth equals is not a separator': 'k\uFF1Dv', +}); + +add('keys', { + 'duplicate keys: last wins': 'k=1\nk=2\nk=3', + 'duplicate via escapes': 'k=1\n\\k=2', + 'proto-like keys': '__proto__=a\nconstructor=b\nhasOwnProperty=c\ntoString=d', + 'numeric keys': '2=b\n10=c\n1=a\n-1=d\n01=e', + 'long key and value': `${'k'.repeat(300)}=${'v'.repeat(10000)}`, + 'long line before short lines': `${'x'.repeat(200)}=y\na=\\`, +}); + +export default cases; diff --git a/test/cases/parse-expected.json b/test/cases/parse-expected.json new file mode 100644 index 0000000..fb955d8 --- /dev/null +++ b/test/cases/parse-expected.json @@ -0,0 +1,2619 @@ +{ + "empty: empty string": { + "input": "", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "empty: only LF": { + "input": "\n", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "empty: only CR": { + "input": "\r", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "empty: only CRLF": { + "input": "\r\n", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "empty: only whitespace": { + "input": " \t\f ", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "empty: whitespace lines": { + "input": " \n\t\n\f\r\n", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "line terminators: LF": { + "input": "a=1\nb=2\n", + "jdk8": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2" + } + } + }, + "line terminators: CR": { + "input": "a=1\rb=2\r", + "jdk8": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2" + } + } + }, + "line terminators: CRLF": { + "input": "a=1\r\nb=2\r\n", + "jdk8": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2" + } + } + }, + "line terminators: LFCR": { + "input": "a=1\n\rb=2\n\r", + "jdk8": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2" + } + } + }, + "line terminators: mixed": { + "input": "a=1\nb=2\rc=3\r\nd=4", + "jdk8": { + "props": { + "a": "1", + "b": "2", + "c": "3", + "d": "4" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2", + "c": "3", + "d": "4" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2", + "c": "3", + "d": "4" + } + } + }, + "line terminators: no final terminator": { + "input": "a=1\nb=2", + "jdk8": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2" + } + } + }, + "line terminators: blank lines between": { + "input": "a=1\n\n\r\n\r\rb=2", + "jdk8": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk11": { + "props": { + "a": "1", + "b": "2" + } + }, + "jdk13": { + "props": { + "a": "1", + "b": "2" + } + } + }, + "line terminators: U+2028 and U+2029 are not terminators": { + "input": "a=1
b=2
c=3\n", + "jdk8": { + "props": { + "a": "1
b=2
c=3" + } + }, + "jdk11": { + "props": { + "a": "1
b=2
c=3" + } + }, + "jdk13": { + "props": { + "a": "1
b=2
c=3" + } + } + }, + "line terminators: NEL (U+0085) is not a terminator": { + "input": "a=1…b=2", + "jdk8": { + "props": { + "a": "1…b=2" + } + }, + "jdk11": { + "props": { + "a": "1…b=2" + } + }, + "jdk13": { + "props": { + "a": "1…b=2" + } + } + }, + "line terminators: vertical tab is not whitespace": { + "input": "\u000ba=1", + "jdk8": { + "props": { + "\u000ba": "1" + } + }, + "jdk11": { + "props": { + "\u000ba": "1" + } + }, + "jdk13": { + "props": { + "\u000ba": "1" + } + } + }, + "separators: equals": { + "input": "key=value", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: colon": { + "input": "key:value", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: space": { + "input": "key value", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: tab": { + "input": "key\tvalue", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: form feed": { + "input": "key\fvalue", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: spaces around equals": { + "input": "key = value", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: spaces around colon": { + "input": "key \t: \fvalue", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: equals then colon": { + "input": "key=:value", + "jdk8": { + "props": { + "key": ":value" + } + }, + "jdk11": { + "props": { + "key": ":value" + } + }, + "jdk13": { + "props": { + "key": ":value" + } + } + }, + "separators: colon then equals": { + "input": "key:=value", + "jdk8": { + "props": { + "key": "=value" + } + }, + "jdk11": { + "props": { + "key": "=value" + } + }, + "jdk13": { + "props": { + "key": "=value" + } + } + }, + "separators: two equals": { + "input": "key==value", + "jdk8": { + "props": { + "key": "=value" + } + }, + "jdk11": { + "props": { + "key": "=value" + } + }, + "jdk13": { + "props": { + "key": "=value" + } + } + }, + "separators: space then two equals": { + "input": "key = = value", + "jdk8": { + "props": { + "key": "= value" + } + }, + "jdk11": { + "props": { + "key": "= value" + } + }, + "jdk13": { + "props": { + "key": "= value" + } + } + }, + "separators: space then colon then space": { + "input": "key : value", + "jdk8": { + "props": { + "key": "value" + } + }, + "jdk11": { + "props": { + "key": "value" + } + }, + "jdk13": { + "props": { + "key": "value" + } + } + }, + "separators: whitespace only separator, then equals in value": { + "input": "key value=x", + "jdk8": { + "props": { + "key": "value=x" + } + }, + "jdk11": { + "props": { + "key": "value=x" + } + }, + "jdk13": { + "props": { + "key": "value=x" + } + } + }, + "separators: no separator (key only)": { + "input": "key", + "jdk8": { + "props": { + "key": "" + } + }, + "jdk11": { + "props": { + "key": "" + } + }, + "jdk13": { + "props": { + "key": "" + } + } + }, + "separators: key only with trailing spaces": { + "input": "key ", + "jdk8": { + "props": { + "key": "" + } + }, + "jdk11": { + "props": { + "key": "" + } + }, + "jdk13": { + "props": { + "key": "" + } + } + }, + "separators: key then separator, no value": { + "input": "key=", + "jdk8": { + "props": { + "key": "" + } + }, + "jdk11": { + "props": { + "key": "" + } + }, + "jdk13": { + "props": { + "key": "" + } + } + }, + "separators: key then separator and spaces, no value": { + "input": "key = ", + "jdk8": { + "props": { + "key": "" + } + }, + "jdk11": { + "props": { + "key": "" + } + }, + "jdk13": { + "props": { + "key": "" + } + } + }, + "separators: empty key (equals)": { + "input": "=value", + "jdk8": { + "props": { + "": "value" + } + }, + "jdk11": { + "props": { + "": "value" + } + }, + "jdk13": { + "props": { + "": "value" + } + } + }, + "separators: empty key (colon)": { + "input": ":value", + "jdk8": { + "props": { + "": "value" + } + }, + "jdk11": { + "props": { + "": "value" + } + }, + "jdk13": { + "props": { + "": "value" + } + } + }, + "separators: empty key, empty value": { + "input": "=", + "jdk8": { + "props": { + "": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "separators: leading whitespace before empty key": { + "input": " =value", + "jdk8": { + "props": { + "": "value" + } + }, + "jdk11": { + "props": { + "": "value" + } + }, + "jdk13": { + "props": { + "": "value" + } + } + }, + "separators: escaped separator in key": { + "input": "a\\=b=c", + "jdk8": { + "props": { + "a=b": "c" + } + }, + "jdk11": { + "props": { + "a=b": "c" + } + }, + "jdk13": { + "props": { + "a=b": "c" + } + } + }, + "separators: escaped colon in key": { + "input": "a\\:b:c", + "jdk8": { + "props": { + "a:b": "c" + } + }, + "jdk11": { + "props": { + "a:b": "c" + } + }, + "jdk13": { + "props": { + "a:b": "c" + } + } + }, + "separators: escaped space in key": { + "input": "a\\ b c", + "jdk8": { + "props": { + "a b": "c" + } + }, + "jdk11": { + "props": { + "a b": "c" + } + }, + "jdk13": { + "props": { + "a b": "c" + } + } + }, + "separators: escaped tab in key": { + "input": "a\\\tb c", + "jdk8": { + "props": { + "a\tb": "c" + } + }, + "jdk11": { + "props": { + "a\tb": "c" + } + }, + "jdk13": { + "props": { + "a\tb": "c" + } + } + }, + "separators: double backslash before separator": { + "input": "a\\\\=b", + "jdk8": { + "props": { + "a\\": "b" + } + }, + "jdk11": { + "props": { + "a\\": "b" + } + }, + "jdk13": { + "props": { + "a\\": "b" + } + } + }, + "separators: triple backslash before separator": { + "input": "a\\\\\\=b=c", + "jdk8": { + "props": { + "a\\=b": "c" + } + }, + "jdk11": { + "props": { + "a\\=b": "c" + } + }, + "jdk13": { + "props": { + "a\\=b": "c" + } + } + }, + "separators: separator chars in value": { + "input": "k=a=b:c d", + "jdk8": { + "props": { + "k": "a=b:c d" + } + }, + "jdk11": { + "props": { + "k": "a=b:c d" + } + }, + "jdk13": { + "props": { + "k": "a=b:c d" + } + } + }, + "separators: value with leading escaped space": { + "input": "k=\\ v", + "jdk8": { + "props": { + "k": " v" + } + }, + "jdk11": { + "props": { + "k": " v" + } + }, + "jdk13": { + "props": { + "k": " v" + } + } + }, + "separators: value trailing whitespace kept": { + "input": "k=v \t\f", + "jdk8": { + "props": { + "k": "v \t\f" + } + }, + "jdk11": { + "props": { + "k": "v \t\f" + } + }, + "jdk13": { + "props": { + "k": "v \t\f" + } + } + }, + "comments: hash": { + "input": "# comment\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: bang": { + "input": "! comment\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: indented hash": { + "input": " # comment\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: indented bang with tab": { + "input": "\t! comment\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: comment at EOF without terminator": { + "input": "k=v\n# comment", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: hash inside key is not a comment": { + "input": "a#b=c", + "jdk8": { + "props": { + "a#b": "c" + } + }, + "jdk11": { + "props": { + "a#b": "c" + } + }, + "jdk13": { + "props": { + "a#b": "c" + } + } + }, + "comments: hash inside value": { + "input": "k=v # not a comment", + "jdk8": { + "props": { + "k": "v # not a comment" + } + }, + "jdk11": { + "props": { + "k": "v # not a comment" + } + }, + "jdk13": { + "props": { + "k": "v # not a comment" + } + } + }, + "comments: escaped hash key": { + "input": "\\#k=v", + "jdk8": { + "props": { + "#k": "v" + } + }, + "jdk11": { + "props": { + "#k": "v" + } + }, + "jdk13": { + "props": { + "#k": "v" + } + } + }, + "comments: comment ending with backslash does not continue": { + "input": "# comment \\\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: comment ending with two backslashes": { + "input": "# comment \\\\\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: comment ending with backslash then CRLF": { + "input": "# comment \\\r\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: comment ending with backslash at EOF": { + "input": "k=v\n# comment \\", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comments: only comments": { + "input": "#a\n!b\n # c", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "comments: comment with CR terminator": { + "input": "# c\rk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "continuation: simple": { + "input": "k=a\\\nb", + "jdk8": { + "props": { + "k": "ab" + } + }, + "jdk11": { + "props": { + "k": "ab" + } + }, + "jdk13": { + "props": { + "k": "ab" + } + } + }, + "continuation: leading whitespace of next line skipped": { + "input": "k=a\\\n \t\fb", + "jdk8": { + "props": { + "k": "ab" + } + }, + "jdk11": { + "props": { + "k": "ab" + } + }, + "jdk13": { + "props": { + "k": "ab" + } + } + }, + "continuation: CRLF": { + "input": "k=a\\\r\nb", + "jdk8": { + "props": { + "k": "ab" + } + }, + "jdk11": { + "props": { + "k": "ab" + } + }, + "jdk13": { + "props": { + "k": "ab" + } + } + }, + "continuation: CR": { + "input": "k=a\\\rb", + "jdk8": { + "props": { + "k": "ab" + } + }, + "jdk11": { + "props": { + "k": "ab" + } + }, + "jdk13": { + "props": { + "k": "ab" + } + } + }, + "continuation: LFCR is two terminators": { + "input": "k=a\\\n\rb=c", + "jdk8": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk11": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk13": { + "props": { + "b": "c", + "k": "a" + } + } + }, + "continuation: multiple": { + "input": "k=a\\\nb\\\nc", + "jdk8": { + "props": { + "k": "abc" + } + }, + "jdk11": { + "props": { + "k": "abc" + } + }, + "jdk13": { + "props": { + "k": "abc" + } + } + }, + "continuation: in key": { + "input": "ke\\\ny=v", + "jdk8": { + "props": { + "key": "v" + } + }, + "jdk11": { + "props": { + "key": "v" + } + }, + "jdk13": { + "props": { + "key": "v" + } + } + }, + "continuation: in separator area": { + "input": "k\\\n = v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "continuation: escaped backslash at EOL is not a continuation": { + "input": "k=a\\\\\nb=c", + "jdk8": { + "props": { + "b": "c", + "k": "a\\" + } + }, + "jdk11": { + "props": { + "b": "c", + "k": "a\\" + } + }, + "jdk13": { + "props": { + "b": "c", + "k": "a\\" + } + } + }, + "continuation: three backslashes at EOL": { + "input": "k=a\\\\\\\nb", + "jdk8": { + "props": { + "k": "a\\b" + } + }, + "jdk11": { + "props": { + "k": "a\\b" + } + }, + "jdk13": { + "props": { + "k": "a\\b" + } + } + }, + "continuation: to an empty line": { + "input": "k=a\\\n\nb=c", + "jdk8": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk11": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk13": { + "props": { + "b": "c", + "k": "a" + } + } + }, + "continuation: to a whitespace-only line": { + "input": "k=a\\\n \nb=c", + "jdk8": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk11": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk13": { + "props": { + "b": "c", + "k": "a" + } + } + }, + "continuation: to a comment-looking line": { + "input": "k=a\\\n# not a comment", + "jdk8": { + "props": { + "k": "a# not a comment" + } + }, + "jdk11": { + "props": { + "k": "a# not a comment" + } + }, + "jdk13": { + "props": { + "k": "a# not a comment" + } + } + }, + "continuation: to a bang-looking line": { + "input": "k=a\\\n!x", + "jdk8": { + "props": { + "k": "a!x" + } + }, + "jdk11": { + "props": { + "k": "a!x" + } + }, + "jdk13": { + "props": { + "k": "a!x" + } + } + }, + "continuation: at EOF": { + "input": "k=v\\", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "continuation: at EOF after LF": { + "input": "k=v\\\n", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "continuation: twice at EOF": { + "input": "k=v\\\n\\\n", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "continuation: continued line starting with escaped space": { + "input": "k=a\\\n\\ b", + "jdk8": { + "props": { + "k": "a b" + } + }, + "jdk11": { + "props": { + "k": "a b" + } + }, + "jdk13": { + "props": { + "k": "a b" + } + } + }, + "continuation: CR then LF on continued line start": { + "input": "k=a\\\r\n\r\nb=c", + "jdk8": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk11": { + "props": { + "b": "c", + "k": "a" + } + }, + "jdk13": { + "props": { + "b": "c", + "k": "a" + } + } + }, + "lone continuation backslash: alone": { + "input": "\\", + "jdk8": { + "props": { + "": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "lone continuation backslash: alone with LF": { + "input": "\\\n", + "jdk8": { + "props": { + "": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "lone continuation backslash: alone with CRLF": { + "input": "\\\r\n", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "lone continuation backslash: after property": { + "input": "a=1\n\\", + "jdk8": { + "props": { + "": "", + "a": "1" + } + }, + "jdk11": { + "props": { + "": "", + "a": "1" + } + }, + "jdk13": { + "props": { + "": "", + "a": "1" + } + } + }, + "lone continuation backslash: after property, with LF": { + "input": "a=1\n\\\n", + "jdk8": { + "props": { + "": "", + "a": "1" + } + }, + "jdk11": { + "props": { + "": "", + "a": "1" + } + }, + "jdk13": { + "props": { + "": "", + "a": "1" + } + } + }, + "lone continuation backslash: indented": { + "input": " \\", + "jdk8": { + "props": { + "": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "lone continuation backslash: followed by whitespace line": { + "input": "\\\n ", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "lone continuation backslash: followed by empty line": { + "input": "\\\n\n", + "jdk8": { + "props": {} + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "lone continuation backslash: followed by property": { + "input": "\\\nk=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "empty continuation then comment char: hash": { + "input": "\\\n#foo=bar\nbaz=1", + "jdk8": { + "props": { + "#foo": "bar", + "baz": "1" + } + }, + "jdk11": { + "props": { + "baz": "1" + } + }, + "jdk13": { + "props": { + "baz": "1" + } + } + }, + "empty continuation then comment char: bang with indentation": { + "input": " \\\n!x=1", + "jdk8": { + "props": { + "!x": "1" + } + }, + "jdk11": { + "props": {} + }, + "jdk13": { + "props": {} + } + }, + "empty continuation then comment char: hash after CRLF": { + "input": "\\\r\n#c\r\nk=v", + "jdk8": { + "props": { + "#c": "", + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "empty continuation then comment char: hash with leading whitespace on next line": { + "input": "\\\n #c\nk=v", + "jdk8": { + "props": { + "#c": "", + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "empty continuation then comment char: double continuation then hash": { + "input": "\\\n\\\n#c\nk=v", + "jdk8": { + "props": { + "#c": "", + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "comment ending with backslash, then backslashes: comment ending with backslash, then backslash-only line": { + "input": "#comment \\\n\\\\", + "jdk8": { + "props": { + "\\": "" + } + }, + "jdk11": { + "props": { + "\\": "" + } + }, + "jdk13": { + "props": { + "\\": "" + } + } + }, + "comment ending with backslash, then backslashes: bang comment, CRLF, lone backslash": { + "input": "!\\\r\n\\", + "jdk8": { + "props": { + "\u0000": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "comment ending with backslash, then backslashes: comment backslash, then key with trailing escaped backslash": { + "input": "#\\\nk=\\\\", + "jdk8": { + "props": { + "k": "\\" + } + }, + "jdk11": { + "props": { + "k": "\\" + } + }, + "jdk13": { + "props": { + "k": "\\" + } + } + }, + "comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes": { + "input": "#\\\\\\\n\\\\\\\\", + "jdk8": { + "props": { + "\\\\": "" + } + }, + "jdk11": { + "props": { + "\\\\": "" + } + }, + "jdk13": { + "props": { + "\\\\": "" + } + } + }, + "stale line buffer: lone backslash decode after comment": { + "input": "#\\v\\\r\n\t\\", + "jdk8": { + "props": { + "v": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "stale line buffer: truncated unicode after longer line": { + "input": "abcdef12\na=\\u00", + "jdk8": { + "props": { + "a": "\u0012", + "abcdef12": "" + } + }, + "jdk11": { + "props": { + "a": "\u0012", + "abcdef12": "" + } + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "stale line buffer: truncated unicode after longer non-hex line": { + "input": "abcdef1z\na=\\u00", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "stale line buffer: truncated unicode in key after longer line": { + "input": "xxxxxx0041\n\\u00", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "escapes: standard escapes": { + "input": "k=\\t\\n\\r\\f", + "jdk8": { + "props": { + "k": "\t\n\r\f" + } + }, + "jdk11": { + "props": { + "k": "\t\n\r\f" + } + }, + "jdk13": { + "props": { + "k": "\t\n\r\f" + } + } + }, + "escapes: unknown escapes drop the backslash": { + "input": "k=\\a\\b\\c\\d\\e\\g\\z\\0\\9", + "jdk8": { + "props": { + "k": "abcdegz09" + } + }, + "jdk11": { + "props": { + "k": "abcdegz09" + } + }, + "jdk13": { + "props": { + "k": "abcdegz09" + } + } + }, + "escapes: escaped backslash": { + "input": "k=\\\\", + "jdk8": { + "props": { + "k": "\\" + } + }, + "jdk11": { + "props": { + "k": "\\" + } + }, + "jdk13": { + "props": { + "k": "\\" + } + } + }, + "escapes: escaped quote chars": { + "input": "k=\\\"\\'", + "jdk8": { + "props": { + "k": "\"'" + } + }, + "jdk11": { + "props": { + "k": "\"'" + } + }, + "jdk13": { + "props": { + "k": "\"'" + } + } + }, + "escapes: escapes in key": { + "input": "\\t\\n\\r\\f\\a=v", + "jdk8": { + "props": { + "\t\n\r\fa": "v" + } + }, + "jdk11": { + "props": { + "\t\n\r\fa": "v" + } + }, + "jdk13": { + "props": { + "\t\n\r\fa": "v" + } + } + }, + "escapes: escaped separators in value": { + "input": "k=\\=\\:\\ \\#\\!", + "jdk8": { + "props": { + "k": "=: #!" + } + }, + "jdk11": { + "props": { + "k": "=: #!" + } + }, + "jdk13": { + "props": { + "k": "=: #!" + } + } + }, + "escapes: backslash before non-ASCII": { + "input": "k=\\é\\❤", + "jdk8": { + "props": { + "k": "é❤" + } + }, + "jdk11": { + "props": { + "k": "é❤" + } + }, + "jdk13": { + "props": { + "k": "é❤" + } + } + }, + "escapes: backslash before U+2028": { + "input": "k=\\
", + "jdk8": { + "props": { + "k": "
" + } + }, + "jdk11": { + "props": { + "k": "
" + } + }, + "jdk13": { + "props": { + "k": "
" + } + } + }, + "escapes: backslash before NUL": { + "input": "k=\\\u0000", + "jdk8": { + "props": { + "k": "\u0000" + } + }, + "jdk11": { + "props": { + "k": "\u0000" + } + }, + "jdk13": { + "props": { + "k": "\u0000" + } + } + }, + "unicode escapes: lowercase hex": { + "input": "k=\\u00e9", + "jdk8": { + "props": { + "k": "é" + } + }, + "jdk11": { + "props": { + "k": "é" + } + }, + "jdk13": { + "props": { + "k": "é" + } + } + }, + "unicode escapes: uppercase hex": { + "input": "k=\\u00E9", + "jdk8": { + "props": { + "k": "é" + } + }, + "jdk11": { + "props": { + "k": "é" + } + }, + "jdk13": { + "props": { + "k": "é" + } + } + }, + "unicode escapes: mixed case hex": { + "input": "k=\\uAbCd", + "jdk8": { + "props": { + "k": "ꯍ" + } + }, + "jdk11": { + "props": { + "k": "ꯍ" + } + }, + "jdk13": { + "props": { + "k": "ꯍ" + } + } + }, + "unicode escapes: in key": { + "input": "\\u0041=v", + "jdk8": { + "props": { + "A": "v" + } + }, + "jdk11": { + "props": { + "A": "v" + } + }, + "jdk13": { + "props": { + "A": "v" + } + } + }, + "unicode escapes: NUL": { + "input": "k=\\u0000", + "jdk8": { + "props": { + "k": "\u0000" + } + }, + "jdk11": { + "props": { + "k": "\u0000" + } + }, + "jdk13": { + "props": { + "k": "\u0000" + } + } + }, + "unicode escapes: surrogate pair": { + "input": "k=\\uD83D\\uDC51", + "jdk8": { + "props": { + "k": "👑" + } + }, + "jdk11": { + "props": { + "k": "👑" + } + }, + "jdk13": { + "props": { + "k": "👑" + } + } + }, + "unicode escapes: lone high surrogate": { + "input": "k=\\uD83D", + "jdk8": { + "props": { + "k": "\ud83d" + } + }, + "jdk11": { + "props": { + "k": "\ud83d" + } + }, + "jdk13": { + "props": { + "k": "\ud83d" + } + } + }, + "unicode escapes: lone low surrogate": { + "input": "k=\\uDC51", + "jdk8": { + "props": { + "k": "\udc51" + } + }, + "jdk11": { + "props": { + "k": "\udc51" + } + }, + "jdk13": { + "props": { + "k": "\udc51" + } + } + }, + "unicode escapes: escaped separator via unicode is not a separator": { + "input": "k\\u003Dx=v", + "jdk8": { + "props": { + "k=x": "v" + } + }, + "jdk11": { + "props": { + "k=x": "v" + } + }, + "jdk13": { + "props": { + "k=x": "v" + } + } + }, + "unicode escapes: unicode escaped backslash": { + "input": "k=\\u005Cn", + "jdk8": { + "props": { + "k": "\\n" + } + }, + "jdk11": { + "props": { + "k": "\\n" + } + }, + "jdk13": { + "props": { + "k": "\\n" + } + } + }, + "unicode escapes: followed by more chars": { + "input": "k=\\u00410", + "jdk8": { + "props": { + "k": "A0" + } + }, + "jdk11": { + "props": { + "k": "A0" + } + }, + "jdk13": { + "props": { + "k": "A0" + } + } + }, + "unicode escapes: uppercase U is not an escape": { + "input": "k=\\U0041", + "jdk8": { + "props": { + "k": "U0041" + } + }, + "jdk11": { + "props": { + "k": "U0041" + } + }, + "jdk13": { + "props": { + "k": "U0041" + } + } + }, + "unicode escapes: escaped u": { + "input": "k=\\\\u0041", + "jdk8": { + "props": { + "k": "\\u0041" + } + }, + "jdk11": { + "props": { + "k": "\\u0041" + } + }, + "jdk13": { + "props": { + "k": "\\u0041" + } + } + }, + "unicode escapes: malformed: non-hex": { + "input": "k=\\u00g1", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "unicode escapes: malformed: too short at end": { + "input": "k=\\u12", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "unicode escapes: malformed: empty at end": { + "input": "k=\\u", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "unicode escapes: malformed: in key": { + "input": "\\uZZZZ=v", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "unicode escapes: malformed: short then separator": { + "input": "k\\u12=v", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "unicode escapes: malformed: spans continuation": { + "input": "k=\\u00\\\n41", + "jdk8": { + "props": { + "k": "A" + } + }, + "jdk11": { + "props": { + "k": "A" + } + }, + "jdk13": { + "props": { + "k": "A" + } + } + }, + "unicode escapes: malformed: plus sign": { + "input": "k=\\u+123", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "unicode escapes: malformed: fullwidth digits": { + "input": "k=\\u0041", + "jdk8": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk11": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, + "jdk13": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + } + }, + "raw characters: non-ASCII key and value": { + "input": "clé=valeur é ❤", + "jdk8": { + "props": { + "clé": "valeur é ❤" + } + }, + "jdk11": { + "props": { + "clé": "valeur é ❤" + } + }, + "jdk13": { + "props": { + "clé": "valeur é ❤" + } + } + }, + "raw characters: emoji (surrogate pair)": { + "input": "k=👑", + "jdk8": { + "props": { + "k": "👑" + } + }, + "jdk11": { + "props": { + "k": "👑" + } + }, + "jdk13": { + "props": { + "k": "👑" + } + } + }, + "raw characters: lone surrogate": { + "input": "k=\ud83d", + "jdk8": { + "props": { + "k": "\ud83d" + } + }, + "jdk11": { + "props": { + "k": "\ud83d" + } + }, + "jdk13": { + "props": { + "k": "\ud83d" + } + } + }, + "raw characters: NUL char": { + "input": "k=a\u0000b", + "jdk8": { + "props": { + "k": "a\u0000b" + } + }, + "jdk11": { + "props": { + "k": "a\u0000b" + } + }, + "jdk13": { + "props": { + "k": "a\u0000b" + } + } + }, + "raw characters: control chars": { + "input": "k=\u0001\u001f", + "jdk8": { + "props": { + "k": "\u0001\u001f" + } + }, + "jdk11": { + "props": { + "k": "\u0001\u001f" + } + }, + "jdk13": { + "props": { + "k": "\u0001\u001f" + } + } + }, + "raw characters: BOM is not stripped": { + "input": "k=v", + "jdk8": { + "props": { + "k": "v" + } + }, + "jdk11": { + "props": { + "k": "v" + } + }, + "jdk13": { + "props": { + "k": "v" + } + } + }, + "raw characters: BOM on second line": { + "input": "a=1\nk=v", + "jdk8": { + "props": { + "a": "1", + "k": "v" + } + }, + "jdk11": { + "props": { + "a": "1", + "k": "v" + } + }, + "jdk13": { + "props": { + "a": "1", + "k": "v" + } + } + }, + "raw characters: non-breaking space is not whitespace": { + "input": " k=v", + "jdk8": { + "props": { + " k": "v" + } + }, + "jdk11": { + "props": { + " k": "v" + } + }, + "jdk13": { + "props": { + " k": "v" + } + } + }, + "raw characters: fullwidth equals is not a separator": { + "input": "k=v", + "jdk8": { + "props": { + "k=v": "" + } + }, + "jdk11": { + "props": { + "k=v": "" + } + }, + "jdk13": { + "props": { + "k=v": "" + } + } + }, + "keys: duplicate keys: last wins": { + "input": "k=1\nk=2\nk=3", + "jdk8": { + "props": { + "k": "3" + } + }, + "jdk11": { + "props": { + "k": "3" + } + }, + "jdk13": { + "props": { + "k": "3" + } + } + }, + "keys: duplicate via escapes": { + "input": "k=1\n\\k=2", + "jdk8": { + "props": { + "k": "2" + } + }, + "jdk11": { + "props": { + "k": "2" + } + }, + "jdk13": { + "props": { + "k": "2" + } + } + }, + "keys: proto-like keys": { + "input": "__proto__=a\nconstructor=b\nhasOwnProperty=c\ntoString=d", + "jdk8": { + "props": { + "__proto__": "a", + "constructor": "b", + "hasOwnProperty": "c", + "toString": "d" + } + }, + "jdk11": { + "props": { + "__proto__": "a", + "constructor": "b", + "hasOwnProperty": "c", + "toString": "d" + } + }, + "jdk13": { + "props": { + "__proto__": "a", + "constructor": "b", + "hasOwnProperty": "c", + "toString": "d" + } + } + }, + "keys: numeric keys": { + "input": "2=b\n10=c\n1=a\n-1=d\n01=e", + "jdk8": { + "props": { + "1": "a", + "2": "b", + "10": "c", + "-1": "d", + "01": "e" + } + }, + "jdk11": { + "props": { + "1": "a", + "2": "b", + "10": "c", + "-1": "d", + "01": "e" + } + }, + "jdk13": { + "props": { + "1": "a", + "2": "b", + "10": "c", + "-1": "d", + "01": "e" + } + } + }, + "keys: long key and value": { + "input": "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk=vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv", + "jdk8": { + "props": { + "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" + } + }, + "jdk11": { + "props": { + "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" + } + }, + "jdk13": { + "props": { + "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" + } + } + }, + "keys: long line before short lines": { + "input": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=y\na=\\", + "jdk8": { + "props": { + "a": "", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "y" + } + }, + "jdk11": { + "props": { + "a": "", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "y" + } + }, + "jdk13": { + "props": { + "a": "", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "y" + } + } + } +} diff --git a/test/cases/parse-snapshot.json b/test/cases/parse-snapshot.json new file mode 100644 index 0000000..0c557f1 --- /dev/null +++ b/test/cases/parse-snapshot.json @@ -0,0 +1,869 @@ +{ + "empty: empty string": { + "input": "", + "props": {} + }, + "empty: only LF": { + "input": "\n", + "props": {} + }, + "empty: only CR": { + "input": "\r", + "props": {} + }, + "empty: only CRLF": { + "input": "\r\n", + "props": {} + }, + "empty: only whitespace": { + "input": " \t\f ", + "props": {} + }, + "empty: whitespace lines": { + "input": " \n\t\n\f\r\n", + "props": {} + }, + "line terminators: LF": { + "input": "a=1\nb=2\n", + "props": { + "a": "1", + "b": "2" + } + }, + "line terminators: CR": { + "input": "a=1\rb=2\r", + "props": { + "a": "1", + "b": "2" + } + }, + "line terminators: CRLF": { + "input": "a=1\r\nb=2\r\n", + "props": { + "a": "1", + "b": "2" + } + }, + "line terminators: LFCR": { + "input": "a=1\n\rb=2\n\r", + "props": { + "a": "1", + "b": "2" + } + }, + "line terminators: mixed": { + "input": "a=1\nb=2\rc=3\r\nd=4", + "props": { + "a": "1", + "b": "2", + "c": "3", + "d": "4" + } + }, + "line terminators: no final terminator": { + "input": "a=1\nb=2", + "props": { + "a": "1", + "b": "2" + } + }, + "line terminators: blank lines between": { + "input": "a=1\n\n\r\n\r\rb=2", + "props": { + "a": "1", + "b": "2" + } + }, + "line terminators: U+2028 and U+2029 are not terminators": { + "input": "a=1
b=2
c=3\n", + "props": { + "a": "1
b=2
c=3" + } + }, + "line terminators: NEL (U+0085) is not a terminator": { + "input": "a=1…b=2", + "props": { + "a": "1…b=2" + } + }, + "line terminators: vertical tab is not whitespace": { + "input": "\u000ba=1", + "props": { + "\u000ba": "1" + } + }, + "separators: equals": { + "input": "key=value", + "props": { + "key": "value" + } + }, + "separators: colon": { + "input": "key:value", + "props": { + "key": "value" + } + }, + "separators: space": { + "input": "key value", + "props": { + "key": "value" + } + }, + "separators: tab": { + "input": "key\tvalue", + "props": { + "key": "value" + } + }, + "separators: form feed": { + "input": "key\fvalue", + "props": { + "key": "value" + } + }, + "separators: spaces around equals": { + "input": "key = value", + "props": { + "key": "value" + } + }, + "separators: spaces around colon": { + "input": "key \t: \fvalue", + "props": { + "key": "value" + } + }, + "separators: equals then colon": { + "input": "key=:value", + "props": { + "key": ":value" + } + }, + "separators: colon then equals": { + "input": "key:=value", + "props": { + "key": "=value" + } + }, + "separators: two equals": { + "input": "key==value", + "props": { + "key": "=value" + } + }, + "separators: space then two equals": { + "input": "key = = value", + "props": { + "key": "= value" + } + }, + "separators: space then colon then space": { + "input": "key : value", + "props": { + "key": "value" + } + }, + "separators: whitespace only separator, then equals in value": { + "input": "key value=x", + "props": { + "key": "value=x" + } + }, + "separators: no separator (key only)": { + "input": "key", + "props": { + "key": "" + } + }, + "separators: key only with trailing spaces": { + "input": "key ", + "props": { + "key": "" + } + }, + "separators: key then separator, no value": { + "input": "key=", + "props": { + "key": "" + } + }, + "separators: key then separator and spaces, no value": { + "input": "key = ", + "props": { + "key": "" + } + }, + "separators: empty key (equals)": { + "input": "=value", + "props": { + "": "value" + } + }, + "separators: empty key (colon)": { + "input": ":value", + "props": { + "": "value" + } + }, + "separators: empty key, empty value": { + "input": "=", + "props": { + "": "" + } + }, + "separators: leading whitespace before empty key": { + "input": " =value", + "props": { + "": "value" + } + }, + "separators: escaped separator in key": { + "input": "a\\=b=c", + "props": { + "a=b": "c" + } + }, + "separators: escaped colon in key": { + "input": "a\\:b:c", + "props": { + "a:b": "c" + } + }, + "separators: escaped space in key": { + "input": "a\\ b c", + "props": { + "a b": "c" + } + }, + "separators: escaped tab in key": { + "input": "a\\\tb c", + "props": { + "a\tb": "c" + } + }, + "separators: double backslash before separator": { + "input": "a\\\\=b", + "props": { + "a\\": "b" + } + }, + "separators: triple backslash before separator": { + "input": "a\\\\\\=b=c", + "props": { + "a\\=b": "c" + } + }, + "separators: separator chars in value": { + "input": "k=a=b:c d", + "props": { + "k": "a=b:c d" + } + }, + "separators: value with leading escaped space": { + "input": "k=\\ v", + "props": { + "k": " v" + } + }, + "separators: value trailing whitespace kept": { + "input": "k=v \t\f", + "props": { + "k": "v \t\f" + } + }, + "comments: hash": { + "input": "# comment\nk=v", + "props": { + "k": "v" + } + }, + "comments: bang": { + "input": "! comment\nk=v", + "props": { + "k": "v" + } + }, + "comments: indented hash": { + "input": " # comment\nk=v", + "props": { + "k": "v" + } + }, + "comments: indented bang with tab": { + "input": "\t! comment\nk=v", + "props": { + "k": "v" + } + }, + "comments: comment at EOF without terminator": { + "input": "k=v\n# comment", + "props": { + "k": "v" + } + }, + "comments: hash inside key is not a comment": { + "input": "a#b=c", + "props": { + "a#b": "c" + } + }, + "comments: hash inside value": { + "input": "k=v # not a comment", + "props": { + "k": "v # not a comment" + } + }, + "comments: escaped hash key": { + "input": "\\#k=v", + "props": { + "#k": "v" + } + }, + "comments: comment ending with backslash does not continue": { + "input": "# comment \\\nk=v", + "props": { + "k": "v" + } + }, + "comments: comment ending with two backslashes": { + "input": "# comment \\\\\nk=v", + "props": { + "k": "v" + } + }, + "comments: comment ending with backslash then CRLF": { + "input": "# comment \\\r\nk=v", + "props": { + "k": "v" + } + }, + "comments: comment ending with backslash at EOF": { + "input": "k=v\n# comment \\", + "props": { + "k": "v" + } + }, + "comments: only comments": { + "input": "#a\n!b\n # c", + "props": {} + }, + "comments: comment with CR terminator": { + "input": "# c\rk=v", + "props": { + "k": "v" + } + }, + "continuation: simple": { + "input": "k=a\\\nb", + "props": { + "k": "ab" + } + }, + "continuation: leading whitespace of next line skipped": { + "input": "k=a\\\n \t\fb", + "props": { + "k": "ab" + } + }, + "continuation: CRLF": { + "input": "k=a\\\r\nb", + "props": { + "k": "ab" + } + }, + "continuation: CR": { + "input": "k=a\\\rb", + "props": { + "k": "ab" + } + }, + "continuation: LFCR is two terminators": { + "input": "k=a\\\n\rb=c", + "props": { + "b": "c", + "k": "a" + } + }, + "continuation: multiple": { + "input": "k=a\\\nb\\\nc", + "props": { + "k": "abc" + } + }, + "continuation: in key": { + "input": "ke\\\ny=v", + "props": { + "key": "v" + } + }, + "continuation: in separator area": { + "input": "k\\\n = v", + "props": { + "k": "v" + } + }, + "continuation: escaped backslash at EOL is not a continuation": { + "input": "k=a\\\\\nb=c", + "props": { + "b": "c", + "k": "a\\" + } + }, + "continuation: three backslashes at EOL": { + "input": "k=a\\\\\\\nb", + "props": { + "k": "a\\b" + } + }, + "continuation: to an empty line": { + "input": "k=a\\\n\nb=c", + "props": { + "b": "c", + "k": "a" + } + }, + "continuation: to a whitespace-only line": { + "input": "k=a\\\n \nb=c", + "props": { + "b": "c", + "k": "a" + } + }, + "continuation: to a comment-looking line": { + "input": "k=a\\\n# not a comment", + "props": { + "k": "a# not a comment" + } + }, + "continuation: to a bang-looking line": { + "input": "k=a\\\n!x", + "props": { + "k": "a!x" + } + }, + "continuation: at EOF": { + "input": "k=v\\", + "props": { + "k": "v" + } + }, + "continuation: at EOF after LF": { + "input": "k=v\\\n", + "props": { + "k": "v" + } + }, + "continuation: twice at EOF": { + "input": "k=v\\\n\\\n", + "props": { + "k": "v" + } + }, + "continuation: continued line starting with escaped space": { + "input": "k=a\\\n\\ b", + "props": { + "k": "a b" + } + }, + "continuation: CR then LF on continued line start": { + "input": "k=a\\\r\n\r\nb=c", + "props": { + "b": "c", + "k": "a" + } + }, + "lone continuation backslash: alone": { + "input": "\\", + "props": {} + }, + "lone continuation backslash: alone with LF": { + "input": "\\\n", + "props": {} + }, + "lone continuation backslash: alone with CRLF": { + "input": "\\\r\n", + "props": {} + }, + "lone continuation backslash: after property": { + "input": "a=1\n\\", + "props": { + "a": "1" + } + }, + "lone continuation backslash: after property, with LF": { + "input": "a=1\n\\\n", + "props": { + "a": "1" + } + }, + "lone continuation backslash: indented": { + "input": " \\", + "props": {} + }, + "lone continuation backslash: followed by whitespace line": { + "input": "\\\n ", + "props": {} + }, + "lone continuation backslash: followed by empty line": { + "input": "\\\n\n", + "props": {} + }, + "lone continuation backslash: followed by property": { + "input": "\\\nk=v", + "props": { + "k": "v" + } + }, + "empty continuation then comment char: hash": { + "input": "\\\n#foo=bar\nbaz=1", + "props": { + "#foo": "bar", + "baz": "1" + } + }, + "empty continuation then comment char: bang with indentation": { + "input": " \\\n!x=1", + "props": { + "!x": "1" + } + }, + "empty continuation then comment char: hash after CRLF": { + "input": "\\\r\n#c\r\nk=v", + "props": { + "#c": "", + "k": "v" + } + }, + "empty continuation then comment char: hash with leading whitespace on next line": { + "input": "\\\n #c\nk=v", + "props": { + "#c": "", + "k": "v" + } + }, + "empty continuation then comment char: double continuation then hash": { + "input": "\\\n\\\n#c\nk=v", + "props": { + "#c": "", + "k": "v" + } + }, + "comment ending with backslash, then backslashes: comment ending with backslash, then backslash-only line": { + "input": "#comment \\\n\\\\", + "props": { + "": "" + } + }, + "comment ending with backslash, then backslashes: bang comment, CRLF, lone backslash": { + "input": "!\\\r\n\\", + "props": { + "": "" + } + }, + "comment ending with backslash, then backslashes: comment backslash, then key with trailing escaped backslash": { + "input": "#\\\nk=\\\\", + "props": { + "k": "\\" + } + }, + "comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes": { + "input": "#\\\\\\\n\\\\\\\\", + "props": { + "\\": "" + } + }, + "stale line buffer: lone backslash decode after comment": { + "input": "#\\v\\\r\n\t\\", + "props": { + "": "" + } + }, + "stale line buffer: truncated unicode after longer line": { + "input": "abcdef12\na=\\u00", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "stale line buffer: truncated unicode after longer non-hex line": { + "input": "abcdef1z\na=\\u00", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "stale line buffer: truncated unicode in key after longer line": { + "input": "xxxxxx0041\n\\u00", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "escapes: standard escapes": { + "input": "k=\\t\\n\\r\\f", + "props": { + "k": "\t\n\r\f" + } + }, + "escapes: unknown escapes drop the backslash": { + "input": "k=\\a\\b\\c\\d\\e\\g\\z\\0\\9", + "props": { + "k": "abcdegz09" + } + }, + "escapes: escaped backslash": { + "input": "k=\\\\", + "props": { + "k": "\\" + } + }, + "escapes: escaped quote chars": { + "input": "k=\\\"\\'", + "props": { + "k": "\"'" + } + }, + "escapes: escapes in key": { + "input": "\\t\\n\\r\\f\\a=v", + "props": { + "\t\n\r\fa": "v" + } + }, + "escapes: escaped separators in value": { + "input": "k=\\=\\:\\ \\#\\!", + "props": { + "k": "=: #!" + } + }, + "escapes: backslash before non-ASCII": { + "input": "k=\\é\\❤", + "props": { + "k": "é❤" + } + }, + "escapes: backslash before U+2028": { + "input": "k=\\
", + "props": { + "k": "
" + } + }, + "escapes: backslash before NUL": { + "input": "k=\\\u0000", + "props": { + "k": "\u0000" + } + }, + "unicode escapes: lowercase hex": { + "input": "k=\\u00e9", + "props": { + "k": "é" + } + }, + "unicode escapes: uppercase hex": { + "input": "k=\\u00E9", + "props": { + "k": "é" + } + }, + "unicode escapes: mixed case hex": { + "input": "k=\\uAbCd", + "props": { + "k": "ꯍ" + } + }, + "unicode escapes: in key": { + "input": "\\u0041=v", + "props": { + "A": "v" + } + }, + "unicode escapes: NUL": { + "input": "k=\\u0000", + "props": { + "k": "\u0000" + } + }, + "unicode escapes: surrogate pair": { + "input": "k=\\uD83D\\uDC51", + "props": { + "k": "👑" + } + }, + "unicode escapes: lone high surrogate": { + "input": "k=\\uD83D", + "props": { + "k": "\ud83d" + } + }, + "unicode escapes: lone low surrogate": { + "input": "k=\\uDC51", + "props": { + "k": "\udc51" + } + }, + "unicode escapes: escaped separator via unicode is not a separator": { + "input": "k\\u003Dx=v", + "props": { + "k=x": "v" + } + }, + "unicode escapes: unicode escaped backslash": { + "input": "k=\\u005Cn", + "props": { + "k": "\\n" + } + }, + "unicode escapes: followed by more chars": { + "input": "k=\\u00410", + "props": { + "k": "A0" + } + }, + "unicode escapes: uppercase U is not an escape": { + "input": "k=\\U0041", + "props": { + "k": "U0041" + } + }, + "unicode escapes: escaped u": { + "input": "k=\\\\u0041", + "props": { + "k": "\\u0041" + } + }, + "unicode escapes: malformed: non-hex": { + "input": "k=\\u00g1", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "unicode escapes: malformed: too short at end": { + "input": "k=\\u12", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "unicode escapes: malformed: empty at end": { + "input": "k=\\u", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "unicode escapes: malformed: in key": { + "input": "\\uZZZZ=v", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "unicode escapes: malformed: short then separator": { + "input": "k\\u12=v", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "unicode escapes: malformed: spans continuation": { + "input": "k=\\u00\\\n41", + "props": { + "k": "A" + } + }, + "unicode escapes: malformed: plus sign": { + "input": "k=\\u+123", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "unicode escapes: malformed: fullwidth digits": { + "input": "k=\\u0041", + "throws": "Error", + "message": "Malformed \\uxxxx encoding." + }, + "raw characters: non-ASCII key and value": { + "input": "clé=valeur é ❤", + "props": { + "clé": "valeur é ❤" + } + }, + "raw characters: emoji (surrogate pair)": { + "input": "k=👑", + "props": { + "k": "👑" + } + }, + "raw characters: lone surrogate": { + "input": "k=\ud83d", + "props": { + "k": "\ud83d" + } + }, + "raw characters: NUL char": { + "input": "k=a\u0000b", + "props": { + "k": "a\u0000b" + } + }, + "raw characters: control chars": { + "input": "k=\u0001\u001f", + "props": { + "k": "\u0001\u001f" + } + }, + "raw characters: BOM is not stripped": { + "input": "k=v", + "props": { + "k": "v" + } + }, + "raw characters: BOM on second line": { + "input": "a=1\nk=v", + "props": { + "a": "1", + "k": "v" + } + }, + "raw characters: non-breaking space is not whitespace": { + "input": " k=v", + "props": { + " k": "v" + } + }, + "raw characters: fullwidth equals is not a separator": { + "input": "k=v", + "props": { + "k=v": "" + } + }, + "keys: duplicate keys: last wins": { + "input": "k=1\nk=2\nk=3", + "props": { + "k": "3" + } + }, + "keys: duplicate via escapes": { + "input": "k=1\n\\k=2", + "props": { + "k": "2" + } + }, + "keys: proto-like keys": { + "input": "__proto__=a\nconstructor=b\nhasOwnProperty=c\ntoString=d", + "props": { + "__proto__": "a", + "constructor": "b", + "hasOwnProperty": "c", + "toString": "d" + } + }, + "keys: numeric keys": { + "input": "2=b\n10=c\n1=a\n-1=d\n01=e", + "props": { + "1": "a", + "2": "b", + "10": "c", + "-1": "d", + "01": "e" + } + }, + "keys: long key and value": { + "input": "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk=vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv", + "props": { + "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" + } + }, + "keys: long line before short lines": { + "input": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=y\na=\\", + "props": { + "a": "", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "y" + } + } +} diff --git a/test/compat/api-snapshot.json b/test/compat/api-snapshot.json new file mode 100644 index 0000000..f70901c --- /dev/null +++ b/test/compat/api-snapshot.json @@ -0,0 +1,34 @@ +{ + "main": [ + "default: object", + "parse: function", + "parseFile: function", + "stringify: function" + ], + "mainDefault": [ + "parse: function", + "parseFile: function", + "stringify: function" + ], + "browser": [ + "default: object", + "parse: function", + "stringify: function" + ], + "browserDefault": [ + "parse: function", + "stringify: function" + ], + "utils": [ + "LineReader: function", + "convertLine: function", + "decodeLine: function", + "encodeLine: function", + "rawParse: function", + "rawParseLine: function" + ], + "lineReader": [ + "constructor", + "readLine" + ] +} diff --git a/test/compat/check-esm.mjs b/test/compat/check-esm.mjs new file mode 100644 index 0000000..36b09b1 --- /dev/null +++ b/test/compat/check-esm.mjs @@ -0,0 +1,28 @@ +// ESM consumers of the built package (dist/). Runs on Node.js >= 12.20 (named imports from CommonJS). +// Keep this file in Node 12 syntax (no top-level await). +import assert from 'assert'; +import mainDefault, {parse, parseFile, stringify} from '../../dist/node-java-props.js'; +import browserDefault, * as browser from '../../dist/java-props.js'; +import {decodeLine, encodeLine, LineReader, rawParse, rawParseLine, convertLine} from '../../dist/utils.js'; + +// `import x from` gives module.exports (CommonJS), whose `default` is the deprecated default export +assert.strictEqual(mainDefault.parse, parse); +assert.strictEqual(mainDefault.default.parseFile, parseFile); +assert.strictEqual(browserDefault.stringify, stringify); +assert.strictEqual(browser.parse, parse); +assert.strictEqual(convertLine, decodeLine); +for (const fn of [encodeLine, rawParse, rawParseLine]) assert.strictEqual(typeof fn, 'function'); +assert.strictEqual(new LineReader('a=b').readLine(), 'a=b'); + +assert.deepStrictEqual(Object.assign({}, parse('a=1\nb : 2')), {a: '1', b: '2'}); +assert.strictEqual(stringify({a: '1'}), 'a: 1\n'); +parseFile(new URL('../fixtures/backslashes.properties', import.meta.url)).then( + (props) => { + assert.ok(Object.keys(props).length > 0); + console.log(`OK: ESM imports work on Node.js ${process.version}`); + }, + (err) => { + console.error(err); + process.exitCode = 1; + } +); diff --git a/test/compat/check-package.mjs b/test/compat/check-package.mjs new file mode 100644 index 0000000..c64bda0 --- /dev/null +++ b/test/compat/check-package.mjs @@ -0,0 +1,41 @@ +// Checks the npm package (dev only: needs a recent Node.js, npm and a build). +import assert from 'node:assert/strict'; +import {execFileSync} from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; + +const ROOT = path.join(import.meta.dirname, '../..'); +const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')); + +// Entry points. Users also import `java-props/dist/*` directly: every dist module is public. +assert.equal(pkg.main, 'dist/node-java-props.js'); +assert.equal(pkg.browser, 'dist/java-props.js'); +const [pack] = JSON.parse( + execFileSync('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], { + cwd: ROOT, + shell: process.platform === 'win32', // npm.cmd + }) +); +const files = pack.files.map((f) => f.path); +const required = ['java-props', 'node-java-props', 'utils'].flatMap((name) => + ['.js', '.d.ts', '.js.map'].map((ext) => `dist/${name}${ext}`) +); +assert.deepEqual( + required.filter((file) => !files.includes(file)), + [], + 'files missing from the package' +); + +// Fields that would break existing users +assert.equal(pkg.exports, undefined, '"exports" would block the `java-props/dist/*` imports'); +assert.ok(pkg.type === undefined || pkg.type === 'commonjs', 'the package must stay CommonJS'); +assert.equal(pkg.engines, undefined, '"engines" could reject installs on old Node.js versions'); +assert.equal(pkg.dependencies, undefined, 'no runtime dependencies'); + +// The browser entry must not require Node.js modules +const requires = (file) => + [...fs.readFileSync(path.join(ROOT, file), 'utf8').matchAll(/require\("([^"]+)"\)/g)].map((m) => m[1]); +assert.deepEqual(requires(pkg.browser), ['./utils'], 'requires of the browser entry'); +assert.deepEqual(requires('dist/utils.js'), [], 'requires of dist/utils.js'); + +console.log(`OK: package content (${files.length} files)`); diff --git a/test/compat/check-types.mjs b/test/compat/check-types.mjs new file mode 100644 index 0000000..c23d888 --- /dev/null +++ b/test/compat/check-types.mjs @@ -0,0 +1,64 @@ +// Compiles test/compat/types/consumer.ts against the package declarations with a given TypeScript version. +// Usage: node test/compat/check-types.mjs (needs a build) +import {execFileSync} from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +const ROOT = path.join(import.meta.dirname, '../..'); +const tsVersion = process.argv[2]; +if (!tsVersion) throw new Error('Usage: check-types.mjs '); + +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'java-props-types-')); +try { + // Fake installed package (old TypeScript versions only use "main" when the .js file exists) + const installed = path.join(tmp, 'node_modules/java-props'); + fs.mkdirSync(installed, {recursive: true}); + fs.copyFileSync(path.join(ROOT, 'package.json'), path.join(installed, 'package.json')); + fs.cpSync(path.join(ROOT, 'dist'), path.join(installed, 'dist'), {recursive: true}); + fs.cpSync(path.join(import.meta.dirname, 'types/stub'), path.join(tmp, 'types'), {recursive: true}); + fs.copyFileSync(path.join(import.meta.dirname, 'types/consumer.ts'), path.join(tmp, 'consumer.ts')); + + const tsc = (args) => + execFileSync('pnpm', ['--silent', 'dlx', '--package', `typescript@${tsVersion}`, 'tsc', ...args], { + cwd: tmp, + encoding: 'utf8', + }); + const major = Number( + tsc(['--version']) + .replace(/^Version /, '') + .split('.')[0] + ); + + const base = {noEmit: true, noImplicitAny: true, strictNullChecks: true, typeRoots: ['./types'], types: ['node']}; + const configs = { + node10: {...base, module: 'commonjs', moduleResolution: 'node', target: 'es6', lib: ['es6']}, + nodenext: {...base, module: 'nodenext', moduleResolution: 'nodenext', target: 'es2022', lib: ['es2022']}, + bundler: {...base, module: 'preserve', moduleResolution: 'bundler', target: 'es2022', lib: ['es2022']}, + }; + // node10 resolution is deprecated in TypeScript 6 and removed in 7; module "preserve" needs TypeScript 5.4+ + if (major === 6) configs.node10.ignoreDeprecations = '6.0'; + const names = + major >= 7 + ? ['nodenext', 'bundler'] + : major === 6 + ? ['node10', 'nodenext', 'bundler'] + : major === 5 + ? ['node10', 'nodenext'] + : ['node10']; + for (const name of names) { + fs.writeFileSync( + path.join(tmp, 'tsconfig.json'), + JSON.stringify({compilerOptions: configs[name], files: ['consumer.ts']}) + ); + try { + tsc(['-p', '.']); + } catch (err) { + console.error(err.stdout || err); + throw new Error(`TypeScript ${tsVersion} (${name}) failed`); + } + console.log(`OK: TypeScript ${tsVersion} (${name})`); + } +} finally { + fs.rmSync(tmp, {recursive: true, force: true}); +} diff --git a/test/compat/run-smoke.js b/test/compat/run-smoke.js new file mode 100644 index 0000000..5b2b1cf --- /dev/null +++ b/test/compat/run-smoke.js @@ -0,0 +1,133 @@ +// Smoke test of the built package (dist/). Runs on every supported Node.js version (>= 6), so: Node 6 syntax (no +// async/await, object spread, Object.entries/values, optional catch binding or trailing commas in calls), and no +// import of test/support (hence its own copies of a few helpers). + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.join(__dirname, '../..'); +const FIXTURES_DIR = path.join(ROOT, 'test/fixtures'); + +const main = require(ROOT); // package.json "main" +const browser = require(path.join(ROOT, require(path.join(ROOT, 'package.json')).browser)); +const utils = require(path.join(ROOT, 'dist/utils')); + +let count = 0; +function check(name, fn) { + try { + fn(); + count++; + } catch (err) { + console.error('FAILED: ' + name); + throw err; + } +} + +function sortedEntries(obj) { + return Object.keys(obj) + .sort() + .map((key) => [key, obj[key]]); +} + +// API: same as the snapshot of the sources (test/unit/api.test.ts) +function shape(mod) { + return Object.keys(mod) + .filter((key) => key !== '__esModule') + .map((key) => key + ': ' + typeof mod[key]) + .sort(); +} +const api = JSON.parse(fs.readFileSync(path.join(__dirname, 'api-snapshot.json'), 'utf8')); +check('API shape', () => { + assert.deepStrictEqual( + { + main: shape(main), + mainDefault: shape(main.default), + browser: shape(browser), + browserDefault: shape(browser.default), + utils: shape(utils), + lineReader: Object.getOwnPropertyNames(utils.LineReader.prototype).sort(), + }, + api + ); + assert.strictEqual(main.parse, browser.parse); + assert.strictEqual(main.stringify, browser.stringify); + assert.strictEqual(main.default.parse, main.parse); + assert.strictEqual(main.default.parseFile, main.parseFile); + assert.strictEqual(browser.default.stringify, browser.stringify); + assert.strictEqual(utils.convertLine, utils.decodeLine); +}); + +// Fixtures +fs.readdirSync(FIXTURES_DIR) + .filter((file) => /\.properties$/.test(file)) + .forEach((file) => { + check('fixture ' + file, () => { + const input = fs.readFileSync(path.join(FIXTURES_DIR, file), 'utf8'); + const expected = JSON.parse( + fs.readFileSync( + path.join(FIXTURES_DIR, file.replace(/(\.crlf)?\.properties$/, '.expected.json')), + 'utf8' + ) + ); + const props = main.parse(input); + assert.deepStrictEqual(sortedEntries(props), sortedEntries(expected)); + assert.deepStrictEqual(sortedEntries(main.parse(main.stringify(props))), sortedEntries(expected)); + }); + }); + +// Edge cases: same results as the sources (test/unit/parse-cases.test.ts) +const cases = JSON.parse(fs.readFileSync(path.join(ROOT, 'test/cases/parse-snapshot.json'), 'utf8')); +Object.keys(cases).forEach((name) => { + check('case ' + name, () => { + const c = cases[name]; + let res; + try { + res = {props: sortedEntries(main.parse(c.input))}; + } catch (err) { + res = {throws: err.message}; + } + assert.deepStrictEqual(res, c.throws ? {throws: c.message} : {props: sortedEntries(c.props)}); + }); +}); + +// stringify +check('stringify', () => { + assert.strictEqual(main.stringify({a: 'b c', ' k=': ' é\n'}), 'a: b c\n\\ k\\=: \\ \\u00E9\\n\n'); + const nullProto = Object.create(null); + nullProto.k = 'v'; + assert.strictEqual(main.stringify(nullProto), 'k: v\n'); +}); + +// parseFile +const file = path.join(FIXTURES_DIR, 'syntax.properties'); +const expected = JSON.parse(fs.readFileSync(path.join(FIXTURES_DIR, 'syntax.expected.json'), 'utf8')); +main.parseFile(file) + .then((props) => { + check('parseFile', () => { + assert.deepStrictEqual(sortedEntries(props), sortedEntries(expected)); + }); + return main.parseFile(file, 'utf8'); + }) + .then((props) => { + check('parseFile with encoding', () => { + assert.deepStrictEqual(sortedEntries(props), sortedEntries(expected)); + }); + return main.parseFile(path.join(FIXTURES_DIR, 'nonexistent.properties')).then( + () => { + throw new Error('parseFile must reject'); + }, + (err) => { + check('parseFile rejection', () => { + assert.strictEqual(err.code, 'ENOENT'); + }); + } + ); + }) + .then(() => { + console.log('OK: ' + count + ' checks passed on Node.js ' + process.version); + }) + .catch((err) => { + console.error(err); + process.exitCode = 1; + }); diff --git a/test/compat/types/consumer.ts b/test/compat/types/consumer.ts new file mode 100644 index 0000000..af2120e --- /dev/null +++ b/test/compat/types/consumer.ts @@ -0,0 +1,42 @@ +// Uses every public declaration. Compiled by TypeScript 2.9+ (check-types.mjs): keep it in TypeScript 2.9 syntax. +import javaProps = require('java-props'); +import defaultProps from 'java-props'; +import {parse, parseFile, stringify, Properties} from 'java-props'; +import * as browser from 'java-props/dist/java-props'; +import browserDefault from 'java-props/dist/java-props'; +import { + LineReader, + ParsedLine, + rawParse, + rawParseLine, + decodeLine, + encodeLine, + convertLine, +} from 'java-props/dist/utils'; + +const p1: Properties = javaProps.parse('a=b'); +const p2: Properties = parse('a=b'); +const s: string = stringify(p1) + javaProps.stringify(p2) + browser.stringify(p2) + browserDefault.stringify(p2); +const p3: Properties = defaultProps.parse(s) || browser.parse(s) || browserDefault.parse(s); +const value: string = p3['a']; + +const promises: Array> = [ + parseFile('file'), + parseFile('file', 'latin1'), + parseFile(Buffer.from('file')), + parseFile(new URL('file:///file')), + parseFile(3), + javaProps.parseFile('file'), + defaultProps.parseFile('file'), +]; + +const lr = new LineReader('a=b'); +const line: string | undefined = lr.readLine(); +const pos: number = lr.pos + lr.strLen; +const str: string = lr.str; +rawParse(s, (res: ParsedLine) => { + const n: number = res.sepStart + res.valueStart; + const l: string = res.line; +}); +const parsed: ParsedLine = rawParseLine('a=b'); +const decoded: string = decodeLine('a') + encodeLine('a') + encodeLine('a', true) + convertLine('a'); diff --git a/test/compat/types/stub/node/index.d.ts b/test/compat/types/stub/node/index.d.ts new file mode 100644 index 0000000..2a164f8 --- /dev/null +++ b/test/compat/types/stub/node/index.d.ts @@ -0,0 +1,7 @@ +// Minimal stand-in for @types/node: enough for the java-props declarations, and parseable by any TypeScript version. +declare class Buffer { + static from(str: string): Buffer; +} +declare class URL { + constructor(url: string); +} diff --git a/test/data/testData1.dos.properties b/test/data/testData1.dos.properties deleted file mode 100644 index fd33463..0000000 --- a/test/data/testData1.dos.properties +++ /dev/null @@ -1,35 +0,0 @@ -# testcases -key0=abcd\ - -\ -key1=value1 - -key2 \= abcde -key3 - - #key4=abcde -key5 ===Arial,ANSI_CHARSET -key6= WingDings,SYMBOL_CHARSET \\\ - abc -key7 Symbol,SYMBOL_CHARSET \ - - keyabcdef - -key8notassign abcdef -key9\ Term=ABCDE - -\\:key10=bar - -\\\\:key11=bar2 - -\\\:key12=bar2 - -key13dialog.3= -key14_asdfa -key15 \ abcdef -key16\ b= \ abcdef -key17=\ -#bar\ -baz - - diff --git a/test/data/testData1.properties b/test/data/testData1.properties deleted file mode 100644 index 2be6432..0000000 --- a/test/data/testData1.properties +++ /dev/null @@ -1,35 +0,0 @@ -# testcases -key0=abcd\ - -\ -key1=value1 - -key2 \= abcde -key3 - - #key4=abcde -key5 ===Arial,ANSI_CHARSET -key6= WingDings,SYMBOL_CHARSET \\\ - abc -key7 Symbol,SYMBOL_CHARSET \ - - keyabcdef - -key8notassign abcdef -key9\ Term=ABCDE - -\\:key10=bar - -\\\\:key11=bar2 - -\\\:key12=bar2 - -key13dialog.3= -key14_asdfa -key15 \ abcdef -key16\ b= \ abcdef -key17=\ -#bar\ -baz - - diff --git a/test/data/testData1.properties-result.json b/test/data/testData1.properties-result.json deleted file mode 100644 index 23d683f..0000000 --- a/test/data/testData1.properties-result.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "key0": "abcd", - "key1": "value1", - "key2": "= abcde", - "key3": "", - "key5": "==Arial,ANSI_CHARSET", - "key6": "WingDings,SYMBOL_CHARSET \\abc", - "key7": "Symbol,SYMBOL_CHARSET ", - "keyabcdef": "", - "key8notassign": "abcdef", - "key9 Term": "ABCDE", - "\\": "key10=bar", - "\\\\": "key11=bar2", - "\\:key12": "bar2", - "key13dialog.3": "", - "key14_asdfa": "", - "key15": " abcdef", - "key16 b": " abcdef", - "key17": "#barbaz" -} diff --git a/test/data/testData2.dos.properties b/test/data/testData2.dos.properties deleted file mode 100644 index fb97374..0000000 --- a/test/data/testData2.dos.properties +++ /dev/null @@ -1,4 +0,0 @@ -# -key1=-monotype-timesnewroman-regular-r---*-%d-*-*-p-*-iso8859-1serif.1a-monotype-timesnewroman-regular-r-normal--*-%d-*-*-p-*-iso8859-2serif.2a-b&h-LucidaBrightLat4-Normal-r-normal--*-%d-*-*-p-*-iso8859-4serif.3a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-5serif.4a-monotype-timesnewromangreek-regular-r-normal--*-%d-*-*-p-*-iso8859-7serif.5a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-9serif.6a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-15serif.7a-hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1serif.8a-sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0serif.9a-ricoh-hgminchol-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0serif.10a-ricoh-hgminchol-medium-r-normal--*-%d-*-*-m-*-jisx0208.1983-0serif.11a-ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0serif.12a-hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3serif.13a-urw-itczapfdingbats-medium-r-normal--*-%d-*-*-p-*-sun-fontspecificserif.14a-*-symbol-medium-r-normal--*-%d-*-*-p-*-sun-fontspecificbserif.italic.0=-monotype-timesbnewbroman-regular-i---*-%d-*-*-p-*-iso8859-1bserif.italic.1=-monotype-timesbnewbroman-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-2 - -key2=-b&h-LucidaBrightLat4-normal-i-normal-Italic-*-%d-*-*-p-*-iso8859-4 diff --git a/test/data/testData2.properties b/test/data/testData2.properties deleted file mode 100644 index b2d70ea..0000000 --- a/test/data/testData2.properties +++ /dev/null @@ -1,4 +0,0 @@ -# -key1=-monotype-timesnewroman-regular-r---*-%d-*-*-p-*-iso8859-1serif.1a-monotype-timesnewroman-regular-r-normal--*-%d-*-*-p-*-iso8859-2serif.2a-b&h-LucidaBrightLat4-Normal-r-normal--*-%d-*-*-p-*-iso8859-4serif.3a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-5serif.4a-monotype-timesnewromangreek-regular-r-normal--*-%d-*-*-p-*-iso8859-7serif.5a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-9serif.6a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-15serif.7a-hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1serif.8a-sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0serif.9a-ricoh-hgminchol-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0serif.10a-ricoh-hgminchol-medium-r-normal--*-%d-*-*-m-*-jisx0208.1983-0serif.11a-ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0serif.12a-hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3serif.13a-urw-itczapfdingbats-medium-r-normal--*-%d-*-*-p-*-sun-fontspecificserif.14a-*-symbol-medium-r-normal--*-%d-*-*-p-*-sun-fontspecificbserif.italic.0=-monotype-timesbnewbroman-regular-i---*-%d-*-*-p-*-iso8859-1bserif.italic.1=-monotype-timesbnewbroman-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-2 - -key2=-b&h-LucidaBrightLat4-normal-i-normal-Italic-*-%d-*-*-p-*-iso8859-4 diff --git a/test/data/testData2.properties-result.json b/test/data/testData2.properties-result.json deleted file mode 100644 index 2f39e6e..0000000 --- a/test/data/testData2.properties-result.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "key1": "-monotype-timesnewroman-regular-r---*-%d-*-*-p-*-iso8859-1serif.1a-monotype-timesnewroman-regular-r-normal--*-%d-*-*-p-*-iso8859-2serif.2a-b&h-LucidaBrightLat4-Normal-r-normal--*-%d-*-*-p-*-iso8859-4serif.3a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-5serif.4a-monotype-timesnewromangreek-regular-r-normal--*-%d-*-*-p-*-iso8859-7serif.5a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-9serif.6a-monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-15serif.7a-hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1serif.8a-sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0serif.9a-ricoh-hgminchol-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0serif.10a-ricoh-hgminchol-medium-r-normal--*-%d-*-*-m-*-jisx0208.1983-0serif.11a-ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0serif.12a-hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3serif.13a-urw-itczapfdingbats-medium-r-normal--*-%d-*-*-p-*-sun-fontspecificserif.14a-*-symbol-medium-r-normal--*-%d-*-*-p-*-sun-fontspecificbserif.italic.0=-monotype-timesbnewbroman-regular-i---*-%d-*-*-p-*-iso8859-1bserif.italic.1=-monotype-timesbnewbroman-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-2", - "key2": "-b&h-LucidaBrightLat4-normal-i-normal-Italic-*-%d-*-*-p-*-iso8859-4" -} diff --git a/test/data/testData3.dos.properties b/test/data/testData3.dos.properties deleted file mode 100644 index dd6118d..0000000 --- a/test/data/testData3.dos.properties +++ /dev/null @@ -1,29 +0,0 @@ -key1=\ --monotype-times new roman-regular-r---*-%d-*-*-p-*-iso8859-1, \ --monotype-times new roman-regular-r-normal--*-%d-*-*-p-*-iso8859-2, \ --b&h-LucidaBrightLat4-Normal-r-normal--*-%d-*-*-p-*-iso8859-4, \ --monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-5, \ --monotype-times new roman greek-regular-r-normal--*-%d-*-*-p-*-iso8859-7, \ --monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-9, \ --monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-15, \ --hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1, \ --sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0, \ --ricoh-hg gothic b-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0, \ --ricoh-hg gothic b-medium-r-normal-*-*-%d-*-*-m-*-jisx0208.1983-0, \ --ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0, \ --hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3 - -key2=\ --monotype-times new roman-regular-i---*-%d-*-*-p-*-iso8859-1, \ --monotype-times new roman-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-2, \ --b&h-LucidaBrightLat4-normal-i-normal-Italic-*-%d-*-*-p-*-iso8859-4, \ --monotype-times-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-5, \ --monotype-times new roman greek-regular-i-normal--*-%d-*-*-p-*-iso8859-7, \ --monotype-times-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-9, \ --monotype-times-regular-i-normal--*-%d-*-*-p-*-iso8859-15, \ --hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1, \ --sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0, \ --ricoh-hg gothic b-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0, \ --ricoh-hg gothic b-medium-r-normal-*-*-%d-*-*-m-*-jisx0208.1983-0, \ --ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0, \ --hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3 diff --git a/test/data/testData3.properties-result.json b/test/data/testData3.properties-result.json deleted file mode 100644 index 901dfaf..0000000 --- a/test/data/testData3.properties-result.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "key1": "-monotype-times new roman-regular-r---*-%d-*-*-p-*-iso8859-1, -monotype-times new roman-regular-r-normal--*-%d-*-*-p-*-iso8859-2, -b&h-LucidaBrightLat4-Normal-r-normal--*-%d-*-*-p-*-iso8859-4, -monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-5, -monotype-times new roman greek-regular-r-normal--*-%d-*-*-p-*-iso8859-7, -monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-9, -monotype-times-regular-r-normal--*-%d-*-*-p-*-iso8859-15, -hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1, -sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0, -ricoh-hg gothic b-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0, -ricoh-hg gothic b-medium-r-normal-*-*-%d-*-*-m-*-jisx0208.1983-0, -ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0, -hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3", - "key2": "-monotype-times new roman-regular-i---*-%d-*-*-p-*-iso8859-1, -monotype-times new roman-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-2, -b&h-LucidaBrightLat4-normal-i-normal-Italic-*-%d-*-*-p-*-iso8859-4, -monotype-times-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-5, -monotype-times new roman greek-regular-i-normal--*-%d-*-*-p-*-iso8859-7, -monotype-times-regular-i-normal-italic-*-%d-*-*-p-*-iso8859-9, -monotype-times-regular-i-normal--*-%d-*-*-p-*-iso8859-15, -hanyi-ming-medium-r-normal--*-%d-*-*-m-*-big5-1, -sun-song-medium-r-normal--*-%d-*-*-c-*-gb2312.1980-0, -ricoh-hg gothic b-medium-r-normal--*-%d-*-*-m-*-jisx0201.1976-0, -ricoh-hg gothic b-medium-r-normal-*-*-%d-*-*-m-*-jisx0208.1983-0, -ricoh-heiseimin-w3-r-normal--*-%d-*-*-m-*-jisx0212.1990-0, -hanyang-myeongjo-medium-r-normal--*-%d-*-*-m-*-ksc5601.1992-3" -} diff --git a/test/fixtures/README.md b/test/fixtures/README.md new file mode 100644 index 0000000..790a311 --- /dev/null +++ b/test/fixtures/README.md @@ -0,0 +1,11 @@ +# Test fixtures + +Each `x.properties` file (and its CRLF variant `x.crlf.properties`, if any) is parsed and compared to +`x.expected.json`. The expected results are checked against real JVMs by `pnpm run reference:check`. + +The `.properties` files are byte-exact (see `.gitattributes`): some use CRLF or mixed line endings on purpose. + +- `syntax`: separators, escapes, comments, continuation lines, keys without value. +- `backslashes`: trailing backslashes. +- `multiline`: values and keys spanning several lines (continuation lines). +- `mixed-line-endings`: LF, CRLF and CR in the same file. diff --git a/test/data/testData4.properties-result.json b/test/fixtures/backslashes.expected.json similarity index 100% rename from test/data/testData4.properties-result.json rename to test/fixtures/backslashes.expected.json diff --git a/test/data/testData4.properties b/test/fixtures/backslashes.properties similarity index 100% rename from test/data/testData4.properties rename to test/fixtures/backslashes.properties diff --git a/test/fixtures/mixed-line-endings.expected.json b/test/fixtures/mixed-line-endings.expected.json new file mode 100644 index 0000000..03b1e11 --- /dev/null +++ b/test/fixtures/mixed-line-endings.expected.json @@ -0,0 +1,10 @@ +{ + "continued.cr": "ab", + "continued.crlf": "ab", + "continued.lf": "ab", + "continued.mixed": "abcd", + "cr": "3", + "crlf": "2", + "last": "end", + "lf": "1" +} diff --git a/test/fixtures/mixed-line-endings.properties b/test/fixtures/mixed-line-endings.properties new file mode 100644 index 0000000..aed7b9e --- /dev/null +++ b/test/fixtures/mixed-line-endings.properties @@ -0,0 +1,14 @@ +# LF, CRLF and CR line terminators in one file +lf=1 +crlf=2 +cr=3 continued.lf=a\ + b +continued.crlf=a\ + b +continued.cr=a\ b continued.mixed=a\ + b\ c\ + d + + +# comment ! comment +last=end \ No newline at end of file diff --git a/test/fixtures/multiline.crlf.properties b/test/fixtures/multiline.crlf.properties new file mode 100644 index 0000000..b5d2279 --- /dev/null +++ b/test/fixtures/multiline.crlf.properties @@ -0,0 +1,34 @@ +# Values and keys spanning several lines +greeting = Hello, \ + World! +path=/usr/local/bin:\ + /usr/bin:\ + /bin + +indented.key\ + .continued = value + indented.entry = first \ + second +whitespace.kinds = one\ + two +unicode.across.lines = caf\ + \u00e9 au lait +escape.across.lines = tab\ + \tseparated + +# An even number of backslashes doesn't continue the line +windows.dir = C:\\Program Files\\ +after.windows.dir = not part of the previous value +odd.backslashes = a\\\ + b + +# Continuation lines are never comments +hash.continuation = start\ + # not a comment +bang.continuation = start\ + ! not a comment + +key\ with\ spaces = value with trailing spaces +empty.value = +only.key +last = end diff --git a/test/fixtures/multiline.expected.json b/test/fixtures/multiline.expected.json new file mode 100644 index 0000000..ee12d3c --- /dev/null +++ b/test/fixtures/multiline.expected.json @@ -0,0 +1,18 @@ +{ + "after.windows.dir": "not part of the previous value", + "bang.continuation": "start! not a comment", + "empty.value": "", + "escape.across.lines": "tab\tseparated", + "greeting": "Hello, World!", + "hash.continuation": "start# not a comment", + "indented.entry": "first second", + "indented.key.continued": "value", + "key with spaces": "value with trailing spaces ", + "last": "end", + "odd.backslashes": "a\\b", + "only.key": "", + "path": "/usr/local/bin:/usr/bin:/bin", + "unicode.across.lines": "café au lait", + "whitespace.kinds": "onetwo", + "windows.dir": "C:\\Program Files\\" +} diff --git a/test/fixtures/multiline.properties b/test/fixtures/multiline.properties new file mode 100644 index 0000000..4edc24a --- /dev/null +++ b/test/fixtures/multiline.properties @@ -0,0 +1,34 @@ +# Values and keys spanning several lines +greeting = Hello, \ + World! +path=/usr/local/bin:\ + /usr/bin:\ + /bin + +indented.key\ + .continued = value + indented.entry = first \ + second +whitespace.kinds = one\ + two +unicode.across.lines = caf\ + \u00e9 au lait +escape.across.lines = tab\ + \tseparated + +# An even number of backslashes doesn't continue the line +windows.dir = C:\\Program Files\\ +after.windows.dir = not part of the previous value +odd.backslashes = a\\\ + b + +# Continuation lines are never comments +hash.continuation = start\ + # not a comment +bang.continuation = start\ + ! not a comment + +key\ with\ spaces = value with trailing spaces +empty.value = +only.key +last = end diff --git a/test/data/testData0.properties-result.json b/test/fixtures/syntax.expected.json similarity index 100% rename from test/data/testData0.properties-result.json rename to test/fixtures/syntax.expected.json diff --git a/test/data/testData0.properties b/test/fixtures/syntax.properties similarity index 100% rename from test/data/testData0.properties rename to test/fixtures/syntax.properties diff --git a/test/java-props.test.ts b/test/java-props.test.ts deleted file mode 100644 index b745f77..0000000 --- a/test/java-props.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import fs from 'fs'; -import JavaProps from '../src/java-props'; -import {decodeLine, encodeLine} from '../src/utils'; - -const dataFiles = [ - 'testData0.properties', - 'testData1.properties', - 'testData1.dos.properties', - 'testData2.properties', - 'testData2.dos.properties', - 'testData3.dos.properties', - 'testData4.properties', -]; - -describe('parse', () => { - for (const file of dataFiles) { - it('validate ' + file, async () => { - const res = JSON.parse( - fs.readFileSync(__dirname + '/data/' + file.replace('.dos', '') + '-result.json', 'utf8') - ); - const props = JavaProps.parse(fs.readFileSync(__dirname + '/data/' + file, 'utf8')); - expect(props).toEqual(res); - }); - } - - it('must escape crlf', () => { - expect(JavaProps.parse('abc=123\\\r\ndef=456')).toEqual({abc: '123def=456'}); - expect(JavaProps.parse('abc=123\\\rdef=456')).toEqual({abc: '123def=456'}); - }); - - it('must ignore escaped eof', () => { - expect(JavaProps.parse('\\')).toEqual({}); - expect(JavaProps.parse('key=value\\')).toEqual({key: 'value'}); - }); - - it('must ignore empty last line', () => { - expect(JavaProps.parse('')).toEqual({}); - expect(JavaProps.parse('\n')).toEqual({}); - expect(JavaProps.parse('\n ')).toEqual({}); - expect(JavaProps.parse('key=value\n')).toEqual({key: 'value'}); - expect(JavaProps.parse('key=value\n ')).toEqual({key: 'value'}); - expect(JavaProps.parse('key=value\n# comment')).toEqual({key: 'value'}); - }); -}); - -describe('stringify', () => { - const expectConvertBack = (props: any) => { - const newProps = JavaProps.parse(JavaProps.stringify(props)); - expect(newProps).toEqual(props); - }; - - for (const file of dataFiles) { - it('convert back ' + file, async () => { - expectConvertBack(JavaProps.parse(fs.readFileSync(__dirname + '/data/' + file, 'utf8'))); - }); - } - - it('must handle spacings', async () => { - expectConvertBack({' key': 'value'}); - expectConvertBack({key: ' value'}); - expectConvertBack({'key ': 'value'}); - expectConvertBack({key: 'value '}); - expectConvertBack({' ke:y= ': ' = \\nval\\ue\n '}); - }); - - it('must skip non-own properties', async () => { - const obj = {key: 'value'}; - (Object.prototype as any).notAKey = 'x'; - expect(JavaProps.stringify(obj)).toBe('key: value\n'); - }); - - it('must handle null-prototype objects', async () => { - const obj = Object.create(null); - obj.key = 'value'; - expect(JavaProps.stringify(obj)).toBe('key: value\n'); - }); -}); - -const converts: any = { - t: '\t', - r: '\r', - n: '\n', - f: '\f', -}; - -describe('utils.decodeLine', () => { - for (const char in converts) { - if (converts.hasOwnProperty(char)) { - const repl = converts[char]; - it('must decode \\' + char, () => { - expect(decodeLine('abc\\' + char + 'def\\' + char + 'ghi')).toBe('abc' + repl + 'def' + repl + 'ghi'); - }); - } - } - - it('must remove useless escapes', () => { - expect(decodeLine('a\\bc\\def\\\\')).toBe('abcdef\\'); - }); - - it('must replace \\uxxxx', () => { - expect(decodeLine('\\u0058 \\u2764')).toBe('X ❤'); - expect(decodeLine('\\u00580')).toBe('X0'); - expect(decodeLine('\\u0058A\\u2764Test')).toBe('XA❤Test'); - expect(decodeLine('\\u002F \\u002f')).toBe('/ /'); - }); - - it('must throw on malformed \\uxxxx', () => { - expect(() => decodeLine('\\u')).toThrow(); - expect(() => decodeLine('\\u58')).toThrow(); - expect(() => decodeLine('\\u27g4')).toThrow(); - }); - - it('must not replace escaped', () => { - expect(decodeLine('test\\nand\\\\n \\\\\\n')).toBe('test\nand\\n \\\n'); - expect(decodeLine('abc \\\\u0000')).toBe('abc \\u0000'); - expect(decodeLine('abc \\\\uxxxx')).toBe('abc \\uxxxx'); - }); -}); - -const origEncodeLine = encodeLine; -for (const isKey of [false, true]) { - const encodeLine = (line: string) => origEncodeLine(line, isKey); - describe('utils.encodeLine (isKey=' + isKey + ')', () => { - for (const char in converts) { - if (converts.hasOwnProperty(char)) { - const repl = converts[char]; - it('must encode \\' + char, () => { - expect(encodeLine('abc' + repl + 'def' + repl + 'ghi')).toBe( - 'abc\\' + char + 'def\\' + char + 'ghi' - ); - }); - } - } - - it('must escape special chars', () => { - expect(encodeLine('!# :=\\')).toBe(isKey ? '\\!\\#\\ \\:\\=\\\\' : '!# :=\\\\'); - }); - - if (!isKey) { - it('must escape beginning space', () => { - expect(encodeLine(' abc def ghi jkl ')).toBe('\\ abc def ghi jkl '); - }); - } - - it('must not escape normal chars', () => { - let str = ''; - for (let cc = 33; cc < 127; ++cc) { - const c = String.fromCharCode(cc); - if (c === '\\' || (isKey && (c === '!' || c === '#' || c === ' ' || c === ':' || c === '='))) { - continue; - } - str += c; - } - expect(encodeLine(str)).toBe(str); - }); - - it('must encode others unicode chars', () => { - expect(encodeLine('\0')).toBe('\\u0000'); - expect(encodeLine('\u0014')).toBe('\\u0014'); - expect(encodeLine('\u007F')).toBe('\\u007F'); - expect(encodeLine('é')).toBe('\\u00E9'); - expect(encodeLine('👑')).toBe('\\uD83D\\uDC51'); - expect(encodeLine(String.fromCharCode(15))).toBe('\\u000F'); - expect(encodeLine(String.fromCharCode(255))).toBe('\\u00FF'); - expect(encodeLine(String.fromCharCode(4095))).toBe('\\u0FFF'); - expect(encodeLine(String.fromCharCode(65535))).toBe('\\uFFFF'); - }); - }); -} diff --git a/test/node-java-props.test.ts b/test/node-java-props.test.ts deleted file mode 100644 index 5d4fcd0..0000000 --- a/test/node-java-props.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import fs from 'fs'; -import JavaProps from '../src/node-java-props'; - -describe('parseFile', () => { - const file = 'testData0.properties'; - it('read and validate ' + file, async () => { - const res = JSON.parse(fs.readFileSync(__dirname + '/data/' + file + '-result.json', 'utf8')); - const props = await JavaProps.parseFile(__dirname + '/data/' + file, 'utf8'); - expect(props).toEqual(res); - }); - - it('must throw on read error', async () => { - let err; - try { - await JavaProps.parseFile(__dirname + '/nonexistent-file.properties'); - } catch (e) { - err = e; - } - expect(err).toBeDefined(); - }); -}); diff --git a/test/support/data.mts b/test/support/data.mts new file mode 100644 index 0000000..d3dd445 --- /dev/null +++ b/test/support/data.mts @@ -0,0 +1,33 @@ +// Test fixtures (test/fixtures) and the Java results of the edge cases (test/cases). +import fs from 'node:fs'; +import path from 'node:path'; +import type {LoadEra, LoadResult} from './load-result.mts'; + +export const FIXTURES_DIR = path.join(import.meta.dirname, '../fixtures'); +export const PARSE_EXPECTED_FILE = path.join(import.meta.dirname, '../cases/parse-expected.json'); + +/** The .properties files of test/fixtures. */ +export function fixtureFiles(): string[] { + return fs + .readdirSync(FIXTURES_DIR) + .filter((file) => file.endsWith('.properties')) + .sort(); +} + +export function readFixture(file: string): string { + return fs.readFileSync(path.join(FIXTURES_DIR, file), 'utf8'); +} + +/** + * Expected result of a fixture: `x.expected.json`, for `x.properties` and its CRLF variant `x.crlf.properties`. + */ +export function readFixtureResult(file: string): Record { + return JSON.parse(readFixture(file.replace(/(\.crlf)?\.properties$/, '.expected.json'))); +} + +export type ParseExpected = Record>; + +/** Reads parse-expected.json (not imported as a module: Vite rejects lone surrogate escapes like "\ud83d"). */ +export function readParseExpected(): ParseExpected { + return JSON.parse(fs.readFileSync(PARSE_EXPECTED_FILE, 'utf8')); +} diff --git a/test/support/load-result.mts b/test/support/load-result.mts new file mode 100644 index 0000000..cce355b --- /dev/null +++ b/test/support/load-result.mts @@ -0,0 +1,33 @@ +// Results of Properties.load() / parse(), and the behavior eras of Properties.load(). + +export type LoadResult = {props: Record} | {throws: string; message: string}; + +/** + * Behavior eras of Properties.load(), each named after a representative JDK: + * - jdk8: the original implementation (unchanged from JDK 6 to 10, also in 11.0.0 to 11.0.4 and in 12). + * - jdk11: JDK-8224202 "Speed up Properties.load" (https://bugs.openjdk.org/browse/JDK-8224202), a rewrite of + * Properties.LineReader that also changed its behavior: `#`/`!` right after an empty continuation line starts a + * comment, and no state (backslash count, stale buffer content) leaks from comment lines anymore. + * Released in JDK 13, and backported to 11.0.5+ (but not to 12). + * - jdk13: jdk11 + JDK-8224240 "Properties.load fails to throw IAE on malformed unicode in certain circumstances" + * (https://bugs.openjdk.org/browse/JDK-8224240): a truncated `\uXXXX` at the end of a line throws, instead of + * decoding stale buffer content. JDK 13+ (not backported to 11). + */ +export const LOAD_ERAS = ['jdk8', 'jdk11', 'jdk13'] as const; +export type LoadEra = (typeof LOAD_ERAS)[number]; + +/** Copies properties into a null-prototype object (so "__proto__" stays a data key), sorted by key. */ +export function sortedProps(props: Record): Record { + const res: Record = Object.create(null); + for (const key of Object.keys(props).sort()) res[key] = props[key]; + return res; +} + +/** Runs a JS parse function and captures its result, or the error it throws. */ +export function captureLoad(parse: () => Record): LoadResult { + try { + return {props: sortedProps(parse())}; + } catch (err) { + return {throws: (err as Error).name, message: (err as Error).message}; + } +} diff --git a/test/unit/api.test.ts b/test/unit/api.test.ts new file mode 100644 index 0000000..f0d870e --- /dev/null +++ b/test/unit/api.test.ts @@ -0,0 +1,34 @@ +// Snapshot of the public API (export names and types): removing or retyping an export breaks users. +// The built dist/ is checked against the same snapshot by test/compat/run-smoke.js. +import {describe, expect, it} from 'vitest'; +import CommonJavaProps, * as CommonJavaPropsNs from '../../src/java-props'; +import NodeJavaProps, * as NodeJavaPropsNs from '../../src/node-java-props'; +import * as Utils from '../../src/utils'; + +const shape = (mod: object) => + Object.entries(mod) + .filter(([key]) => key !== '__esModule') + .map(([key, value]) => `${key}: ${typeof value}`) + .sort(); + +describe('exports', () => { + it('match the API snapshot', async () => { + const api = { + main: shape(NodeJavaPropsNs), + mainDefault: shape(NodeJavaProps), + browser: shape(CommonJavaPropsNs), + browserDefault: shape(CommonJavaProps), + utils: shape(Utils), + lineReader: Object.getOwnPropertyNames(Utils.LineReader.prototype).sort(), + }; + await expect(`${JSON.stringify(api, null, 2)}\n`).toMatchFileSnapshot('../compat/api-snapshot.json'); + }); + + it('share the same functions', () => { + for (const mod of [CommonJavaProps, CommonJavaPropsNs, NodeJavaProps, NodeJavaPropsNs]) { + expect(mod.parse).toBe(CommonJavaPropsNs.parse); + expect(mod.stringify).toBe(CommonJavaPropsNs.stringify); + } + expect(NodeJavaProps.parseFile).toBe(NodeJavaPropsNs.parseFile); + }); +}); diff --git a/test/unit/java-props.test.ts b/test/unit/java-props.test.ts new file mode 100644 index 0000000..9384335 --- /dev/null +++ b/test/unit/java-props.test.ts @@ -0,0 +1,86 @@ +import {describe, expect, it} from 'vitest'; +import JavaProps, {type Properties} from '../../src/java-props'; +import {fixtureFiles, readFixture, readFixtureResult} from '../support/data.mts'; + +const fixtures = fixtureFiles(); + +describe('parse', () => { + it('has fixtures', () => { + expect(fixtures.length).toBeGreaterThan(0); + }); + + for (const file of fixtures) { + it(`parses ${file}`, () => { + expect(JavaProps.parse(readFixture(file))).toEqual(readFixtureResult(file)); + }); + } + + it('joins continuation lines ending with CRLF or CR', () => { + expect(JavaProps.parse('abc=123\\\r\ndef=456')).toEqual({abc: '123def=456'}); + expect(JavaProps.parse('abc=123\\\rdef=456')).toEqual({abc: '123def=456'}); + }); + + it('ignores a continuation backslash at the end (Java returns an empty key)', () => { + expect(JavaProps.parse('\\')).toEqual({}); + expect(JavaProps.parse('key=value\\')).toEqual({key: 'value'}); + }); + + it('ignores a trailing blank line or comment', () => { + expect(JavaProps.parse('')).toEqual({}); + expect(JavaProps.parse('\n')).toEqual({}); + expect(JavaProps.parse('\n ')).toEqual({}); + expect(JavaProps.parse('key=value\n')).toEqual({key: 'value'}); + expect(JavaProps.parse('key=value\n ')).toEqual({key: 'value'}); + expect(JavaProps.parse('key=value\n# comment')).toEqual({key: 'value'}); + }); + + it('returns a null-prototype object', () => { + const props = JavaProps.parse('__proto__=a\nconstructor=b'); + expect(Object.getPrototypeOf(props)).toBe(null); + expect(Object.keys(props)).toEqual(['__proto__', 'constructor']); + expect(Object.getOwnPropertyDescriptor(props, '__proto__')?.value).toBe('a'); + }); + + it('throws on malformed \\uXXXX', () => { + expect(() => JavaProps.parse('k=\\u12')).toThrow(new Error('Malformed \\uxxxx encoding.')); + }); +}); + +describe('stringify', () => { + const expectConvertBack = (props: Properties) => { + expect(JavaProps.parse(JavaProps.stringify(props))).toEqual(props); + }; + + for (const file of fixtures) { + it(`round-trips ${file}`, () => { + expectConvertBack(JavaProps.parse(readFixture(file))); + }); + } + + it('round-trips leading and trailing spaces', () => { + expectConvertBack({' key': 'value'}); + expectConvertBack({key: ' value'}); + expectConvertBack({'key ': 'value'}); + expectConvertBack({key: 'value '}); + expectConvertBack({' ke:y= ': ' = \\nval\\ue\n '}); + }); + + it('writes "key: value" lines', () => { + expect(JavaProps.stringify({a: '1', b: '2'})).toBe('a: 1\nb: 2\n'); + expect(JavaProps.stringify({})).toBe(''); + expect(JavaProps.stringify({'': ''})).toBe(': \n'); + }); + + it('skips inherited properties', () => { + const proto = {inherited: 'x'}; + const obj = Object.create(proto); + obj.key = 'value'; + expect(JavaProps.stringify(obj)).toBe('key: value\n'); + }); + + it('accepts null-prototype objects', () => { + const obj = Object.create(null); + obj.key = 'value'; + expect(JavaProps.stringify(obj)).toBe('key: value\n'); + }); +}); diff --git a/test/unit/node-java-props.test.ts b/test/unit/node-java-props.test.ts new file mode 100644 index 0000000..4ed3f6c --- /dev/null +++ b/test/unit/node-java-props.test.ts @@ -0,0 +1,65 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {pathToFileURL} from 'node:url'; +import {afterAll, beforeAll, describe, expect, it} from 'vitest'; +import JavaProps from '../../src/node-java-props'; +import {FIXTURES_DIR, readFixtureResult} from '../support/data.mts'; + +const file = path.join(FIXTURES_DIR, 'syntax.properties'); +const expected = readFixtureResult('syntax.properties'); + +describe('parseFile', () => { + let tmpDir: string; + beforeAll(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'java-props-')); + }); + afterAll(() => { + fs.rmSync(tmpDir, {recursive: true, force: true}); + }); + + it('reads a file path (utf8 by default)', async () => { + expect(await JavaProps.parseFile(file)).toEqual(expected); + expect(await JavaProps.parseFile(file, 'utf8')).toEqual(expected); + }); + + it('reads a Buffer path, a file URL and a file descriptor', async () => { + expect(await JavaProps.parseFile(Buffer.from(file))).toEqual(expected); + expect(await JavaProps.parseFile(pathToFileURL(file))).toEqual(expected); + const fd = fs.openSync(file, 'r'); + try { + expect(await JavaProps.parseFile(fd)).toEqual(expected); + } finally { + fs.closeSync(fd); + } + }); + + it('uses the given encoding', async () => { + const latin1File = path.join(tmpDir, 'latin1.properties'); + fs.writeFileSync(latin1File, Buffer.from('k=\u00e9\n', 'latin1')); + expect(await JavaProps.parseFile(latin1File, 'latin1')).toEqual({k: '\u00e9'}); + expect(await JavaProps.parseFile(latin1File)).toEqual({k: '\ufffd'}); + }); + + it('keeps the UTF-8 BOM (like Java)', async () => { + const bomFile = path.join(tmpDir, 'bom.properties'); + fs.writeFileSync(bomFile, '\ufeffk=v\n'); + expect(await JavaProps.parseFile(bomFile)).toEqual({'\ufeffk': 'v'}); + }); + + it('rejects on read error', async () => { + await expect(JavaProps.parseFile(path.join(FIXTURES_DIR, 'nonexistent.properties'))).rejects.toMatchObject({ + code: 'ENOENT', + }); + }); + + it('rejects on invalid encoding', async () => { + await expect(JavaProps.parseFile(file, 'not-an-encoding')).rejects.toThrow(); + }); + + it('rejects on parse error', async () => { + const badFile = path.join(tmpDir, 'bad.properties'); + fs.writeFileSync(badFile, 'k=\\u12\n'); + await expect(JavaProps.parseFile(badFile)).rejects.toThrow('Malformed \\uxxxx encoding.'); + }); +}); diff --git a/test/unit/parse-cases.test.ts b/test/unit/parse-cases.test.ts new file mode 100644 index 0000000..a1e28e4 --- /dev/null +++ b/test/unit/parse-cases.test.ts @@ -0,0 +1,82 @@ +// Runs the edge cases of test/cases/parse-cases.mts: their results are snapshotted in parse-snapshot.json, and compared +// to the results of real JVMs (parse-expected.json, generated by tools/gen-parse-expected.mts). +import {describe, expect, it} from 'vitest'; +import {parse} from '../../src/java-props'; +import cases from '../cases/parse-cases.mts'; +import {readParseExpected} from '../support/data.mts'; +import {captureLoad, LOAD_ERAS, type LoadEra, type LoadResult} from '../support/load-result.mts'; + +const expected = readParseExpected(); +const results: Record = {}; +for (const c of cases) results[c.name] = captureLoad(() => parse(c.input)); + +// Cases where parse() differs from Java, kept on purpose so that existing code keeps working. +// Exception classes are not compared (Error vs IllegalArgumentException). +const JAVA_DIVERGENCES: Record = { + jdk8: [ + 'lone continuation backslash: alone', + 'lone continuation backslash: alone with LF', + 'lone continuation backslash: after property', + 'lone continuation backslash: after property, with LF', + 'lone continuation backslash: indented', + 'comment ending with backslash, then backslashes: comment ending with backslash, then backslash-only line', + 'comment ending with backslash, then backslashes: bang comment, CRLF, lone backslash', + 'comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes', + 'stale line buffer: lone backslash decode after comment', + 'stale line buffer: truncated unicode after longer line', + ], + jdk11: [ + 'lone continuation backslash: alone', + 'lone continuation backslash: alone with LF', + 'lone continuation backslash: after property', + 'lone continuation backslash: after property, with LF', + 'lone continuation backslash: indented', + 'empty continuation then comment char: hash', + 'empty continuation then comment char: bang with indentation', + 'empty continuation then comment char: hash after CRLF', + 'empty continuation then comment char: hash with leading whitespace on next line', + 'empty continuation then comment char: double continuation then hash', + 'comment ending with backslash, then backslashes: comment ending with backslash, then backslash-only line', + 'comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes', + 'stale line buffer: truncated unicode after longer line', + ], + jdk13: [ + 'lone continuation backslash: alone', + 'lone continuation backslash: alone with LF', + 'lone continuation backslash: after property', + 'lone continuation backslash: after property, with LF', + 'lone continuation backslash: indented', + 'empty continuation then comment char: hash', + 'empty continuation then comment char: bang with indentation', + 'empty continuation then comment char: hash after CRLF', + 'empty continuation then comment char: hash with leading whitespace on next line', + 'empty continuation then comment char: double continuation then hash', + 'comment ending with backslash, then backslashes: comment ending with backslash, then backslash-only line', + 'comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes', + ], +}; + +const sameOutcome = (a: LoadResult, b: LoadResult) => { + if ('throws' in a || 'throws' in b) return 'throws' in a && 'throws' in b && a.message === b.message; + return JSON.stringify(Object.entries(a.props).sort()) === JSON.stringify(Object.entries(b.props).sort()); +}; + +describe('parse cases', () => { + it('match the snapshot', async () => { + const snapshot = Object.fromEntries(cases.map((c) => [c.name, {input: c.input, ...results[c.name]}])); + await expect(`${JSON.stringify(snapshot, null, 2)}\n`).toMatchFileSnapshot('../cases/parse-snapshot.json'); + }); + + it('all have Java results', () => { + expect(Object.keys(expected).sort()).toEqual(cases.map((c) => c.name).sort()); + }); + + for (const era of LOAD_ERAS) { + it(`only diverge from Java (${era}) on the known cases`, () => { + const divergences = cases + .filter((c) => !sameOutcome(results[c.name], expected[c.name][era])) + .map((c) => c.name); + expect(divergences).toEqual(JAVA_DIVERGENCES[era]); + }); + } +}); diff --git a/test/unit/properties.test.ts b/test/unit/properties.test.ts new file mode 100644 index 0000000..722741b --- /dev/null +++ b/test/unit/properties.test.ts @@ -0,0 +1,36 @@ +// Property-based tests (fast-check): random inputs, shrunk to a minimal one on failure. +import fc from 'fast-check'; +import {describe, expect, it} from 'vitest'; +import {parse, stringify} from '../../src/java-props'; + +// Pieces of .properties text that matter to the parser, and some that don't (backslash weighted) +const PIECES = [ + ...['\\', '\\', '\\', '\\u', '\\u00', '\\u0041', '\\\n', '\\\r\n'], + ...['\n', '\r', '\r\n', '\n#', '\n!', ' ', '\t', '\f', '=', ':', '#', '!'], + ...['a', 'k', 'v', 'u', '0', 'F', 'g', 'n', 't', 'é'], + ...['\u0000', '\u007F', '\u00A0', '\uFEFF', '\uD83D', '\uDC51'], +]; +const text = (maxPieces: number) => + fc.array(fc.constantFrom(...PIECES), {maxLength: maxPieces}).map((pieces) => pieces.join('')); + +describe('properties', () => { + it('parse only throws on malformed \\uXXXX', () => { + fc.assert( + fc.property(text(24), (input) => { + try { + parse(input); + } catch (err) { + expect((err as Error).message).toBe('Malformed \\uxxxx encoding.'); + } + }) + ); + }); + + it('stringify output is parsed back to the same properties', () => { + fc.assert( + fc.property(fc.dictionary(text(8), text(8)), (props) => { + expect({...parse(stringify(props))}).toEqual({...props}); + }) + ); + }); +}); diff --git a/test/unit/types.test-d.ts b/test/unit/types.test-d.ts new file mode 100644 index 0000000..53b170b --- /dev/null +++ b/test/unit/types.test-d.ts @@ -0,0 +1,44 @@ +// Public types (vitest --typecheck). Compatibility with old TypeScript versions: test/compat/check-types.mjs. +import {describe, expectTypeOf, it} from 'vitest'; +import javaProps, {type Properties, parse, parseFile, stringify} from '../../src/node-java-props'; +import { + convertLine, + decodeLine, + encodeLine, + LineReader, + type ParsedLine, + rawParse, + rawParseLine, +} from '../../src/utils'; + +describe('types', () => { + it('main', () => { + expectTypeOf().toEqualTypeOf<{[key: string]: string}>(); + expectTypeOf(parse).toEqualTypeOf<(str: string) => Properties>(); + expectTypeOf(stringify).toEqualTypeOf<(props: Properties) => string>(); + expectTypeOf(parseFile).toEqualTypeOf< + (path: string | Buffer | URL | number, encoding?: string) => Promise + >(); + expectTypeOf(javaProps).toEqualTypeOf<{ + parse: typeof parse; + parseFile: typeof parseFile; + stringify: typeof stringify; + }>(); + }); + + it('utils', () => { + expectTypeOf().toEqualTypeOf<{line: string; sepStart: number; valueStart: number}>(); + expectTypeOf(rawParse).toEqualTypeOf<(str: string, consumeFn: (res: ParsedLine) => void) => void>(); + expectTypeOf(rawParseLine).toEqualTypeOf<(line: string) => ParsedLine>(); + expectTypeOf(decodeLine).toEqualTypeOf<(line: string) => string>(); + expectTypeOf(convertLine).toEqualTypeOf(decodeLine); + expectTypeOf(encodeLine).toEqualTypeOf<(line: string, isKey?: boolean) => string>(); + expectTypeOf(LineReader).constructorParameters.toEqualTypeOf<[str: string]>(); + expectTypeOf().toEqualTypeOf<() => string | undefined>(); + expectTypeOf>().toEqualTypeOf<{ + readonly str: string; + readonly strLen: number; + pos: number; + }>(); + }); +}); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts new file mode 100644 index 0000000..5b4cbfb --- /dev/null +++ b/test/unit/utils.test.ts @@ -0,0 +1,176 @@ +import {describe, expect, it} from 'vitest'; +import { + convertLine, + decodeLine, + encodeLine, + LineReader, + type ParsedLine, + rawParse, + rawParseLine, +} from '../../src/utils'; + +const converts: Record = { + t: '\t', + r: '\r', + n: '\n', + f: '\f', +}; + +describe('utils.decodeLine', () => { + for (const [char, repl] of Object.entries(converts)) { + it(`decodes \\${char}`, () => { + expect(decodeLine(`abc\\${char}def\\${char}ghi`)).toBe(`abc${repl}def${repl}ghi`); + }); + } + + it('drops the backslash of other escapes', () => { + expect(decodeLine('a\\bc\\def\\\\')).toBe('abcdef\\'); + }); + + it('decodes \\uXXXX', () => { + expect(decodeLine('\\u0058 \\u2764')).toBe('X ❤'); + expect(decodeLine('\\u00580')).toBe('X0'); + expect(decodeLine('\\u0058A\\u2764Test')).toBe('XA❤Test'); + expect(decodeLine('\\u002F \\u002f')).toBe('/ /'); + expect(decodeLine('\\uD83D\\uDC51')).toBe('👑'); + }); + + it('throws on malformed \\uXXXX', () => { + expect(() => decodeLine('\\u')).toThrow('Malformed \\uxxxx encoding.'); + expect(() => decodeLine('\\u58')).toThrow('Malformed \\uxxxx encoding.'); + expect(() => decodeLine('\\u27g4')).toThrow('Malformed \\uxxxx encoding.'); + }); + + it('decodes escaped backslashes', () => { + expect(decodeLine('test\\nand\\\\n \\\\\\n')).toBe('test\nand\\n \\\n'); + expect(decodeLine('abc \\\\u0000')).toBe('abc \\u0000'); + expect(decodeLine('abc \\\\uxxxx')).toBe('abc \\uxxxx'); + }); + + it('drops a trailing lone backslash', () => { + expect(decodeLine('abc\\')).toBe('abc'); + }); + + it('has the deprecated convertLine alias', () => { + expect(convertLine).toBe(decodeLine); + }); +}); + +for (const isKey of [false, true]) { + const encode = (line: string) => encodeLine(line, isKey); + describe(`utils.encodeLine (isKey=${isKey})`, () => { + for (const [char, repl] of Object.entries(converts)) { + it(`encodes \\${char}`, () => { + expect(encode(`abc${repl}def${repl}ghi`)).toBe(`abc\\${char}def\\${char}ghi`); + }); + } + + it('escapes special chars', () => { + expect(encode('!# :=\\')).toBe(isKey ? '\\!\\#\\ \\:\\=\\\\' : '!# :=\\\\'); + }); + + if (!isKey) { + it('escapes leading whitespace', () => { + expect(encode(' abc def ghi jkl ')).toBe('\\ abc def ghi jkl '); + expect(encode('\tabc')).toBe('\\tabc'); + }); + } + + it('keeps printable ASCII chars', () => { + let str = ''; + for (let cc = 33; cc < 127; ++cc) { + const c = String.fromCharCode(cc); + if (c === '\\' || (isKey && (c === '!' || c === '#' || c === ' ' || c === ':' || c === '='))) { + continue; + } + str += c; + } + expect(encode(str)).toBe(str); + }); + + it('encodes other chars as \\uXXXX', () => { + expect(encode('\0')).toBe('\\u0000'); + expect(encode('\u0014')).toBe('\\u0014'); + expect(encode('\u007F')).toBe('\\u007F'); + expect(encode('é')).toBe('\\u00E9'); + expect(encode('👑')).toBe('\\uD83D\\uDC51'); + expect(encode(String.fromCharCode(15))).toBe('\\u000F'); + expect(encode(String.fromCharCode(16))).toBe('\\u0010'); + expect(encode(String.fromCharCode(255))).toBe('\\u00FF'); + expect(encode(String.fromCharCode(256))).toBe('\\u0100'); + expect(encode(String.fromCharCode(4095))).toBe('\\u0FFF'); + expect(encode(String.fromCharCode(4096))).toBe('\\u1000'); + expect(encode(String.fromCharCode(65535))).toBe('\\uFFFF'); + }); + }); +} + +describe('utils.encodeLine', () => { + it('defaults to value encoding', () => { + expect(encodeLine(' a=b')).toBe(encodeLine(' a=b', false)); + }); +}); + +describe('utils.LineReader', () => { + const readAll = (str: string) => { + const lr = new LineReader(str); + const lines: string[] = []; + for (let line = lr.readLine(); line !== undefined; line = lr.readLine()) lines.push(line); + return lines; + }; + + it('exposes its state', () => { + const lr = new LineReader('a=1\nb=2'); + expect(lr.str).toBe('a=1\nb=2'); + expect(lr.strLen).toBe(7); + expect(lr.pos).toBe(0); + expect(lr.readLine()).toBe('a=1'); + expect(lr.pos).toBe(4); + expect(lr.readLine()).toBe('b=2'); + expect(lr.readLine()).toBe(undefined); + expect(lr.readLine()).toBe(undefined); + }); + + it('reads logical lines', () => { + expect(readAll(' a = 1 \r\n# c\n\n\tb\\\n c\\\r\n d\\\r e\n! c\\\nf')).toEqual(['a = 1 ', 'bcde', 'f']); + }); + + it('keeps escapes', () => { + expect(readAll('a\\=\\u0041\\\\\nb')).toEqual(['a\\=\\u0041\\\\', 'b']); + }); +}); + +describe('utils.rawParseLine', () => { + const split = (line: string) => { + const res = rawParseLine(line); + expect(res.line).toBe(line); + return [line.substring(0, res.sepStart), line.substring(res.valueStart)]; + }; + + it('finds key and value', () => { + expect(split('key=value')).toEqual(['key', 'value']); + expect(split('key : value')).toEqual(['key', 'value']); + expect(split('key value')).toEqual(['key', 'value']); + expect(split('key =:value')).toEqual(['key', ':value']); + expect(split('k\\=ey\\ \\:=value')).toEqual(['k\\=ey\\ \\:', 'value']); + expect(split('key')).toEqual(['key', '']); + expect(split('=value')).toEqual(['', 'value']); + expect(split('')).toEqual(['', '']); + }); + + it('returns offsets', () => { + expect(rawParseLine('ab = cd')).toEqual({line: 'ab = cd', sepStart: 2, valueStart: 5}); + }); +}); + +describe('utils.rawParse', () => { + it('calls consumeFn for each logical line', () => { + const res: ParsedLine[] = []; + rawParse('a=1\n# c\nb : 2\\\n 3\nc', (line) => res.push(line)); + expect(res).toEqual([ + {line: 'a=1', sepStart: 1, valueStart: 2}, + {line: 'b : 23', sepStart: 1, valueStart: 4}, + {line: 'c', sepStart: 1, valueStart: 1}, + ]); + }); +}); diff --git a/tools/gen-parse-expected.mts b/tools/gen-parse-expected.mts new file mode 100644 index 0000000..7a50a95 --- /dev/null +++ b/tools/gen-parse-expected.mts @@ -0,0 +1,95 @@ +// Generates test/cases/parse-expected.json: the result of each case of test/cases/parse-cases.mts with real JVMs +// (one key per era, see LOAD_ERAS in test/support/load-result.mts). +// Also checks the test/fixtures files against every JVM. +// +// Usage: node tools/gen-parse-expected.mts [--check] +// JDKs: JAVA_REFERENCE_JDKS="name=/path/to/bin/java,..." (default: `java` from PATH). Eras without a JDK keep +// their committed results. +// --check: fail if the committed file is outdated, without writing it. +import fs from 'node:fs'; +import path from 'node:path'; +import cases from '../test/cases/parse-cases.mts'; +import { + fixtureFiles, + PARSE_EXPECTED_FILE, + readFixture, + readFixtureResult, + readParseExpected, +} from '../test/support/data.mts'; +import {LOAD_ERAS, type LoadResult, sortedProps} from '../test/support/load-result.mts'; +import {type Jdk, jvmLoad, listJdks, loadEra} from './java-reference/reference.mts'; + +const ROOT = path.join(import.meta.dirname, '..'); +const check = process.argv.includes('--check'); + +const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b); +let failed = false; +const fail = (msg: string) => { + console.error(`ERROR: ${msg}`); + failed = true; +}; + +// Run every JDK, grouped by era +const jdks = listJdks(); +const byEra = new Map(); +const inputs = cases.map((c) => c.input); +const fixtures = fixtureFiles(); +const fixtureInputs = fixtures.map(readFixture); +for (const jdk of jdks) { + const era = loadEra(jdk.version); + console.log(`${jdk.name}: java.version=${jdk.version} -> era ${era}`); + const results = jvmLoad(jdk, [...inputs, ...fixtureInputs]); + if (!byEra.has(era)) byEra.set(era, []); + byEra.get(era)?.push({jdk, results}); +} + +// Fixtures: every JVM must match the committed .expected.json +fixtures.forEach((file, i) => { + const expected = {props: sortedProps(readFixtureResult(file))}; + for (const runs of byEra.values()) { + for (const {jdk, results} of runs) { + if (!same(results[inputs.length + i], expected)) fail(`fixture ${file}: ${jdk.name} result differs`); + } + } +}); + +// Cases +const previous: Record> = fs.existsSync(PARSE_EXPECTED_FILE) ? readParseExpected() : {}; +const expected: Record> = {}; +const names = new Set(); +cases.forEach((c, i) => { + if (names.has(c.name)) fail(`duplicate case name: ${c.name}`); + names.add(c.name); + const entry: Record = {input: c.input}; + for (const era of LOAD_ERAS) { + const runs = byEra.get(era); + if (!runs) { + if (previous[c.name]?.[era]) entry[era] = previous[c.name][era]; + continue; + } + const [first, ...others] = runs; + for (const other of others) { + if (!same(first.results[i], other.results[i])) { + fail(`${c.name}: ${first.jdk.name} and ${other.jdk.name} disagree (same era ${era}): new era?`); + } + } + entry[era] = first.results[i]; + } + expected[c.name] = entry; +}); + +for (const era of LOAD_ERAS) + if (!byEra.has(era)) console.warn(`WARNING: no JDK for era ${era}, keeping committed results`); + +const json = `${JSON.stringify(expected, null, 2)}\n`; +if (check) { + if (fs.existsSync(PARSE_EXPECTED_FILE) && fs.readFileSync(PARSE_EXPECTED_FILE, 'utf8') === json) { + console.log(`${path.relative(ROOT, PARSE_EXPECTED_FILE)} is up to date`); + } else { + fail(`${path.relative(ROOT, PARSE_EXPECTED_FILE)} is outdated: run "pnpm run reference:gen"`); + } +} else { + fs.writeFileSync(PARSE_EXPECTED_FILE, json); + console.log(`written ${path.relative(ROOT, PARSE_EXPECTED_FILE)} (${cases.length} cases)`); +} +process.exit(failed ? 1 : 0); diff --git a/tools/java-reference/JavaReference.java b/tools/java-reference/JavaReference.java new file mode 100644 index 0000000..99a3522 --- /dev/null +++ b/tools/java-reference/JavaReference.java @@ -0,0 +1,77 @@ +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.StringReader; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +/** + * Runs java.util.Properties for reference.mts. Java 8 compatible (compiled with --release 8, run on every tested JDK). + * + * Protocol: one request per line on stdin, one response per line on stdout. Strings are sent as hex UTF-16 code + * units (4 digits per char), so that any char gets through (line terminators, lone surrogates...): + * - "version" -> "" + * - "load " -> "ok :,..." (sorted by key), or "throw " + */ +public class JavaReference { + public static void main(String[] args) throws Exception { + BufferedReader in = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); + PrintStream out = new PrintStream(System.out, false, "UTF-8"); + String request; + while ((request = in.readLine()) != null) { + int space = request.indexOf(' '); + String command = space < 0 ? request : request.substring(0, space); + String payload = space < 0 ? "" : request.substring(space + 1); + if (command.equals("version")) { + out.println(System.getProperty("java.version")); + } else if (command.equals("load")) { + out.println(load(decode(payload))); + } else { + throw new IllegalArgumentException("Unknown command: " + command); + } + out.flush(); + } + } + + private static String load(String input) { + Properties props = new Properties(); + try { + props.load(new StringReader(input)); + } catch (Throwable t) { + String message = t.getMessage(); + return "throw " + t.getClass().getName() + " " + encode(message == null ? "" : message); + } + TreeMap sorted = new TreeMap(); + for (String key : props.stringPropertyNames()) { + sorted.put(key, props.getProperty(key)); + } + StringBuilder sb = new StringBuilder("ok "); + boolean first = true; + for (Map.Entry entry : sorted.entrySet()) { + if (!first) { + sb.append(','); + } + first = false; + sb.append(encode(entry.getKey())).append(':').append(encode(entry.getValue())); + } + return sb.toString(); + } + + private static String encode(String str) { + StringBuilder sb = new StringBuilder(str.length() * 4); + for (int i = 0; i < str.length(); i++) { + String hex = Integer.toHexString(str.charAt(i)); + sb.append("0000", hex.length(), 4).append(hex); + } + return sb.toString(); + } + + private static String decode(String hex) { + StringBuilder sb = new StringBuilder(hex.length() / 4); + for (int i = 0; i < hex.length(); i += 4) { + sb.append((char) Integer.parseInt(hex.substring(i, i + 4), 16)); + } + return sb.toString(); + } +} diff --git a/tools/java-reference/reference.mts b/tools/java-reference/reference.mts new file mode 100644 index 0000000..0a41a29 --- /dev/null +++ b/tools/java-reference/reference.mts @@ -0,0 +1,101 @@ +// Runs the Java reference (JavaReference.java) on one or more JDKs. +import {execFileSync} from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import type {LoadEra, LoadResult} from '../../test/support/load-result.mts'; + +const REFERENCE_DIR = import.meta.dirname; +const BUILD_DIR = path.join(REFERENCE_DIR, 'build'); + +export interface Jdk { + /** Display name (e.g. "temurin-25") */ + name: string; + /** Path to the `java` executable */ + java: string; + /** Value of the java.version system property */ + version: string; +} + +const encode = (str: string) => { + let hex = ''; + for (let i = 0; i < str.length; i++) hex += str.charCodeAt(i).toString(16).padStart(4, '0'); + return hex; +}; + +const decode = (hex: string) => { + let str = ''; + for (let i = 0; i < hex.length; i += 4) str += String.fromCharCode(Number.parseInt(hex.slice(i, i + 4), 16)); + return str; +}; + +/** Compiles the reference program (with the `javac` from PATH, targeting Java 8) if needed. */ +function compileReference(): void { + const source = path.join(REFERENCE_DIR, 'JavaReference.java'); + const output = path.join(BUILD_DIR, 'JavaReference.class'); + if (fs.existsSync(output) && fs.statSync(output).mtimeMs >= fs.statSync(source).mtimeMs) return; + execFileSync('javac', ['--release', '8', '-Xlint:-options', '-d', BUILD_DIR, source], {stdio: 'inherit'}); +} + +function run(java: string, requests: string[]): string[] { + const output = execFileSync(java, ['-cp', BUILD_DIR, 'JavaReference'], { + input: `${requests.join('\n')}\n`, + maxBuffer: 1 << 30, + }).toString('utf8'); + const lines = output.split('\n'); + lines.pop(); // trailing newline + if (lines.length !== requests.length) + throw new Error(`JavaReference returned ${lines.length}/${requests.length} lines`); + return lines; +} + +/** + * Lists the JDKs to use, from the JAVA_REFERENCE_JDKS env var ("name=/path/to/java,name2=/path/to/java2"), + * or defaults to the `java` from PATH. + */ +export function listJdks(): Jdk[] { + const spec = process.env.JAVA_REFERENCE_JDKS; + const entries = spec + ? spec + .split(',') + .map((entry) => entry.trim()) + .filter((entry) => entry !== '') + .map((entry) => { + const eq = entry.indexOf('='); + return {name: entry.slice(0, eq), java: entry.slice(eq + 1)}; + }) + : [{name: 'java', java: 'java'}]; + compileReference(); + return entries.map((jdk) => ({...jdk, version: run(jdk.java, ['version'])[0]})); +} + +/** Runs Properties.load(Reader) on each input. */ +export function jvmLoad(jdk: Jdk, inputs: string[]): LoadResult[] { + return run( + jdk.java, + inputs.map((input) => `load ${encode(input)}`) + ).map((line) => { + if (line.startsWith('throw ')) { + const [, className, message] = line.split(' '); + return {throws: className, message: decode(message)}; + } + const props: Record = Object.create(null); // keys like "__proto__" must stay data keys + const body = line.slice('ok '.length); + if (body !== '') { + for (const entry of body.split(',')) { + const [key, value] = entry.split(':'); + props[decode(key)] = decode(value); + } + } + return {props}; + }); +} + +/** Behavior era of Properties.load() for a java.version string (see LOAD_ERAS in test/support/load-result.mts). */ +export function loadEra(javaVersion: string): LoadEra { + const parts = javaVersion.replace(/^1\./, '').split(/[.+_-]/); + const major = Number(parts[0]); + const update = Number(parts[2] ?? 0); + if (major >= 13) return 'jdk13'; + if (major === 11 && update >= 5) return 'jdk11'; + return 'jdk8'; +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..ac25e30 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2015", + "lib": ["es2015"], + "types": ["node"], + "module": "commonjs", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "sourceMap": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json index 398c13a..54af350 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,18 @@ { + // Type-checks everything (sources, tests, tools). The published build uses tsconfig.build.json. "compilerOptions": { - "module": "commonjs", + "target": "es2024", + "lib": ["es2024"], + "types": ["node"], + "module": "preserve", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, "esModuleInterop": true, - "target": "es6", - "noImplicitReturns": true, - "noImplicitAny": true, - "moduleResolution": "node", - "sourceMap": true, - "declaration": true, - "outDir": "dist", - "baseUrl": ".", - "paths": { - "*": ["node_modules/*", "src/types/*"] - } + "strict": true, + "skipLibCheck": true, + "noEmit": true }, - "include": ["src/**/*.ts"] + "include": ["src", "test", "tools", "*.mts"], + // Compiled separately, by many TypeScript versions (test/compat/check-types.mjs) + "exclude": ["test/compat/types"] } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index c2f7948..0000000 --- a/tslint.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "extends": "tslint:latest", - "rules": { - "no-unnecessary-type-assertion": true, - "array-type": [true, "array"], - "ban-types": { - "options": [ - ["Object", "Avoid using the `Object` type. Did you mean `object`?"], - [ - "Function", - "Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`." - ], - ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], - ["Number", "Avoid using the `Number` type. Did you mean `number`?"], - ["String", "Avoid using the `String` type. Did you mean `string`?"] - ] - }, - "class-name": true, - "comment-format": [true, "check-space"], - "curly": [true, "ignore-same-line"], - "indent": [true, "spaces"], - "interface-name": [true, "never-prefix"], - "interface-over-type-literal": true, - "jsdoc-format": true, - "linebreak-style": [true, "LF"], - "no-inferrable-types": true, - "no-internal-module": true, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": [true, "ignore-template-strings"], - "no-unnecessary-qualifier": true, - "no-var-keyword": true, - "object-literal-shorthand": true, - "one-line": [true, "check-open-brace", "check-whitespace"], - "prefer-const": true, - "quotemark": [true, "single", "avoid-escape"], - "semicolon": [true, "always", "ignore-bound-class-methods"], - "space-within-parens": true, - "triple-equals": true, - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - }, - { - "call-signature": "onespace", - "index-signature": "onespace", - "parameter": "onespace", - "property-declaration": "onespace", - "variable-declaration": "onespace" - } - ], - "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], - // Config different from tslint:latest - "no-implicit-dependencies": [true, "dev"], - "object-literal-key-quotes": [true, "consistent-as-needed"], - "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], - // Custom - "arrow-parens": true, - "no-empty": false, - "no-parameter-reassignment": true, - // TODO - "arrow-return-shorthand": false, - "ban-types": false, - "forin": false, - "member-access": false, - // [true, "no-public"] - "no-conditional-assignment": false, - "no-console": false, - "no-debugger": false, - "no-empty-interface": false, - "no-eval": false, - "no-object-literal-type-assertion": false, - "no-shadowed-variable": false, - "no-submodule-imports": false, - "no-var-requires": false, - "ordered-imports": false, - "prefer-conditional-expression": false, - "radix": false, - "trailing-comma": false, - // These should be done automatically by a formatter. https://github.com/Microsoft/TypeScript/issues/18340 - "align": false, - "eofline": false, - "max-line-length": false, - "no-consecutive-blank-lines": false, - "space-before-function-paren": false, - // Not doing - "ban-comma-operator": false, - "max-classes-per-file": false, - "member-ordering": false, - "no-angle-bracket-type-assertion": false, - "no-bitwise": false, - "no-namespace": false, - "no-reference": false, - "object-literal-sort-keys": false, - "one-variable-per-declaration": false - } -} diff --git a/typedoc.json b/typedoc.json deleted file mode 100644 index a941c27..0000000 --- a/typedoc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "out": "typedoc", - "name": "node-java-props Documentation", - "entryPoints": ["src/typedoc.d.ts"], - "exclude": ["src/utils.ts"], - "excludePrivate": true, - "disableSources": true, - "githubPages": true -} diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 0000000..f513fe6 --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,13 @@ +import {defineConfig} from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/unit/**/*.test.ts'], + typecheck: {enabled: true, include: ['test/unit/**/*.test-d.ts']}, + coverage: { + provider: 'v8', + include: ['src/**'], + thresholds: {100: true}, + }, + }, +}); diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index c539603..0000000 --- a/yarn.lock +++ /dev/null @@ -1,3610 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.15.8": - version "7.15.8" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz" - integrity sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/compat-data@^7.15.0": - version "7.15.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz" - integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== - -"@babel/core@^7.1.0": - version "7.15.8" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz" - integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og== - dependencies: - "@babel/code-frame" "^7.15.8" - "@babel/generator" "^7.15.8" - "@babel/helper-compilation-targets" "^7.15.4" - "@babel/helper-module-transforms" "^7.15.8" - "@babel/helpers" "^7.15.4" - "@babel/parser" "^7.15.8" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.6" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - source-map "^0.5.0" - -"@babel/generator@^7.15.4", "@babel/generator@^7.15.8", "@babel/generator@^7.4.0": - version "7.15.8" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz" - integrity sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g== - dependencies: - "@babel/types" "^7.15.6" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/helper-compilation-targets@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz" - integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ== - dependencies: - "@babel/compat-data" "^7.15.0" - "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" - semver "^6.3.0" - -"@babel/helper-function-name@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz" - integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== - dependencies: - "@babel/helper-get-function-arity" "^7.15.4" - "@babel/template" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-get-function-arity@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz" - integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-hoist-variables@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz" - integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-member-expression-to-functions@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz" - integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-imports@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz" - integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-transforms@^7.15.8": - version "7.15.8" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz" - integrity sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg== - dependencies: - "@babel/helper-module-imports" "^7.15.4" - "@babel/helper-replace-supers" "^7.15.4" - "@babel/helper-simple-access" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.6" - -"@babel/helper-optimise-call-expression@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz" - integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz" - integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== - -"@babel/helper-replace-supers@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz" - integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.15.4" - "@babel/helper-optimise-call-expression" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-simple-access@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz" - integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-split-export-declaration@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz" - integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz" - integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== - -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== - -"@babel/helpers@^7.15.4": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz" - integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ== - dependencies: - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.8", "@babel/parser@^7.4.3": - version "7.15.8" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz" - integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA== - -"@babel/plugin-syntax-object-rest-spread@^7.0.0": - 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== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/template@^7.15.4", "@babel/template@^7.4.0": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz" - integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.15.4", "@babel/traverse@^7.4.3": - version "7.15.4" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz" - integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-function-name" "^7.15.4" - "@babel/helper-hoist-variables" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.15.4", "@babel/types@^7.15.6", "@babel/types@^7.3.0", "@babel/types@^7.4.0": - version "7.15.6" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz" - integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - to-fast-properties "^2.0.0" - -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" - -"@jest/console@^24.7.1", "@jest/console@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz" - integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== - dependencies: - "@jest/source-map" "^24.9.0" - chalk "^2.0.1" - slash "^2.0.0" - -"@jest/core@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz" - integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== - dependencies: - "@jest/console" "^24.7.1" - "@jest/reporters" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - ansi-escapes "^3.0.0" - chalk "^2.0.1" - exit "^0.1.2" - graceful-fs "^4.1.15" - jest-changed-files "^24.9.0" - jest-config "^24.9.0" - jest-haste-map "^24.9.0" - jest-message-util "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-resolve-dependencies "^24.9.0" - jest-runner "^24.9.0" - jest-runtime "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - jest-watcher "^24.9.0" - micromatch "^3.1.10" - p-each-series "^1.0.0" - realpath-native "^1.1.0" - rimraf "^2.5.4" - slash "^2.0.0" - strip-ansi "^5.0.0" - -"@jest/environment@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz" - integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== - dependencies: - "@jest/fake-timers" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - -"@jest/fake-timers@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz" - integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== - dependencies: - "@jest/types" "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - -"@jest/reporters@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz" - integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== - dependencies: - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - exit "^0.1.2" - glob "^7.1.2" - istanbul-lib-coverage "^2.0.2" - istanbul-lib-instrument "^3.0.1" - istanbul-lib-report "^2.0.4" - istanbul-lib-source-maps "^3.0.1" - istanbul-reports "^2.2.6" - jest-haste-map "^24.9.0" - jest-resolve "^24.9.0" - jest-runtime "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.6.0" - node-notifier "^5.4.2" - slash "^2.0.0" - source-map "^0.6.0" - string-length "^2.0.0" - -"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz" - integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.1.15" - source-map "^0.6.0" - -"@jest/test-result@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz" - integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== - dependencies: - "@jest/console" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/istanbul-lib-coverage" "^2.0.0" - -"@jest/test-sequencer@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz" - integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== - dependencies: - "@jest/test-result" "^24.9.0" - jest-haste-map "^24.9.0" - jest-runner "^24.9.0" - jest-runtime "^24.9.0" - -"@jest/transform@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz" - integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^24.9.0" - babel-plugin-istanbul "^5.1.0" - chalk "^2.0.1" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.1.15" - jest-haste-map "^24.9.0" - jest-regex-util "^24.9.0" - jest-util "^24.9.0" - micromatch "^3.1.10" - pirates "^4.0.1" - realpath-native "^1.1.0" - slash "^2.0.0" - source-map "^0.6.1" - write-file-atomic "2.4.1" - -"@jest/types@^24.9.0": - version "24.9.0" - resolved "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz" - integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^13.0.0" - -"@types/babel__core@^7.1.0": - version "7.1.16" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz" - integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.3" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz" - integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== - dependencies: - "@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== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.14.2" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz" - integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== - dependencies: - "@babel/types" "^7.3.0" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.3" - resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== - -"@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== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^1.1.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz" - integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== - dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" - -"@types/jest@^24.0.18": - version "24.9.1" - resolved "https://registry.npmjs.org/@types/jest/-/jest-24.9.1.tgz" - integrity sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q== - dependencies: - jest-diff "^24.3.0" - -"@types/node@^12.7.11": - version "12.20.32" - resolved "https://registry.npmjs.org/@types/node/-/node-12.20.32.tgz" - integrity sha512-KnuOtJLSoqJX/8bkzy15+5DwsPZZBaPjG5XVb8f6Hq8J1ehGHYbGeIiA+m9en2nzclRs0dkfZiNFoGeMZz3Qsg== - -"@types/stack-utils@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz" - integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== - -"@types/yargs-parser@*": - version "20.2.1" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz" - integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== - -"@types/yargs@^13.0.0": - version "13.0.12" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz" - integrity sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ== - dependencies: - "@types/yargs-parser" "*" - -abab@^2.0.0: - version "2.0.5" - resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== - -acorn-globals@^4.1.0: - version "4.3.4" - resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz" - integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== - dependencies: - acorn "^6.0.1" - acorn-walk "^6.0.1" - -acorn-walk@^6.0.1: - version "6.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz" - integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== - -acorn@^5.5.3: - version "5.7.4" - resolved "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -acorn@^6.0.1: - version "6.4.2" - resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -ajv@^6.12.3: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.0.0, ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - 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" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz" - integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.11.0" - resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz" - integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== - -babel-jest@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz" - integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== - dependencies: - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/babel__core" "^7.1.0" - babel-plugin-istanbul "^5.1.0" - babel-preset-jest "^24.9.0" - chalk "^2.4.2" - slash "^2.0.0" - -babel-plugin-istanbul@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz" - integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - find-up "^3.0.0" - istanbul-lib-instrument "^3.3.0" - test-exclude "^5.2.3" - -babel-plugin-jest-hoist@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz" - integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== - dependencies: - "@types/babel__traverse" "^7.0.6" - -babel-preset-jest@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz" - integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== - dependencies: - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^24.9.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.npmjs.org/base/-/base-0.11.2.tgz" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -brace-expansion@^1.1.7: - 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" - -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browser-resolve@^1.11.3: - version "1.11.3" - resolved "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz" - integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== - dependencies: - resolve "1.1.7" - -browserslist@^4.16.6: - version "4.17.4" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.17.4.tgz" - integrity sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ== - dependencies: - caniuse-lite "^1.0.30001265" - electron-to-chromium "^1.3.867" - escalade "^3.1.1" - node-releases "^2.0.0" - picocolors "^1.0.0" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-from@1.x, buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caniuse-lite@^1.0.30001265: - version "1.0.30001265" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz" - integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== - -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.2: - 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" - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0: - 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" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^2.12.1: - version "2.20.3" - resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -convert-source-map@^1.4.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": - version "0.3.8" - resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^1.0.0: - version "1.4.0" - resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz" - integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== - dependencies: - cssom "0.3.x" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -data-urls@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz" - integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== - dependencies: - abab "^2.0.0" - whatwg-mimetype "^2.2.0" - whatwg-url "^7.0.0" - -debug@^2.2.0, debug@^2.3.3: - 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" - -debug@^4.1.0, debug@^4.1.1: - version "4.3.2" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -deep-is@~0.1.3: - version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -detect-newline@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz" - integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= - -diff-sequences@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz" - integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -domexception@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz" - integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== - dependencies: - webidl-conversions "^4.0.2" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -electron-to-chromium@^1.3.867: - version "1.3.867" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.867.tgz" - integrity sha512-WbTXOv7hsLhjJyl7jBfDkioaY++iVVZomZ4dU6TMe/SzucV6mUAs2VZn/AehBwuZMiNEQDaPuTGn22YK5o+aDw== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.19.1: - version "1.19.1" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz" - integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-symbols "^1.0.2" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.1" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.1" - is-string "^1.0.7" - is-weakref "^1.0.1" - object-inspect "^1.11.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escodegen@^1.9.1: - version "1.14.3" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -estraverse@^4.2.0: - version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expect@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz" - integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== - dependencies: - "@jest/types" "^24.9.0" - ansi-styles "^3.2.0" - jest-get-type "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-regex-util "^24.9.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: - 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== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.2: - 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@^2.0.1: - 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-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: - version "7.2.0" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - 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" - -globals@^11.1.0: - 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@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.8" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== - -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -html-encoding-sniffer@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz" - integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== - dependencies: - whatwg-encoding "^1.0.1" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -iconv-lite@0.4.24: - 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" - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - -invariant@^2.2.4: - version "2.2.4" - resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.2.0: - version "2.7.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== - dependencies: - has "^1.0.3" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - -is-number-object@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz" - integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz" - integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-weakref@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz" - integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ== - dependencies: - call-bind "^1.0.0" - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= - -isarray@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz" - integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== - -istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz" - integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== - dependencies: - "@babel/generator" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" - istanbul-lib-coverage "^2.0.5" - semver "^6.0.0" - -istanbul-lib-report@^2.0.4: - version "2.0.8" - resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz" - integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== - dependencies: - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - supports-color "^6.1.0" - -istanbul-lib-source-maps@^3.0.1: - version "3.0.6" - resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz" - integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - rimraf "^2.6.3" - source-map "^0.6.1" - -istanbul-reports@^2.2.6: - version "2.2.7" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz" - integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== - dependencies: - html-escaper "^2.0.0" - -jest-changed-files@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz" - integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== - dependencies: - "@jest/types" "^24.9.0" - execa "^1.0.0" - throat "^4.0.0" - -jest-cli@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz" - integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== - dependencies: - "@jest/core" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - exit "^0.1.2" - import-local "^2.0.0" - is-ci "^2.0.0" - jest-config "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - prompts "^2.0.1" - realpath-native "^1.1.0" - yargs "^13.3.0" - -jest-config@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz" - integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^24.9.0" - "@jest/types" "^24.9.0" - babel-jest "^24.9.0" - chalk "^2.0.1" - glob "^7.1.1" - jest-environment-jsdom "^24.9.0" - jest-environment-node "^24.9.0" - jest-get-type "^24.9.0" - jest-jasmine2 "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - micromatch "^3.1.10" - pretty-format "^24.9.0" - realpath-native "^1.1.0" - -jest-diff@^24.3.0, jest-diff@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz" - integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== - dependencies: - chalk "^2.0.1" - diff-sequences "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-docblock@^24.3.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz" - integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== - dependencies: - detect-newline "^2.1.0" - -jest-each@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz" - integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== - dependencies: - "@jest/types" "^24.9.0" - chalk "^2.0.1" - jest-get-type "^24.9.0" - jest-util "^24.9.0" - pretty-format "^24.9.0" - -jest-environment-jsdom@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz" - integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== - dependencies: - "@jest/environment" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - jest-util "^24.9.0" - jsdom "^11.5.1" - -jest-environment-node@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz" - integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== - dependencies: - "@jest/environment" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/types" "^24.9.0" - jest-mock "^24.9.0" - jest-util "^24.9.0" - -jest-get-type@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz" - integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== - -jest-haste-map@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz" - integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== - dependencies: - "@jest/types" "^24.9.0" - anymatch "^2.0.0" - fb-watchman "^2.0.0" - graceful-fs "^4.1.15" - invariant "^2.2.4" - jest-serializer "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.9.0" - micromatch "^3.1.10" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^1.2.7" - -jest-jasmine2@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz" - integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - co "^4.6.0" - expect "^24.9.0" - is-generator-fn "^2.0.0" - jest-each "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-runtime "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - pretty-format "^24.9.0" - throat "^4.0.0" - -jest-leak-detector@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz" - integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== - dependencies: - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-matcher-utils@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz" - integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== - dependencies: - chalk "^2.0.1" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - pretty-format "^24.9.0" - -jest-message-util@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz" - integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/stack-utils" "^1.0.1" - chalk "^2.0.1" - micromatch "^3.1.10" - slash "^2.0.0" - stack-utils "^1.0.1" - -jest-mock@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz" - integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== - dependencies: - "@jest/types" "^24.9.0" - -jest-pnp-resolver@^1.2.1: - version "1.2.2" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz" - integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== - -jest-resolve-dependencies@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz" - integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== - dependencies: - "@jest/types" "^24.9.0" - jest-regex-util "^24.3.0" - jest-snapshot "^24.9.0" - -jest-resolve@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz" - integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== - dependencies: - "@jest/types" "^24.9.0" - browser-resolve "^1.11.3" - chalk "^2.0.1" - jest-pnp-resolver "^1.2.1" - realpath-native "^1.1.0" - -jest-runner@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz" - integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== - dependencies: - "@jest/console" "^24.7.1" - "@jest/environment" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - chalk "^2.4.2" - exit "^0.1.2" - graceful-fs "^4.1.15" - jest-config "^24.9.0" - jest-docblock "^24.3.0" - jest-haste-map "^24.9.0" - jest-jasmine2 "^24.9.0" - jest-leak-detector "^24.9.0" - jest-message-util "^24.9.0" - jest-resolve "^24.9.0" - jest-runtime "^24.9.0" - jest-util "^24.9.0" - jest-worker "^24.6.0" - source-map-support "^0.5.6" - throat "^4.0.0" - -jest-runtime@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz" - integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== - dependencies: - "@jest/console" "^24.7.1" - "@jest/environment" "^24.9.0" - "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/yargs" "^13.0.0" - chalk "^2.0.1" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.1.15" - jest-config "^24.9.0" - jest-haste-map "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - jest-regex-util "^24.3.0" - jest-resolve "^24.9.0" - jest-snapshot "^24.9.0" - jest-util "^24.9.0" - jest-validate "^24.9.0" - realpath-native "^1.1.0" - slash "^2.0.0" - strip-bom "^3.0.0" - yargs "^13.3.0" - -jest-serializer@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz" - integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== - -jest-snapshot@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz" - integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^24.9.0" - chalk "^2.0.1" - expect "^24.9.0" - jest-diff "^24.9.0" - jest-get-type "^24.9.0" - jest-matcher-utils "^24.9.0" - jest-message-util "^24.9.0" - jest-resolve "^24.9.0" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - pretty-format "^24.9.0" - semver "^6.2.0" - -jest-util@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz" - integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== - dependencies: - "@jest/console" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/source-map" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - callsites "^3.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.15" - is-ci "^2.0.0" - mkdirp "^0.5.1" - slash "^2.0.0" - source-map "^0.6.0" - -jest-validate@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz" - integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== - dependencies: - "@jest/types" "^24.9.0" - camelcase "^5.3.1" - chalk "^2.0.1" - jest-get-type "^24.9.0" - leven "^3.1.0" - pretty-format "^24.9.0" - -jest-watcher@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz" - integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== - dependencies: - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - "@types/yargs" "^13.0.0" - ansi-escapes "^3.0.0" - chalk "^2.0.1" - jest-util "^24.9.0" - string-length "^2.0.0" - -jest-worker@^24.6.0, jest-worker@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz" - integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== - dependencies: - merge-stream "^2.0.0" - supports-color "^6.1.0" - -jest@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz" - integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== - dependencies: - import-local "^2.0.0" - jest-cli "^24.9.0" - -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -jsdom@^11.5.1: - version "11.12.0" - resolved "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz" - integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== - dependencies: - abab "^2.0.0" - acorn "^5.5.3" - acorn-globals "^4.1.0" - array-equal "^1.0.0" - cssom ">= 0.3.2 < 0.4.0" - cssstyle "^1.0.0" - data-urls "^1.0.0" - domexception "^1.0.1" - escodegen "^1.9.1" - html-encoding-sniffer "^1.0.2" - left-pad "^1.3.0" - nwsapi "^2.0.7" - parse5 "4.0.0" - pn "^1.1.0" - request "^2.87.0" - request-promise-native "^1.0.5" - sax "^1.2.4" - symbol-tree "^3.2.2" - tough-cookie "^2.3.4" - w3c-hr-time "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.3" - whatwg-mimetype "^2.1.0" - whatwg-url "^6.4.1" - ws "^5.2.0" - xml-name-validator "^3.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json5@2.x, json5@^2.1.2: - version "2.2.0" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -left-pad@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz" - integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -lodash.memoize@4.x: - version "4.1.2" - resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - -lodash@^4.17.19: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-error@1.x: - version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.x: - version "1.0.11" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz" - integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= - dependencies: - tmpl "1.0.x" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -mime-db@1.50.0: - version "1.50.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz" - integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.33" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz" - integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== - dependencies: - mime-db "1.50.0" - -minimatch@^3.0.4: - 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" - -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@0.x, mkdirp@^0.5.1: - version "0.5.5" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -nan@^2.12.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= - -node-modules-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz" - integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= - -node-notifier@^5.4.2: - version "5.4.5" - resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz" - integrity sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ== - dependencies: - growly "^1.3.0" - is-wsl "^1.1.0" - semver "^5.5.0" - shellwords "^0.1.1" - which "^1.3.0" - -node-releases@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.0.tgz" - integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA== - -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -nwsapi@^2.0.7: - version "2.2.0" - resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== - -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.11.0, object-inspect@^1.9.0: - version "1.11.0" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz" - integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.getownpropertydescriptors@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz" - integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -p-each-series@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz" - integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= - dependencies: - p-reduce "^1.0.0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-reduce@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz" - integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse5@4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz" - integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-parse@^1.0.6: - version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pirates@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz" - integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== - dependencies: - node-modules-regexp "^1.0.0" - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pn@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz" - integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -pretty-format@^24.9.0: - version "24.9.0" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz" - integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== - dependencies: - "@jest/types" "^24.9.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" - -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -react-is@^16.8.4: - version "16.13.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" - read-pkg "^3.0.0" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -realpath-native@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz" - integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== - dependencies: - util.promisify "^1.0.0" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.5: - version "1.0.9" - resolved "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.87.0: - version "2.88.2" - resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - dependencies: - resolve-from "^3.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - -resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -rimraf@^2.5.4, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5, semver@^5.5.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.5" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz" - integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.5.6: - version "0.5.20" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.0, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.10" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz" - integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - -stack-utils@1.0.2, stack-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" - integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= - -string-length@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz" - integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= - dependencies: - astral-regex "^1.0.0" - strip-ansi "^4.0.0" - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -supports-color@^5.3.0: - 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" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -symbol-tree@^3.2.2: - version "3.2.4" - resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== - -test-exclude@^5.2.3: - version "5.2.3" - resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz" - integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== - dependencies: - glob "^7.1.3" - minimatch "^3.0.4" - read-pkg-up "^4.0.0" - require-main-filename "^2.0.0" - -throat@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz" - integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= - -tmpl@1.0.x: - version "1.0.5" - resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= - dependencies: - punycode "^2.1.0" - -ts-jest@^24.3.0: - version "24.3.0" - resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-24.3.0.tgz" - integrity sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - mkdirp "0.x" - resolve "1.x" - semver "^5.5" - yargs-parser "10.x" - -tslib@^1.8.0, tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslint@^5.20.1: - version "5.20.1" - resolved "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz" - integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -typescript@^3.9.10: - version "3.9.10" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== - -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - which-boxed-primitive "^1.0.2" - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util.promisify@^1.0.0: - version "1.1.1" - resolved "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz" - integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - for-each "^0.3.3" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.1" - -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -w3c-hr-time@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -walker@^1.0.7, walker@~1.0.5: - version "1.0.7" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz" - integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= - dependencies: - makeerror "1.0.x" - -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: - version "1.0.5" - resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - -whatwg-url@^6.4.1: - version "6.5.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz" - integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@^1.2.9, which@^1.3.0: - version "1.3.1" - resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz" - integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - -ws@^5.2.0: - version "5.2.3" - resolved "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz" - integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== - dependencies: - async-limiter "~1.0.0" - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -yargs-parser@10.x: - version "10.1.0" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz" - integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== - dependencies: - camelcase "^4.1.0" - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" From 3f9d72e1559ab4441d0d68fcfd9aac5f81f1f6bd Mon Sep 17 00:00:00 2001 From: Nathan Poirier Date: Wed, 21 Oct 2026 06:42:59 +0200 Subject: [PATCH 2/2] Add javaVersion option (exact JVM emulation) - parse(), parseFile() and stringify() accept a javaVersion option, to work exactly like Properties.load() and store() of that Java version. - New readEntries(), escapeKey() and escapeValue(). java-props/dist/utils is deprecated. - Without options, nothing changes, but parse() and stringify() are about 2x faster. --- .github/workflows/ci.yml | 23 +- .github/workflows/latest-jdks.yml | 4 +- CONTRIBUTING.md | 10 +- README.md | 150 ++++- biome.jsonc | 2 +- package.json | 6 +- src/internal/entry-reader.ts | 116 ++++ src/internal/java-load.ts | 303 +++++++++ src/internal/java-store.ts | 221 +++++++ src/internal/java-version.ts | 65 ++ src/internal/legacy-load.ts | 130 ++++ src/java-props.ts | 206 ++++++- src/node-java-props.ts | 69 ++- src/utils.ts | 196 +----- test/cases/parse-cases.mts | 6 +- test/cases/parse-expected.json | 777 ++++++++++++++++++++++++ test/cases/parse-snapshot.json | 12 + test/cases/stringify-cases.mts | 66 ++ test/cases/stringify-expected.json | 205 +++++++ test/compat/api-snapshot.json | 6 + test/compat/check-esm.mjs | 12 +- test/compat/check-package.mjs | 14 +- test/compat/run-smoke.js | 66 ++ test/compat/types/consumer.ts | 36 +- test/support/arbitraries.mts | 26 + test/support/data.mts | 15 +- test/support/load-result.mts | 16 +- test/unit/java-props.test.ts | 100 ++- test/unit/java-version.test.ts | 38 ++ test/unit/node-java-props.test.ts | 14 + test/unit/parse-cases.test.ts | 45 +- test/unit/properties.test.ts | 21 +- test/unit/read-entries.test.ts | 181 ++++++ test/unit/types.test-d.ts | 76 ++- test/unit/utils.test.ts | 7 + tools/fuzz.mts | 84 +++ tools/gen-expected.mts | 149 +++++ tools/gen-parse-expected.mts | 95 --- tools/java-reference/JavaReference.java | 32 + tools/java-reference/reference.mts | 35 +- 40 files changed, 3221 insertions(+), 414 deletions(-) create mode 100644 src/internal/entry-reader.ts create mode 100644 src/internal/java-load.ts create mode 100644 src/internal/java-store.ts create mode 100644 src/internal/java-version.ts create mode 100644 src/internal/legacy-load.ts create mode 100644 test/cases/stringify-cases.mts create mode 100644 test/cases/stringify-expected.json create mode 100644 test/support/arbitraries.mts create mode 100644 test/unit/java-version.test.ts create mode 100644 test/unit/read-entries.test.ts create mode 100644 tools/fuzz.mts create mode 100644 tools/gen-expected.mts delete mode 100644 tools/gen-parse-expected.mts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e19112a..1d25ec2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: - uses: jdx/mise-action@v5 - run: node test/compat/check-types.mjs ${{ matrix.typescript }} - # Expected results must match real JVMs, for each Properties.load() era (see LOAD_ERAS in test/support/load-result.mts) + # Expected results and the javaVersion emulation must match real JVMs (see LOAD_ERAS in src/internal/java-version.ts) java-reference: name: Java reference runs-on: ubuntu-latest @@ -91,16 +91,13 @@ jobs: - {id: jdk17, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '17'}} - {id: jdk21, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '21'}} - {id: jdk25, uses: actions/setup-java@v6, with: {distribution: zulu, java-version: '25'}} + - run: | + echo "JAVA_REFERENCE_JDKS=8=${{ steps.jdk8.outputs.path }}/bin/java,11.0.4=${{ steps.jdk11_0_4.outputs.path }}/bin/java,\ + 11.0.5=${{ steps.jdk11_0_5.outputs.path }}/bin/java,11=${{ steps.jdk11.outputs.path }}/bin/java,\ + 12=${{ steps.jdk12.outputs.path }}/bin/java,13=${{ steps.jdk13.outputs.path }}/bin/java,\ + 17=${{ steps.jdk17.outputs.path }}/bin/java,21=${{ steps.jdk21.outputs.path }}/bin/java,\ + 25=${{ steps.jdk25.outputs.path }}/bin/java" >> "$GITHUB_ENV" - uses: jdx/mise-action@v5 - - run: node tools/gen-parse-expected.mts --check # no dependency to install - env: - JAVA_REFERENCE_JDKS: >- - 8=${{ steps.jdk8.outputs.path }}/bin/java, - 11.0.4=${{ steps.jdk11_0_4.outputs.path }}/bin/java, - 11.0.5=${{ steps.jdk11_0_5.outputs.path }}/bin/java, - 11=${{ steps.jdk11.outputs.path }}/bin/java, - 12=${{ steps.jdk12.outputs.path }}/bin/java, - 13=${{ steps.jdk13.outputs.path }}/bin/java, - 17=${{ steps.jdk17.outputs.path }}/bin/java, - 21=${{ steps.jdk21.outputs.path }}/bin/java, - 25=${{ steps.jdk25.outputs.path }}/bin/java + - run: pnpm install --frozen-lockfile + - run: pnpm run reference:check + - run: pnpm run reference:fuzz -- --runs 5000 diff --git a/.github/workflows/latest-jdks.yml b/.github/workflows/latest-jdks.yml index 25e6647..58a9ebd 100644 --- a/.github/workflows/latest-jdks.yml +++ b/.github/workflows/latest-jdks.yml @@ -32,4 +32,6 @@ jobs: - uses: actions/setup-java@v6 with: {distribution: zulu, java-version: '25'} - uses: jdx/mise-action@v5 - - run: node tools/gen-parse-expected.mts --check # no dependency to install + - run: pnpm install --frozen-lockfile + - run: pnpm run reference:check + - run: pnpm run reference:fuzz -- --runs 5000 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed0342c..4e74be7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,15 +19,17 @@ pnpm run test:compat # build, then check the package (test/compat/run-smoke.j ## Java reference -Java is the reference: the Java results of the edge cases (`test/cases/parse-expected.json`) and of the fixtures -(`test/fixtures`) are generated and checked by running real JVMs, one or more per behavior era of `Properties.load()`. +Java is the reference: the Java results of the test cases (`test/cases/*-expected.json`, `test/fixtures`) are generated +and checked by running real JVMs, one or more per behavior era of `Properties.load()` (see `src/internal/java-version.ts`). The +fuzzer compares `parse` and `stringify` with `javaVersion` to each JVM, on random inputs. It needs `javac` from a JDK 9+ on the `PATH`, and the JDKs to compare: ```bash # JDKs to use (default: java from PATH): name=/path/to/bin/java,... export JAVA_REFERENCE_JDKS="8=/opt/jdk8/bin/java,11.0.4=/opt/jdk-11.0.4/bin/java,25=/opt/jdk25/bin/java" -pnpm run reference:gen # regenerate -pnpm run reference:check # verify +pnpm run reference:gen # regenerate +pnpm run reference:check # verify +pnpm run reference:fuzz # random inputs (builds first; --runs N, --seed N to replay a failure) ``` A monthly CI job (`.github/workflows/latest-jdks.yml`) also checks the latest GA and early-access JDKs, to catch a diff --git a/README.md b/README.md index dd0da9b..88dd6ec 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,14 @@ [![CI](https://github.com/nathan818fr/node-java-props/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/nathan818fr/node-java-props/actions/workflows/ci.yml) [![license](https://img.shields.io/npm/l/java-props.svg)](LICENSE) -Read and write Java `.properties` files in JavaScript (Node.js and browsers). The parser implements the syntax of -Java's [`Properties.load()`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Properties.html#load(java.io.Reader)). +Read and write Java `.properties` files in JavaScript (Node.js and browsers), with the syntax of Java's +[`Properties.load()`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Properties.html#load(java.io.Reader)), +or exactly the results of a given Java version. - Full syntax: `=`, `:` or whitespace separators, `#` and `!` comments, continuation lines, escapes and `\uXXXX`, LF, CRLF and CR line endings -- [Edge cases](test/cases/parse-cases.mts) checked against real JVMs, from Java 8 to 25 +- With the `javaVersion` option: identical to `Properties.load()` and `store()` of that Java version, checked against + real JVMs (Java 8 to 25) on [edge cases](test/cases/parse-cases.mts) and random inputs - Any key is safe, including `__proto__`; malformed `\uXXXX` escapes throw, like in Java - No dependencies, TypeScript declarations included - Node.js >= 6, browsers (ES2015), CommonJS and ESM, TypeScript >= 2.9 @@ -23,10 +25,13 @@ npm install java-props ## Usage ```javascript -import {parse, parseFile, stringify} from 'java-props'; -// or: const {parse, parseFile, stringify} = require('java-props'); +import {parse, parseFile, readEntries, stringify} from 'java-props'; +// or: const {parse, parseFile, readEntries, stringify} = require('java-props'); ``` +The examples use `javaVersion: 21`, for exactly the results of Java 21 (see [Java versions](#java-versions)). Without +it, java-props uses its legacy parser. + ### Read a file ```properties @@ -42,7 +47,7 @@ key\ with\ spaces = value ``` ```javascript -const props = await parseFile('config.properties'); +const props = await parseFile('config.properties', {javaVersion: 21}); // { // 'app.name': 'My App', // 'app.title': 'Café ☕', @@ -56,55 +61,148 @@ const props = await parseFile('config.properties'); ### Parse a string ```javascript -parse('a = 1\nb : 2'); +parse('a = 1\nb : 2', {javaVersion: 21}); // { a: '1', b: '2' } ``` +### Read entries one by one + +For the order of the entries, duplicate keys, the raw text, or positions: + +```javascript +const reader = readEntries('b = 1\na = 2\nb = 3', {javaVersion: 21, positions: true}); +while (reader.next()) { + console.log(reader.key, reader.value, reader.startLine); +} +// b 1 1 +// a 2 2 +// b 3 3 +``` + ### Stringify ```javascript -stringify({'key with spaces': 'Café ☕', 'line.break': 'a\nb'}); +stringify({'key with spaces': 'Café ☕', 'line.break': 'a\nb'}, {javaVersion: 21}); ``` ```properties -key\ with\ spaces: Caf\u00E9 \u2615 -line.break: a\nb +#Thu Jan 15 14:04:05 CET 2026 +key\ with\ spaces=Café ☕ +line.break=a\nb ``` +Like `Properties.store(Writer)`, non-ASCII characters are written as-is (save it as UTF-8). With `escapeUnicode: true`, +they are written as `\uXXXX`, like `store(OutputStream)`. + ## API -### `parse(str)` +### `parse(str, options?)` Parses a .properties string and returns its properties, as an object with a `null` prototype (any key is safe, including `__proto__`). Throws an `Error` on malformed `\uXXXX` escapes (where Java throws an -`IllegalArgumentException`). +`IllegalArgumentException`), and a `TypeError` or a `RangeError` on an invalid `javaVersion`. + +| Option | Default | Description | +| ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `javaVersion` | legacy parser | Parse exactly like `Properties.load()` of this [Java version](#java-versions): `21`, `'11.0.4'`, `'1.8.0_392'`, `'latest'`... A number is the cheapest form | -### `parseFile(path, encoding?)` +### `parseFile(path, encoding?, options?)`, `parseFile(path, options?)` _Node.js only._ Reads and parses a .properties file, and returns a `Promise` of its properties. `path` is a file path -(`string`, `Buffer` or `URL`) or a file descriptor. `encoding` defaults to `'utf8'`; use `'latin1'` for files written by -Java's `Properties.store(OutputStream)`. +(`string`, `Buffer` or `URL`) or a file descriptor. + +| Option | Default | Description | +| ------------- | -------- | ---------------------------------------------------------------------------------------------------------- | +| `javaVersion` | | See `parse` | +| `encoding` | `'utf8'` | File encoding (also the second argument): `'latin1'` for files written by `Properties.store(OutputStream)` | -### `stringify(props)` +### `readEntries(str, options?)` + +Reads the entries of a .properties string one at a time, in order (duplicate keys included). Nothing is decoded until +it is accessed, so reading only some entries, or only their raw text, is fast. + +```javascript +const reader = readEntries(str, {javaVersion: 21, positions: true}); +while (reader.next()) { + reader.key; // decoded key (throws an Error on malformed escapes, like parse) + reader.value; // decoded value + reader.rawKey; // key with its escape sequences + reader.rawValue; // value with its escape sequences + reader.line; // logical line (continuation lines joined) + reader.start; // with `positions`: offsets of the entry in `str` (start, end), + reader.end; // and line and column of its start (from 1) + reader.startLine; + reader.startColumn; +} +``` + +| Option | Default | Description | +| ------------- | ------- | ----------------------------------------------------- | +| `javaVersion` | | See `parse` | +| `positions` | `false` | Compute `start`, `end`, `startLine` and `startColumn` | + +### `stringify(props, options?)` Converts an object of string values (own enumerable properties only) to a .properties string, which Java reads back as -the same keys and values. +the same keys and values. With options, throws a `TypeError` if a value is not a string. + +| Option | Default | With `javaVersion` | Description | +| --------------------- | ------------- | ---------------------------- | ---------------------------------------------------------------------------- | +| `javaVersion` | | | Write like `Properties.store(Writer)` of this Java version (see `parse`) | +| `escapeUnicode` | `true` | `false` | Escape non-ASCII chars as `\uXXXX`, like `store(OutputStream)` | +| `separator` | `': '` | `'='` | `'='`, `':'`, `' '`, `': '` or `' = '` | +| `comments` | | | Comments written first, like the `comments` argument of `store()` | +| `date` | `false` | `true` | Date comment: `true` (now), a `Date`, or a string (written as a comment) | +| `timeZone` | system | system | Time zone of the date (IANA name, `'UTC'` or a fixed offset like `'+02:00'`) | +| `order` | `'insertion'` | `'sorted'` | `'insertion'` (JavaScript order: integer-like keys first) or `'sorted'` | +| `lineSeparator` | `'\n'` | system (`'\r\n'` on Windows) | `'\n'`, `'\r\n'` or `'\r'` | +| `escapeValueSpecials` | `false` | `true` | Also escape `=`, `:`, `#` and `!` in values | + +Dates are written like Java's `Date.toString()`. Time zone names come from `Intl`, which doesn't always use Java's +names: the output is exact for `'UTC'` and fixed offsets, and a `date` string makes it reproducible. + +### `escapeKey(key, options?)`, `escapeValue(value, options?)` + +Escape a key or a value, like `stringify`. + +| Option | Default | With `javaVersion` | Description | +| --------------------- | ------- | ------------------ | -------------------------------------------------------------------------- | +| `javaVersion` | | | Escape like `Properties.store(Writer)` of this Java version (see `parse`) | +| `escapeUnicode` | `true` | `false` | Escape non-ASCII chars as `\uXXXX`, like `store(OutputStream)` | +| `escapeValueSpecials` | `false` | `true` | `escapeValue`: also escape `=`, `:`, `#` and `!` (`escapeKey` always does) |
-Low-level functions (java-props/dist/utils) +Deprecated APIs -The building blocks of `parse` and `stringify`, for finer parsing (e.g. to keep the line order or the raw key/value -text): +The default export, and the functions of `java-props/dist/utils` (which keep the original behavior, without +`javaVersion`), are deprecated. Their replacements: -- `rawParse(str, consumeFn)`: calls `consumeFn({line, sepStart, valueStart})` for each logical line - (the key is `line.substring(0, sepStart)` and the value `line.substring(valueStart)`, both still escaped). -- `rawParseLine(line)`: the key/value boundaries of one logical line. -- `new LineReader(str).readLine()`: the next logical line, or `undefined` at the end. -- `decodeLine(str)` / `encodeLine(str, isKey?)`: decodes / escapes a key or a value. +| Deprecated | Replacement | +| ------------------------------------------------------ | ---------------------------------------------------------- | +| Default export (`javaProps.default`) | Named exports (`parse`, `parseFile`, `stringify`) | +| `rawParse(str, ({line, sepStart, valueStart}) => ...)` | `readEntries(str)`: `reader.line`, `rawKey` and `rawValue` | +| `new LineReader(str).readLine()` | `readEntries(str)`: `reader.next()` and `reader.line` | +| `rawParseLine(line)` | `reader.rawKey`, `reader.rawValue` | +| `decodeLine(str)`, `convertLine(str)` | `reader.key`, `reader.value` | +| `encodeLine(str)`, `encodeLine(str, true)` | `escapeValue(str)`, `escapeKey(str)` |
-The default export (`javaProps.default`) and `convertLine` (alias of `decodeLine`) are deprecated. +## Java versions + +`Properties.load()` changed in a few Java versions, on unusual inputs only: some combinations of backslashes and +comments, and truncated `\uXXXX` escapes. The `javaVersion` option picks the behavior: + +| `javaVersion` | Behavior | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| not set | Legacy: the original java-props parser, close to Java 8 | +| `6` to `8` | Java 6 to 8 | +| `9`, `10`, `12`, `'11.0.0'` to `'11.0.4'` | Changed in Java 9: comments are skipped faster, which changed what some malformed lines read | +| `11` (`'11.0.5'` and later) | Changed in Java 11.0.5, not in 12: [a rewritten line reader](https://bugs.openjdk.org/browse/JDK-8224202) | +| `13` and later, `'latest'` | Changed in Java 13: [a truncated `\uXXXX` at the end of a line throws](https://bugs.openjdk.org/browse/JDK-8224240) | + +A major version alone means its latest update. For `stringify`, `javaVersion` only sets the defaults of the options, +the same for every version. ## Contributing diff --git a/biome.jsonc b/biome.jsonc index d80030b..868d3de 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -3,7 +3,7 @@ "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { // Also skips git-ignored files. Generated files keep their JSON.stringify formatting. - "includes": ["**", "!test/**/*-snapshot.json", "!test/cases/parse-expected.json"] + "includes": ["**", "!test/**/*-snapshot.json", "!test/cases/*-expected.json"] }, "formatter": { "indentStyle": "space", diff --git a/package.json b/package.json index a6986c5..ecfacbe 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "files": [ "dist/**" ], - "type": "commonjs", "main": "dist/node-java-props.js", "browser": "dist/java-props.js", "sideEffects": false, @@ -31,8 +30,9 @@ "test:watch": "vitest", "pretest:compat": "pnpm run build", "test:compat": "node test/compat/run-smoke.js && node test/compat/check-esm.mjs && node test/compat/check-package.mjs", - "reference:gen": "node tools/gen-parse-expected.mts", - "reference:check": "node tools/gen-parse-expected.mts --check" + "reference:gen": "node tools/gen-expected.mts", + "reference:check": "node tools/gen-expected.mts --check", + "reference:fuzz": "pnpm run build && node tools/fuzz.mts" }, "devDependencies": { "@types/node": "^26.6.5", diff --git a/src/internal/entry-reader.ts b/src/internal/entry-reader.ts new file mode 100644 index 0000000..a0e744a --- /dev/null +++ b/src/internal/entry-reader.ts @@ -0,0 +1,116 @@ +// Implementation of the EntryReader returned by readEntries() (see java-props.ts for the documented interface). +import {JavaLineReader, javaDecode, splitLine} from './java-load'; +import {LoadEra} from './java-version'; +import {LegacyLineReader, legacyDecode} from './legacy-load'; + +export class EntryReaderImpl { + private current: string | undefined; // logical line of the current entry + private sepStart = 0; + private valueStart = 0; + private decodedKey: string | undefined; + private decodedValue: string | undefined; + private readonly str: string; + private readonly era: LoadEra | undefined; + private readonly java: JavaLineReader | undefined; + private readonly legacy: LegacyLineReader | undefined; + private readonly track: boolean; + // Line counting for startLine/startColumn, done lazily up to the entries' starts (always forward) + private counted = 0; + private countedLines = 1; + private countedLineStart = 0; + private countedAfterCR = false; + + public constructor(str: string, era: LoadEra | undefined, track: boolean) { + this.str = str; + this.era = era; + this.track = track; + if (era === undefined) { + this.legacy = new LegacyLineReader(str, track); + } else { + this.java = new JavaLineReader(str, era, track); + } + } + + public next(): boolean { + this.decodedKey = undefined; + this.decodedValue = undefined; + this.current = this.java ? this.java.readLine() : (this.legacy as LegacyLineReader).readLine(); + if (this.current === undefined) return false; + const split = splitLine(this.current); + this.sepStart = split.sepStart; + this.valueStart = split.valueStart; + return true; + } + + public get line(): string { + return this.current as string; + } + + public get rawKey(): string { + return (this.current === undefined ? undefined : this.current.substring(0, this.sepStart)) as string; + } + + public get rawValue(): string { + return (this.current === undefined ? undefined : this.current.substring(this.valueStart)) as string; + } + + public get key(): string { + if (this.decodedKey === undefined && this.current !== undefined) { + // Java can read past the key: into the rest of the line + this.decodedKey = this.decode(this.rawKey, this.sepStart); + } + return this.decodedKey as string; + } + + public get value(): string { + if (this.decodedValue === undefined && this.current !== undefined) { + this.decodedValue = this.decode(this.rawValue, this.current.length); + } + return this.decodedValue as string; + } + + public get start(): number { + return this.position(this.java ? this.java.start : (this.legacy as LegacyLineReader).start); + } + + public get end(): number { + return this.position(this.java ? this.java.end : (this.legacy as LegacyLineReader).end); + } + + public get startLine(): number { + return this.position(this.countLines() ? this.countedLines : 0); + } + + public get startColumn(): number { + return this.position(this.countLines() ? this.start - this.countedLineStart + 1 : 0); + } + + private position(value: number): number { + return (this.track && this.current !== undefined ? value : undefined) as number; + } + + // Counts the line terminators (CR, LF, CRLF) up to the start of the current entry + private countLines(): boolean { + if (!this.track || this.current === undefined) return false; + const str = this.str; + const start = this.start; + for (let i = this.counted; i < start; i++) { + const c = str[i]; + if (c === '\r' || (c === '\n' && !this.countedAfterCR)) this.countedLines++; + if (c === '\r' || c === '\n') this.countedLineStart = i + 1; + this.countedAfterCR = c === '\r'; + } + if (start > this.counted) this.counted = start; + return true; + } + + // Decodes a key or a value, followed in the line by what starts at `followedFrom`. In Java mode, decoding can also + // read past the line: leftovers of Java's line buffer (still the one of this entry). It only reads past `raw` after + // a backslash. + private decode(raw: string, followedFrom: number): string { + if (!this.java) return legacyDecode(raw); + const followedBy = + raw.indexOf('\\') < 0 ? '' : (this.current as string).substring(followedFrom) + this.java.tail(); + return javaDecode(raw, followedBy, this.era as LoadEra); + } +} diff --git a/src/internal/java-load.ts b/src/internal/java-load.ts new file mode 100644 index 0000000..db25896 --- /dev/null +++ b/src/internal/java-load.ts @@ -0,0 +1,303 @@ +import {LoadEra} from './java-version'; + +// Exact ports of Properties.LineReader.readLine() and Properties.loadConvert(), for each load era. + +/** + * Properties.LineReader.readLine() of a load era. Like Java, it keeps a single line buffer: lines overwrite the start + * of it, so it can hold stale content of longer previous lines after the current one (or '\0' where nothing was ever + * written), that Java's decoding reads in some malformed inputs (see {@link tail}). + */ +export class JavaLineReader { + public pos = 0; + /** With `track`: offset of the first char of the last line (of its continuation backslash, if empty) */ + public start = -1; + /** With `track`: offset of the end of the last line (its line terminator, or the end of the input) */ + public end = -1; + // Java's line buffer, as what each line wrote to it, from the longest to the last one: as lines overwrite its start, + // only longer previous lines still show after a line + private written: string[] = []; + private text = ''; // the last line + private limit = 0; // end of the current 8192-char read chunk (Java reads its input by chunks) + private readonly rewritten: boolean; // JDK-8224202 (JDK 11.0.5+ and 13+) + private readonly str: string; + private readonly era: LoadEra; + private readonly track: boolean; + + public constructor(str: string, era: LoadEra, track: boolean) { + this.str = str; + this.era = era; + this.track = track; + this.rewritten = era === 'jdk11' || era === 'jdk13'; + } + + /** The next logical line, or `undefined` at the end of the input */ + public readLine(): string | undefined { + return this.rewritten ? this.readLine11() : this.readLine8(); + } + + /** + * What Java's decoding can read past the last line: the next 5 chars of the buffer when one of the last 5 chars of + * the line is a backslash (`\` + `uXXXX`), else nothing. It stops at the end of what was ever written: there, Java + * reads a '\0', which stops any read. + */ + public tail(): string { + const len = this.text.length; + if (this.text.lastIndexOf('\\') < Math.max(0, len - 5)) return ''; + let out = ''; + let i = this.written.length - 1; + for (let at = len; at < len + 5; at++) { + while (i >= 0 && this.written[i].length <= at) i--; // the last line that wrote at `at` + if (i < 0) break; + out += this.written[i][at]; + } + return out; + } + + // A line wrote `written` to the buffer + private write(written: string): void { + const lines = this.written; + while (lines.length > 0 && lines[lines.length - 1].length <= written.length) lines.pop(); + if (written !== '') lines.push(written); + } + + // The line is the `len` first chars of what it wrote to the buffer (the last one can be a removed backslash) + private found(written: string, len: number): string { + this.write(written); + this.text = len === written.length ? written : written.substring(0, len); + return this.text; + } + + // Like Reader.read() on a StringReader: false at the end of the input + private fill(): boolean { + if (this.pos >= this.str.length) return false; + this.limit = Math.min(this.pos + 8192, this.str.length); + return true; + } + + // JDK 6 to 12, and 11.0.0 to 11.0.4 + private readLine8(): string | undefined { + const str = this.str; + let len = 0; + let skipWhiteSpace = true; + let isCommentLine = false; + let isNewLine = true; + let appendedLineBegin = false; + let precedingBackslash = false; + let skipLF = false; + // What the line wrote to the buffer, as slices of the input: text + str.substring(segStart, segEnd). Its `len` + // first chars are the line, then a removed continuation backslash can follow (until overwritten). + let text = ''; + let segStart = 0; + let segEnd = 0; + while (true) { + if (this.pos >= this.limit && !this.fill()) { + if (len === 0 || isCommentLine) return undefined; + this.end = str.length; + return this.found(text + str.substring(segStart, segEnd), precedingBackslash ? len - 1 : len); + } + let c = str[this.pos++]; + if (skipLF) { + skipLF = false; + if (c === '\n') continue; + } + if (skipWhiteSpace) { + if (c === ' ' || c === '\t' || c === '\f') continue; + if (!appendedLineBegin && (c === '\r' || c === '\n')) continue; + skipWhiteSpace = false; + appendedLineBegin = false; + } + if (isNewLine) { + isNewLine = false; + if (c === '#' || c === '!') { + isCommentLine = true; + if (this.era === 'jdk8') continue; + // JDK 9+: skip up to a line break or a backslash (in the current chunk), then go on with it + while (this.pos < this.limit) { + c = str[this.pos++]; + if (c === '\n' || c === '\r' || c === '\\') break; + } + } + } + if (c !== '\n' && c !== '\r') { + // Comment lines are written to the buffer too, and their backslashes count + if (this.track && len === 0 && !isCommentLine) this.start = this.pos - 1; + if (this.pos - 1 !== segEnd) { + text += str.substring(segStart, segStart + len - text.length); // (without a removed backslash) + segStart = this.pos - 1; + } + segEnd = this.pos; + len++; + precedingBackslash = c === '\\' ? !precedingBackslash : false; + } else { + if (isCommentLine || len === 0) { + this.write(text + str.substring(segStart, segEnd)); + isCommentLine = false; + isNewLine = true; + skipWhiteSpace = true; + len = 0; + text = ''; + segStart = segEnd = 0; + continue; + } + this.end = this.pos - 1; + if (this.pos >= this.limit && !this.fill()) { + return this.found(text + str.substring(segStart, segEnd), precedingBackslash ? len - 1 : len); + } + if (!precedingBackslash) return this.found(text + str.substring(segStart, segEnd), len); + len -= 1; + skipWhiteSpace = true; + appendedLineBegin = true; + precedingBackslash = false; + if (c === '\r') skipLF = true; + } + } + } + + // JDK 11.0.5+ and 13+ (JDK-8224202) + private readLine11(): string | undefined { + const str = this.str; + let len = 0; + let skipWhiteSpace = true; + let appendedLineBegin = false; + let precedingBackslash = false; + let text = ''; + let segStart = 0; + let segEnd = 0; + while (true) { + if (this.pos >= str.length) { + if (len === 0) return undefined; + this.end = str.length; + return this.found(text + str.substring(segStart, segEnd), precedingBackslash ? len - 1 : len); + } + let c = str[this.pos++]; + if (skipWhiteSpace) { + if (c === ' ' || c === '\t' || c === '\f') continue; + if (!appendedLineBegin && (c === '\r' || c === '\n')) continue; + skipWhiteSpace = false; + appendedLineBegin = false; + } + if (len === 0 && (c === '#' || c === '!')) { + // Comment: skipped up to the end of the line, without touching the buffer + do { + if (this.pos >= str.length) return undefined; + c = str[this.pos++]; + } while (c !== '\r' && c !== '\n'); + skipWhiteSpace = true; + continue; + } + if (c !== '\n' && c !== '\r') { + if (this.track && len === 0) this.start = this.pos - 1; + if (this.pos - 1 !== segEnd) { + text += str.substring(segStart, segStart + len - text.length); // (without a removed backslash) + segStart = this.pos - 1; + } + segEnd = this.pos; + len++; + precedingBackslash = c === '\\' ? !precedingBackslash : false; + } else { + if (len === 0) { + skipWhiteSpace = true; + continue; + } + this.end = this.pos - 1; + if (this.pos >= str.length) { + return this.found(text + str.substring(segStart, segEnd), precedingBackslash ? len - 1 : len); + } + if (!precedingBackslash) return this.found(text + str.substring(segStart, segEnd), len); + len -= 1; + skipWhiteSpace = true; + appendedLineBegin = true; + precedingBackslash = false; + if (c === '\r' && str[this.pos] === '\n') this.pos++; + } + } + } +} + +/** + * Properties.loadConvert() of a load era, on `str`. Like Java, an escape at the end of `str` reads on into `followedBy`, + * then '\0' (JDK 6 to 12 and 11.0.5+; JDK 13+ checks the bounds of `\uXXXX`). + */ +export function javaDecode(str: string, followedBy: string, era: LoadEra): string { + const end = str.length; + let off = str.indexOf('\\'); + // No escape: nothing to decode (Java only reads past the end after a backslash). JDK 11.0.5+ also returns the + // string as-is when its first backslash is the last char. + if (off < 0 || ((era === 'jdk11' || era === 'jdk13') && off === end - 1)) return str; + const after = followedBy + '\0'; // a '\0' always stops a read before the end of `after` + const at = (i: number) => (i < end ? str[i] : after[i - end]); + let out = ''; + let start = 0; // start of the text to copy as-is + while (off >= 0 && off < end) { + out += str.substring(start, off); + off++; // backslash + let c = at(off++); + if (c === 'u') { + if (era === 'jdk13' && off > end - 4) throw malformedUnicode(); + let value = 0; + for (let i = 0; i < 4; i++) { + const digit = parseInt(at(off++), 16); + if (Number.isNaN(digit)) throw malformedUnicode(); + value = (value << 4) + digit; + } + c = String.fromCharCode(value); + } else if (c === 't') { + c = '\t'; + } else if (c === 'r') { + c = '\r'; + } else if (c === 'n') { + c = '\n'; + } else if (c === 'f') { + c = '\f'; + } + out += c; + start = off; + off = start < end ? str.indexOf('\\', start) : -1; + } + return start < end ? out + str.substring(start) : out; +} + +function malformedUnicode(): Error { + return new Error('Malformed \\uxxxx encoding.'); +} + +/** Key/value boundaries of a logical line, like Properties.load0() (the same in every era, and in the original behavior) */ +export function splitLine(line: string): {line: string; sepStart: number; valueStart: number} { + let keyLen = 0; + let valueStart = line.length; + let hasSep = false; + let backslash = false; + + const lineLen = line.length; + let pos = 0; + for (; pos < lineLen; pos++) { + const c = line[pos]; + if ((c === '=' || c === ':') && !backslash) { + valueStart = keyLen + 1; + hasSep = true; + break; + } else if ((c === ' ' || c === '\t' || c === '\f') && !backslash) { + valueStart = keyLen + 1; + break; + } + if (c === '\\') { + backslash = !backslash; + } else { + backslash = false; + } + keyLen++; + } + while (valueStart < lineLen) { + const c = line[valueStart]; + if (c !== ' ' && c !== '\t' && c !== '\f') { + if (!hasSep && (c === '=' || c === ':')) { + hasSep = true; + } else { + break; + } + } + valueStart++; + } + + return {line, sepStart: keyLen, valueStart}; +} diff --git a/src/internal/java-store.ts b/src/internal/java-store.ts new file mode 100644 index 0000000..8edb510 --- /dev/null +++ b/src/internal/java-store.ts @@ -0,0 +1,221 @@ +// What Properties.store() writes: escaped keys and values, comments, the date comment, and the line separator. +import {EscapeOptions, StringifyOptions} from '../java-props'; +import {loadEra} from './java-version'; + +/** The settings of an escapeKey()/escapeValue() output, see {@link escapeSettings} */ +export interface EscapeSettings { + escapeValueSpecials: boolean; + escapeUnicode: boolean; +} + +/** The settings of a stringify() output, see {@link storeSettings} */ +export interface StoreSettings extends EscapeSettings { + separator: string; + lineSeparator: string; + comments: string | undefined; + date: boolean | Date | string; + timeZone: string | undefined; + order: 'insertion' | 'sorted'; +} + +/** + * Resolves the options of escapeKey()/escapeValue(): each option, else its default: like `Properties.store(Writer)` + * with `javaVersion`, else like the original output of java-props. + * + * @throws TypeError | RangeError if `options.javaVersion` is invalid + */ +export function escapeSettings(options: EscapeOptions = {}): EscapeSettings { + const store = isStore(options); + return { + escapeValueSpecials: pick(options.escapeValueSpecials, store, true, false), + escapeUnicode: pick(options.escapeUnicode, store, false, true), + }; +} + +/** + * Resolves the options of stringify(), like {@link escapeSettings}. + * + * @throws TypeError | RangeError if `options.javaVersion` is invalid + */ +export function storeSettings(options: StringifyOptions = {}): StoreSettings { + const store = isStore(options); + const {escapeValueSpecials, escapeUnicode} = escapeSettings(options); + return { + escapeValueSpecials, + escapeUnicode, + separator: pick(options.separator, store, '=', ': '), + lineSeparator: pick(options.lineSeparator, store, systemLineSeparator(), '\n'), + comments: options.comments, + date: pick(options.date, store, true, false), + timeZone: options.timeZone, + order: pick<'insertion' | 'sorted'>(options.order, store, 'sorted', 'insertion'), + }; +} + +// With `javaVersion`, the defaults are like Properties.store() (every version writes the same: only validated) +function isStore(options: EscapeOptions): boolean { + if (options.javaVersion === undefined) return false; + loadEra(options.javaVersion); + return true; +} + +function pick(value: T | undefined, store: boolean, storeDefault: T, originalDefault: T): T { + return value !== undefined ? value : store ? storeDefault : originalDefault; +} + +/** `System.lineSeparator()`: `'\r\n'` on Windows, `'\n'` elsewhere (and in browsers) */ +export function systemLineSeparator(): string { + return typeof process !== 'undefined' && process.platform === 'win32' ? '\r\n' : '\n'; +} + +/** `Properties.writeComments()`: comment lines, `\uXXXX` escapes above U+00FF */ +export function writeComments(comments: string, nl: string): string { + let out = '#'; + let last = 0; + for (let current = 0; current < comments.length; current++) { + const code = comments.charCodeAt(current); + if (code > 0xff || code === 0x0a || code === 0x0d) { + out += comments.substring(last, current); + if (code > 0xff) { + out += '\\u' + hex4(code); + } else { + out += nl; + if (code === 0x0d && current !== comments.length - 1 && comments[current + 1] === '\n') { + current++; + } + const next = comments[current + 1]; + if (current === comments.length - 1 || (next !== '#' && next !== '!')) { + out += '#'; + } + } + last = current + 1; + } + } + return out + comments.substring(last) + nl; +} + +/** + * The date comment of `Properties.store()`: `Date.toString()` of a date (`true` = now), or a string written like the + * `java.properties.date` system property of Java 18+ (an empty string means now, like in Java). + * + * @throws RangeError if `timeZone` is invalid, or is not `'UTC'` nor a fixed offset without `Intl` support + */ +export function dateComment(date: true | Date | string, timeZone: string | undefined, nl: string): string { + if (typeof date === 'string' && date !== '') return writeComments(date, nl); + return '#' + javaDateString(date instanceof Date ? date : new Date(), timeZone) + nl; +} + +const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; +const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; +const ZONE_NAME = /^[A-Z]{2,5}$/; + +// Date.toString() of Java: "EEE MMM dd HH:mm:ss zzz yyyy" +function javaDateString(date: Date, timeZone: string | undefined): string { + const {offset, zone} = zoneAt(date, timeZone); + const d = new Date(Math.floor(date.getTime() / 1000) * 1000 + offset * 60000); + return ( + `${DAYS[d.getUTCDay()]} ${MONTHS[d.getUTCMonth()]} ${pad2(d.getUTCDate())} ` + + `${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}:${pad2(d.getUTCSeconds())} ${zone} ${d.getUTCFullYear()}` + ); +} + +// Offset (in minutes) and short name of a time zone at a date +function zoneAt(date: Date, timeZone: string | undefined): {offset: number; zone: string} { + const fixed = timeZone !== undefined ? /^([+-])(\d\d):(\d\d)$/.exec(timeZone) : null; + if (fixed) { + const offset = (fixed[1] === '-' ? -1 : 1) * (Number(fixed[2]) * 60 + Number(fixed[3])); + return {offset, zone: `GMT${fixed[1]}${fixed[2]}:${fixed[3]}`}; + } + if (timeZone === 'UTC') return {offset: 0, zone: 'UTC'}; + const formatToParts = typeof Intl !== 'undefined' && Intl.DateTimeFormat.prototype.formatToParts; + if (!formatToParts) { + if (timeZone !== undefined) throw new RangeError(`Unsupported timeZone ${timeZone}: no Intl support`); + const offset = -date.getTimezoneOffset(); + return {offset, zone: gmtName(offset)}; + } + const parts = (locale: string) => + new Intl.DateTimeFormat(locale, { + timeZone, + hour12: false, + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }).formatToParts(date); + const us = parts('en-US'); + const value = (p: Intl.DateTimeFormatPart[], type: string) => + (p.find((x) => x.type === type) as Intl.DateTimeFormatPart).value; + const field = (type: string) => Number(value(us, type)); + const local = Date.UTC( + field('year'), + field('month') - 1, + field('day'), + field('hour') % 24, + field('minute'), + field('second') + ); + const offset = Math.round((local - Math.floor(date.getTime() / 1000) * 1000) / 60000); + // Java's names are not available: en-US names (EST...), then en-GB ones (CET...), then Java's "GMT+01:00" format + let zone = value(us, 'timeZoneName'); + if (!ZONE_NAME.test(zone)) zone = value(parts('en-GB'), 'timeZoneName'); + return {offset, zone: ZONE_NAME.test(zone) ? zone : gmtName(offset)}; +} + +// Java's name of a custom time zone ("GMT+01:00") +function gmtName(offset: number): string { + const abs = Math.abs(offset); + return `GMT${offset < 0 ? '-' : '+'}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`; +} + +function pad2(n: number): string { + return n < 10 ? '0' + n : String(n); +} + +function hex4(code: number): string { + const hex = code.toString(16).toUpperCase(); + return '0000'.substring(hex.length) + hex; +} + +/** `Properties.saveConvert()`, with the escaping of `=:#!` as an option (Java always escapes them) */ +export function saveConvert( + str: string, + escapeSpace: boolean, + escapeSpecials: boolean, + escapeUnicode: boolean +): string { + let out = ''; + let start = 0; // start of the text to copy as-is + for (let i = 0; i < str.length; i++) { + const c = str[i]; + const code = str.charCodeAt(i); + let escaped: string; + if (code > 61 && code < 127) { + if (c !== '\\') continue; + escaped = '\\\\'; + } else if (c === ' ') { + if (i !== 0 && !escapeSpace) continue; + escaped = '\\ '; + } else if (c === '\t') { + escaped = '\\t'; + } else if (c === '\n') { + escaped = '\\n'; + } else if (c === '\r') { + escaped = '\\r'; + } else if (c === '\f') { + escaped = '\\f'; + } else if (c === '=' || c === ':' || c === '#' || c === '!') { + if (!escapeSpecials) continue; + escaped = '\\' + c; + } else if ((code < 0x20 || code > 0x7e) && escapeUnicode) { + escaped = '\\u' + hex4(code); + } else { + continue; + } + out += str.substring(start, i) + escaped; + start = i + 1; + } + return start === 0 ? str : out + str.substring(start); +} diff --git a/src/internal/java-version.ts b/src/internal/java-version.ts new file mode 100644 index 0000000..922ade7 --- /dev/null +++ b/src/internal/java-version.ts @@ -0,0 +1,65 @@ +/** + * Behavior eras of Properties.load(), each named after its first JDK: + * - jdk8: the original implementation. + * - jdk9: comments are skipped quickly up to their first backslash, so only the rest of a comment is written to the + * line buffer (and read by later decoding). + * - jdk11: JDK-8224202 "Speed up Properties.load", a rewrite of LineReader that also changed its behavior: `#`/`!` + * after an empty continuation line starts a comment, and nothing leaks from comment lines anymore. + * - jdk13: jdk11 + JDK-8224240: a truncated `\uXXXX` at the end of a line throws, instead of decoding stale content. + */ +export const LOAD_ERAS = ['jdk8', 'jdk9', 'jdk11', 'jdk13'] as const; +export type LoadEra = (typeof LOAD_ERAS)[number]; + +const ACCEPTED = 'a major version (6 or more), a java.version string (e.g. "11.0.4", "1.8.0_392") or "latest"'; + +/** + * Behavior era of Properties.load() for a Java version: a major version (`8`, `17`...), a `java.version` string + * (`'11.0.4'`, `'1.8.0_392'`, `'21.0.2+13-LTS'`...) or `'latest'`. A major version alone means its latest update. + * + * @throws TypeError if `javaVersion` is neither a number nor a string + * @throws RangeError if `javaVersion` is not a valid Java version, or is older than Java 6 + */ +export function loadEra(javaVersion: number | string): LoadEra { + if (javaVersion !== cachedVersion) { + cachedEra = resolveEra(javaVersion); + cachedVersion = javaVersion; + } + return cachedEra; +} + +let cachedVersion: number | string | undefined; // escapeKey/escapeValue resolve it on each call +let cachedEra: LoadEra; + +function resolveEra(javaVersion: number | string): LoadEra { + const [major, update] = parseVersion(javaVersion); + if (major >= 13) return 'jdk13'; + if (major === 11 && update >= 5) return 'jdk11'; // JDK-8224202 was backported to 11.0.5 (not to 12) + if (major >= 9) return 'jdk9'; + if (major >= 6) return 'jdk8'; + throw invalid(javaVersion); +} + +// [major, update] of a Java version. A missing update means the latest one, and 'latest' the latest version. +function parseVersion(javaVersion: number | string): [number, number] { + if (javaVersion === 'latest') return [Infinity, Infinity]; + if (typeof javaVersion === 'number') { + if (!Number.isInteger(javaVersion)) throw invalid(javaVersion); + return [javaVersion, Infinity]; + } + if (typeof javaVersion !== 'string') { + throw new TypeError(`Invalid javaVersion type (${typeof javaVersion}): expected ${ACCEPTED}`); + } + // "11.0.4", "21.0.2+13-LTS", "17-ea" (Java 9+), or "1.8.0_392" (up to Java 8: "1.") + const m = /^(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:\.\d+)*(?:_\d+)?(?:[-+].*)?$/.exec(javaVersion); + if (!m) throw invalid(javaVersion); + const [, first, second, update] = m; + if (first === '1') { + if (second === undefined) throw invalid(javaVersion); + return [Number(second), Infinity]; + } + return [Number(first), update === undefined ? Infinity : Number(update)]; +} + +function invalid(javaVersion: number | string): RangeError { + return new RangeError(`Invalid javaVersion ${JSON.stringify(javaVersion)}: expected ${ACCEPTED}`); +} diff --git a/src/internal/legacy-load.ts b/src/internal/legacy-load.ts new file mode 100644 index 0000000..5e09024 --- /dev/null +++ b/src/internal/legacy-load.ts @@ -0,0 +1,130 @@ +// The original reading of java-props (a port of JDK 8's, with differences kept to not break existing code). + +const UNICODE_PATTERN = /^[0-9a-fA-F]{4}$/; + +/** + * Reads the logical lines of a string, like java-props always did: skips comments, blank lines and leading whitespace, + * and joins continuation lines. With `track`, it also gives the range of each line in the input. + */ +export class LegacyLineReader { + public pos = 0; + /** With `track`: offset of the first char of the last line (of its continuation backslash, if empty) */ + public start = -1; + /** With `track`: offset of the end of the last line (its line terminator, or the end of the input) */ + public end = -1; + private readonly str: string; + private readonly track: boolean; + + public constructor(str: string, track: boolean) { + this.str = str; + this.track = track; + } + + /** The next logical line, or `undefined` at the end of the input */ + public readLine(): string | undefined { + const str = this.str; + let skipWhiteSpace = true; + let commentLine = false; + let newLine = true; + let appendedLineBegin = false; + let backslash = false; + let skipLF = false; + // The line (of `len` chars), as slices of the input: text + str.substring(segStart, segEnd) + let len = 0; + let text = ''; + let segStart = 0; + let segEnd = 0; + while (this.pos < str.length) { + const c = str[this.pos++]; + if (skipLF) { + skipLF = false; + if (c === '\n') continue; + } + if (skipWhiteSpace) { + if (c === ' ' || c === '\t' || c === '\f') continue; + if (!appendedLineBegin && (c === '\r' || c === '\n')) continue; + skipWhiteSpace = false; + appendedLineBegin = false; + } + if (newLine) { + newLine = false; + if (c === '#' || c === '!') { + commentLine = true; + continue; + } + } + if (c !== '\n' && c !== '\r') { + if (this.track && len === 0 && !commentLine) this.start = this.pos - 1; + len++; + if (this.pos - 1 !== segEnd) { + text += str.substring(segStart, segEnd); + segStart = this.pos - 1; + } + segEnd = this.pos; + backslash = c === '\\' ? !backslash : false; + } else { + if (commentLine || len === 0) { + commentLine = false; + newLine = true; + skipWhiteSpace = true; + len = 0; + text = ''; + segStart = segEnd = 0; + continue; + } + if (!backslash) { + this.end = this.pos - 1; + return text + str.substring(segStart, segEnd); + } + len--; + segEnd--; + skipWhiteSpace = true; + appendedLineBegin = true; + backslash = false; + if (c === '\r') skipLF = true; + } + } + if (backslash && len > 0) { + // (`backslash` can be left over from a comment line) + len--; + segEnd--; + } + if (len === 0 || commentLine) return undefined; + this.end = str.length; + return text + str.substring(segStart, segEnd); + } +} + +/** Decodes the escape sequences of a key or a value, like java-props always did */ +export function legacyDecode(line: string): string { + let off = line.indexOf('\\'); + if (off < 0) return line; + let out = ''; + let start = 0; // start of the text to copy as-is + while (off >= 0) { + out += line.substring(start, off); + off++; // backslash + const c = line[off]; + if (c === 'u') { + // Up to 4 chars of the same line (originally: `\\u(.{0,4})`), that must be hex digits + off++; + let end = off; + while (end < off + 4 && end < line.length && !isLineTerminator(line[end])) end++; + const unicode = line.substring(off, end); + if (!UNICODE_PATTERN.test(unicode)) throw new Error('Malformed \\uxxxx encoding.'); + out += String.fromCharCode(parseInt(unicode, 16)); + off = end; + } else if (c !== undefined && !isLineTerminator(c)) { + out += c === 't' ? '\t' : c === 'r' ? '\r' : c === 'n' ? '\n' : c === 'f' ? '\f' : c; + off++; + } // else, the backslash is dropped + start = off; + off = line.indexOf('\\', start); + } + return out + line.substring(start); +} + +// The chars that `.` doesn't match in a regex +function isLineTerminator(c: string): boolean { + return c === '\n' || c === '\r' || c === '\u2028' || c === '\u2029'; +} diff --git a/src/java-props.ts b/src/java-props.ts index 41b1c2a..f6c3961 100644 --- a/src/java-props.ts +++ b/src/java-props.ts @@ -1,58 +1,218 @@ -import {decodeLine, encodeLine, rawParse} from './utils'; +import {EntryReaderImpl} from './internal/entry-reader'; +import {dateComment, escapeSettings, saveConvert, storeSettings, writeComments} from './internal/java-store'; +import {loadEra} from './internal/java-version'; export interface Properties { [key: string]: string; } +export interface ParseOptions { + /** + * Parse exactly like `Properties.load()` of this Java version: a major version (`8`, `17`...), a `java.version` + * string (`'11.0.4'`, `'1.8.0_392'`...) or `'latest'`. Without it, java-props keeps its original behavior, which + * differs from Java on a few unusual inputs. It is resolved once per call: a number is the cheapest form. + */ + javaVersion?: number | string; +} + /** - * Parses a .properties string and returns the result as an object. + * Parses a .properties string. * - * @param str The string to parse as .properties - * @return The result as an object + * @param str The .properties content + * @param options See {@link ParseOptions} + * @return The properties, as an object with a `null` prototype + * @throws Error on malformed `\uXXXX` escapes (where Java throws an `IllegalArgumentException`) + * @throws TypeError | RangeError if `options.javaVersion` is invalid * * @example ```javascript - * const props = javaProps.parse('foo=Hello\nbar=World'); - * console.log(props.foo + ' ' + props.bar); - * // "Hello World" + * parse('foo=Hello\nbar=World'); + * // {foo: 'Hello', bar: 'World'} * ``` */ -export function parse(str: string): Properties { +export function parse(str: string, options?: ParseOptions): Properties { const result: Properties = Object.create(null); - rawParse(str, (res) => { - const key = decodeLine(res.line.substring(0, res.sepStart)); - result[key] = decodeLine(res.line.substring(res.valueStart)); - }); + const reader = readEntries(str, options); + while (reader.next()) { + result[reader.key] = reader.value; + } return result; } +export interface ReadEntriesOptions extends ParseOptions { + /** Also give the position of each entry in the input: see {@link EntryPositions} */ + positions?: boolean; +} + +/** + * Reads the entries of a .properties string, one at a time, in order (duplicate keys included). Its properties are + * computed on first access, and are only valid after {@link EntryReader.next} returned `true`. + */ +export interface EntryReader { + /** Goes to the next entry, and returns `false` at the end of the input */ + next(): boolean; + /** The logical line of the entry (continuation lines joined), with its escape sequences */ + readonly line: string; + /** The key, with its escape sequences */ + readonly rawKey: string; + /** The value, with its escape sequences */ + readonly rawValue: string; + /** The decoded key. Throws an `Error` on malformed `\uXXXX` escapes (where Java throws). */ + readonly key: string; + /** The decoded value. Throws an `Error` on malformed `\uXXXX` escapes (where Java throws). */ + readonly value: string; +} + +/** Position of the current entry in the input (with the `positions` option) */ +export interface EntryPositions { + /** Offset of the entry's first char (or of its continuation backslash, for an empty entry) */ + readonly start: number; + /** Offset of the end of the entry: its line terminator, or the end of the input */ + readonly end: number; + /** Line of `start` (from 1; CR, LF and CRLF end a line, like in Java) */ + readonly startLine: number; + /** Column of `start` (from 1, in UTF-16 code units) */ + readonly startColumn: number; +} + +/** + * Reads the entries of a .properties string one at a time: for order, duplicate keys, raw text, positions, or fast + * partial reads (nothing is decoded until accessed). + * + * @param str The .properties content + * @param options See {@link ReadEntriesOptions} + * @throws TypeError | RangeError if `options.javaVersion` is invalid + * + * @example ```javascript + * const reader = readEntries('a=1\nb=2'); + * while (reader.next()) { + * console.log(reader.key, reader.value); + * } + * ``` + */ +export function readEntries(str: string, options: ReadEntriesOptions & {positions: true}): EntryReader & EntryPositions; +export function readEntries(str: string, options?: ReadEntriesOptions): EntryReader; +export function readEntries(str: string, options?: ReadEntriesOptions): EntryReader { + const era = options && options.javaVersion !== undefined ? loadEra(options.javaVersion) : undefined; + return new EntryReaderImpl(str, era, !!options && !!options.positions); +} + +export interface EscapeOptions { + /** + * Escape like `Properties.store()` of this Java version (same forms as {@link ParseOptions.javaVersion}): it sets + * the defaults of the other options. + */ + javaVersion?: number | string; + /** Also escape `=`, `:`, `#` and `!` in values, like `store()` (default: `false`, or `true` with `javaVersion`) */ + escapeValueSpecials?: boolean; + /** + * Escape the characters outside of printable ASCII as `\uXXXX`, like `store(OutputStream)` (default: `true`, or + * `false` with `javaVersion`, like `store(Writer)`) + */ + escapeUnicode?: boolean; +} + +/** + * Options of {@link stringify}. Without `javaVersion`, they default to the original output of java-props; with it, + * they default to the output of `Properties.store(Writer)`. + */ +export interface StringifyOptions extends EscapeOptions { + /** Separator between keys and values (default: `': '`, or `'='` with `javaVersion`) */ + separator?: '=' | ':' | ' ' | ': ' | ' = '; + /** Comments written at the start, like the `comments` argument of `Properties.store()` */ + comments?: string; + /** + * Date comment, written after `comments` (default: `false`, or `true` with `javaVersion`): `true` (the current date) + * or a `Date`, written like Java's `Date.toString()`; or a string, written as a comment like the + * `java.properties.date` system property of Java 18+. + */ + date?: boolean | Date | string; + /** + * IANA time zone of the date comment (default: the system time zone, like the JVM). Time zone names can differ from + * Java's own names: use a fixed offset (`'+02:00'`), `'UTC'`, or a `date` string for an exact output. + */ + timeZone?: string; + /** Line separator (default: `'\n'`, or the system one with `javaVersion`, like Java: `'\r\n'` on Windows) */ + lineSeparator?: '\n' | '\r\n' | '\r'; + /** + * Order of the properties (default: `'insertion'`, or `'sorted'` with `javaVersion`, like Java 18+). JavaScript + * objects list integer-like keys first, in ascending order. + */ + order?: 'insertion' | 'sorted'; +} + /** - * Converts a JavaScript object (associating keys to string values) to a .properties string. + * Converts an object of string values (its own enumerable properties) to a .properties string, which Java reads back + * as the same keys and values. * - * @param props The JavaScript object to convert - * @return The .properties string corresponding to the given JavaScript object + * @param props The properties + * @param options See {@link StringifyOptions} + * @return The .properties content + * @throws TypeError if a value is not a string, or if `options.javaVersion` has an invalid type + * @throws RangeError if `options.javaVersion` or `options.timeZone` is invalid * * @example ```javascript - * const str = javaProps.stringify({'foo': 'Hello', 'bar': 'World'}); - * console.log(str); - * // "foo: Hello\nbar: World\n" + * stringify({foo: 'Hello', bar: 'World'}); + * // 'foo: Hello\nbar: World\n' * ``` */ -export function stringify(props: Properties): string { +export function stringify(props: Properties, options?: StringifyOptions): string { + const settings = storeSettings(options); + const nl = settings.lineSeparator; let str = ''; + if (settings.comments !== undefined) { + str += writeComments(settings.comments, nl); + } + if (settings.date !== false) { + str += dateComment(settings.date, settings.timeZone, nl); + } + const keys: string[] = []; for (const key in props) { - if (Object.prototype.hasOwnProperty.call(props, key)) { - const value = props[key]; - str += encodeLine(key, true) + ': ' + encodeLine(value) + '\n'; + if (Object.prototype.hasOwnProperty.call(props, key)) keys.push(key); + } + if (settings.order === 'sorted') keys.sort(); + for (const key of keys) { + const value = props[key]; + if (typeof value !== 'string') { + throw new TypeError(`The value of ${JSON.stringify(key)} is not a string (${typeof value})`); } + const escapedKey = saveConvert(key, true, true, settings.escapeUnicode); + const escapedValue = saveConvert(value, false, settings.escapeValueSpecials, settings.escapeUnicode); + str += escapedKey + settings.separator + escapedValue + nl; } return str; } +/** + * Escapes a key: spaces, separators, comment chars, and by default the characters outside of printable ASCII. + * + * @throws TypeError if `key` is not a string, or if `options.javaVersion` has an invalid type + * @throws RangeError if `options.javaVersion` is invalid + */ +export function escapeKey(key: string, options?: EscapeOptions): string { + return escapeText(key, true, options); +} + +/** + * Escapes a value: its leading space, and by default the characters outside of printable ASCII. + * + * @throws TypeError if `value` is not a string, or if `options.javaVersion` has an invalid type + * @throws RangeError if `options.javaVersion` is invalid + */ +export function escapeValue(value: string, options?: EscapeOptions): string { + return escapeText(value, false, options); +} + +function escapeText(str: string, isKey: boolean, options: EscapeOptions | undefined): string { + if (typeof str !== 'string') throw new TypeError(`Expected a string, got ${typeof str}`); + const settings = escapeSettings(options); + return saveConvert(str, isKey, isKey || settings.escapeValueSpecials, settings.escapeUnicode); +} + const _default = { parse, stringify, }; /** - * @deprecated + * @deprecated Use the named exports. */ export default _default; diff --git a/src/node-java-props.ts b/src/node-java-props.ts index 2085833..e57d95b 100644 --- a/src/node-java-props.ts +++ b/src/node-java-props.ts @@ -1,47 +1,60 @@ /// import fs from 'fs'; -import {Properties, parse, stringify} from './java-props'; +import {loadEra} from './internal/java-version'; +import {ParseOptions, Properties, parse, stringify} from './java-props'; export * from './java-props'; +export interface ParseFileOptions extends ParseOptions { + /** File encoding (default: `'utf8'`). Use `'latin1'` for files written by `Properties.store(OutputStream)`. */ + encoding?: string; +} + /** - * Parses a .properties file and returns the result as an object. + * Reads and parses a .properties file. * - * @param path Filename or file descriptor - * @param encoding File encoding (default: utf8) - * @return The result as an object + * @param path A file path, or a file descriptor + * @param encoding File encoding (default: `'utf8'`) + * @param options See {@link ParseOptions} + * @return A promise of the properties (see {@link parse}). It rejects if the file can't be read, if the encoding is + * unknown, or if {@link parse} throws. * * @example ```javascript - * javaProps.parseFile('./foobar.properties').then((props) => { - * console.log(props.foo + ' ' + props.bar); - * // "Hello World" - * }).catch((err) => { - * console.error(err); - * }); - * ``` - * *- or with async/await -* - * ```javascript - * async function fct() { - * try { - * const props = await javaProps.parseFile('./foobar.properties'); - * console.log(props.foo + ' ' + props.bar); - * // "Hello World" - * } catch (err) { - * console.error(err); - * } - * } + * await parseFile('./config.properties'); + * await parseFile('./config.properties', {encoding: 'latin1', javaVersion: 21}); * ``` */ -export function parseFile(path: string | Buffer | URL | number, encoding?: string): Promise { +export function parseFile( + path: string | Buffer | URL | number, + encoding?: string, + options?: ParseOptions +): Promise; +/** + * Reads and parses a .properties file (see the other signature). + * + * @param path A file path, or a file descriptor + * @param options The file encoding, and the options of {@link parse} + */ +export function parseFile(path: string | Buffer | URL | number, options?: ParseFileOptions): Promise; +export function parseFile( + path: string | Buffer | URL | number, + encodingOrOptions?: string | ParseFileOptions, + parseOptions?: ParseOptions +): Promise { + const fileOptions = + typeof encodingOrOptions === 'object' && encodingOrOptions !== null ? encodingOrOptions : undefined; + const encoding = fileOptions ? fileOptions.encoding : (encodingOrOptions as string | undefined); + const options = fileOptions || parseOptions; return new Promise((resolve, reject) => { + // Invalid options reject before reading the file + if (options && options.javaVersion !== undefined) loadEra(options.javaVersion); // `encoding` accepts any string: unknown encodings reject - fs.readFile(path, {encoding: (encoding || 'utf8') as BufferEncoding, flag: 'r'}, (err, data) => { + fs.readFile(path, {encoding: (encoding || 'utf8') as BufferEncoding}, (err, data) => { if (err) { return reject(err); } try { - const res = parse(data); - return resolve(res); + return resolve(parse(data, options)); } catch (err) { return reject(err); } @@ -55,6 +68,6 @@ const _default = { stringify, }; /** - * @deprecated + * @deprecated Use the named exports. */ export default _default; diff --git a/src/utils.ts b/src/utils.ts index cfebec8..12a86fd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,21 +1,14 @@ /* - * Port of the JDK 8 implementation of java.util.Properties: - * - rawParse / rawParseLine: Properties.load0() - * - LineReader: Properties.LineReader.readLine() - * - decodeLine: Properties.loadConvert() - * - encodeLine: Properties.saveConvert() - * - * Everything exported here is public API (`java-props/dist/utils`). + * Deprecated low-level API (`java-props/dist/utils`, public since 2.3.0): the original behavior of java-props only, + * frozen. Replaced by readEntries(), escapeKey() and escapeValue() of the main module. */ +import {splitLine} from './internal/java-load'; +import {saveConvert} from './internal/java-store'; +import {LegacyLineReader, legacyDecode} from './internal/legacy-load'; -const DECODE_PATTERN = /(?:\\u(.{0,4})|\\(.?))/g; -const UNICODE_PATTERN = /^[0-9a-fA-F]{4}$/; - -// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters must be escaped -const ENCODE_PATTERN = /(?:[\u0000-\u001F\\\u007F-\uFFFF])/g; -// biome-ignore lint/suspicious/noControlCharactersInRegex: ENCODE_PATTERN + separators (=: and whitespace) + comment chars (#!) -const ENCODE_KEY_PATTERN = /(?:[\u0000-\u0020!#:=\\\u007F-\uFFFF])/g; - +/** + * @deprecated Use {@link readEntries}. + */ export interface ParsedLine { line: string; sepStart: number; @@ -24,121 +17,56 @@ export interface ParsedLine { /** * Splits a .properties string into logical lines, and calls `consumeFn` with the key/value boundaries of each one. + * + * @deprecated Use {@link readEntries}: its `rawKey` and `rawValue`. */ export function rawParse(str: string, consumeFn: (res: ParsedLine) => void): void { - const lr = new LineReader(str); - for (let line = lr.readLine(); line !== undefined; line = lr.readLine()) { - consumeFn(rawParseLine(line)); + const reader = new LegacyLineReader(str, false); + for (let line = reader.readLine(); line !== undefined; line = reader.readLine()) { + consumeFn(splitLine(line)); } } /** * Finds the key/value boundaries of a logical line (as returned by {@link LineReader.readLine}). * The key is `line.substring(0, sepStart)` and the value is `line.substring(valueStart)`, both still escaped. + * + * @deprecated Use {@link readEntries}: its `rawKey` and `rawValue`. */ export function rawParseLine(line: string): ParsedLine { - let keyLen = 0; - let valueStart = line.length; - let hasSep = false; - let backslash = false; - - const lineLen = line.length; - let pos = 0; - for (; pos < lineLen; pos++) { - const c = line[pos]; - if ((c === '=' || c === ':') && !backslash) { - valueStart = keyLen + 1; - hasSep = true; - break; - } else if ((c === ' ' || c === '\t' || c === '\f') && !backslash) { - valueStart = keyLen + 1; - break; - } - if (c === '\\') { - backslash = !backslash; - } else { - backslash = false; - } - keyLen++; - } - while (valueStart < lineLen) { - const c = line[valueStart]; - if (c !== ' ' && c !== '\t' && c !== '\f') { - if (!hasSep && (c === '=' || c === ':')) { - hasSep = true; - } else { - break; - } - } - valueStart++; - } - - return {line, sepStart: keyLen, valueStart}; + return splitLine(line); } /** * Decodes the escape sequences of a key or a value (`\t`, `\uXXXX`, `\=`, ...). * * @throws Error on malformed `\uXXXX` sequences + * @deprecated Use {@link readEntries}: its `key` and `value`. */ export function decodeLine(line: string): string { - return line.replace(DECODE_PATTERN, (_, unicode, char) => { - if (unicode !== undefined) { - if (!unicode.match(UNICODE_PATTERN)) { - throw new Error('Malformed \\uxxxx encoding.'); - } - const charVal = parseInt(unicode, 16); - return String.fromCharCode(charVal); - } else if (char === 't') { - return '\t'; - } else if (char === 'r') { - return '\r'; - } else if (char === 'n') { - return '\n'; - } else if (char === 'f') { - return '\f'; - } else { - return char; - } - }); + return legacyDecode(line); } /** * Escapes a value, or a key if `isKey` is true (keys also escape spaces, separators and comment chars). + * + * @deprecated Use {@link escapeKey} or {@link escapeValue}. */ export function encodeLine(line: string, isKey?: boolean): string { - let str = line.replace(isKey ? ENCODE_KEY_PATTERN : ENCODE_PATTERN, (c) => { - if (c === '\t') { - return '\\t'; - } else if (c === '\r') { - return '\\r'; - } else if (c === '\n') { - return '\\n'; - } else if (c === '\f') { - return '\\f'; - } else if (c >= ' ' && c <= '~') { - return '\\' + c; - } else { - const hex = c.charCodeAt(0).toString(16).toUpperCase(); - return '\\u' + '0000'.substring(hex.length) + hex; - } - }); - if (!isKey) { - const c = str.charAt(0); - if (c === ' ' || c === '\t' || c === '\f') { - str = '\\' + str; - } - } - return str; + // The original implementation called line.replace(): the same TypeError for anything but a string + if (typeof line !== 'string') (line as unknown as {replace(): void}).replace(); + return saveConvert(line, !!isKey, !!isKey, true); } /** - * @deprecated Use {@link #decodeLine}. + * @deprecated Use {@link readEntries}: its `key` and `value`. */ export const convertLine = decodeLine; /** * Reads logical lines: skips comments, blank lines and leading whitespace, and joins continuation lines. + * + * @deprecated Use {@link readEntries}: its `line`. */ export class LineReader { public readonly str: string; @@ -154,74 +82,10 @@ export class LineReader { * @return The next logical line, or `undefined` at the end of the input */ public readLine(): string | undefined { - let skipWhiteSpace = true; - let commentLine = false; - let newLine = true; - let appendedLineBegin = false; - let backslash = false; - let skipLF = false; - let line = ''; - while (this.pos < this.strLen) { - const c = this.str[this.pos++]; - if (skipLF) { - skipLF = false; - if (c === '\n') { - continue; - } - } - if (skipWhiteSpace) { - if (c === ' ' || c === '\t' || c === '\f') { - continue; - } - if (!appendedLineBegin && (c === '\r' || c === '\n')) { - continue; - } - skipWhiteSpace = false; - appendedLineBegin = false; - } - if (newLine) { - newLine = false; - if (c === '#' || c === '!') { - commentLine = true; - continue; - } - } - - if (c !== '\n' && c !== '\r') { - line += c; - if (c === '\\') { - backslash = !backslash; - } else { - backslash = false; - } - } else { - // reached EOL - if (commentLine || line === '') { - commentLine = false; - newLine = true; - skipWhiteSpace = true; - line = ''; - continue; - } - if (backslash) { - line = line.substring(0, line.length - 1); - skipWhiteSpace = true; - appendedLineBegin = true; - backslash = false; - if (c === '\r') { - skipLF = true; - } - } else { - return line; - } - } - } - if (backslash) { - line = line.substring(0, line.length - 1); - } - if (line === '' || commentLine) { - return undefined; - } + const reader = new LegacyLineReader(this.str, false); + reader.pos = this.pos; + const line = reader.readLine(); + this.pos = reader.pos; return line; } } diff --git a/test/cases/parse-cases.mts b/test/cases/parse-cases.mts index d3f2498..cd12185 100644 --- a/test/cases/parse-cases.mts +++ b/test/cases/parse-cases.mts @@ -117,7 +117,7 @@ add('lone continuation backslash', { 'followed by property': '\\\nk=v', }); -// Comment or key? Changed by JDK-8224202 (see LOAD_ERAS in test/support/load-result.mts). +// Comment or key? Changed by JDK-8224202 (see LOAD_ERAS in src/internal/java-version.ts). add('empty continuation then comment char', { hash: '\\\n#foo=bar\nbaz=1', 'bang with indentation': ' \\\n!x=1', @@ -134,12 +134,14 @@ add('comment ending with backslash, then backslashes', { 'comment with 3 backslashes, then backslashes': '#\\\\\\\n\\\\\\\\', }); -// JDK 8 reads stale chars of its reused line buffer (partly fixed by JDK-8224202, then by JDK-8224240). +// Java reads stale chars of its reused line buffer (changed in JDK 9, partly fixed by JDK-8224202, then JDK-8224240). add('stale line buffer', { 'lone backslash decode after comment': '#\\v\\\r\n\t\\', 'truncated unicode after longer line': 'abcdef12\na=\\u00', 'truncated unicode after longer non-hex line': 'abcdef1z\na=\\u00', 'truncated unicode in key after longer line': 'xxxxxx0041\n\\u00', + 'comment chars before a backslash (skipped since JDK 9)': '#ab\\\n\\', + 'comment ending with backslash (skipped since JDK 9)': '#a\\\n\\', }); add('escapes', { diff --git a/test/cases/parse-expected.json b/test/cases/parse-expected.json index fb955d8..1408b53 100644 --- a/test/cases/parse-expected.json +++ b/test/cases/parse-expected.json @@ -4,6 +4,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -16,6 +19,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -28,6 +34,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -40,6 +49,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -52,6 +64,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -64,6 +79,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -79,6 +97,12 @@ "b": "2" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2" + } + }, "jdk11": { "props": { "a": "1", @@ -100,6 +124,12 @@ "b": "2" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2" + } + }, "jdk11": { "props": { "a": "1", @@ -121,6 +151,12 @@ "b": "2" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2" + } + }, "jdk11": { "props": { "a": "1", @@ -142,6 +178,12 @@ "b": "2" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2" + } + }, "jdk11": { "props": { "a": "1", @@ -165,6 +207,14 @@ "d": "4" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2", + "c": "3", + "d": "4" + } + }, "jdk11": { "props": { "a": "1", @@ -190,6 +240,12 @@ "b": "2" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2" + } + }, "jdk11": { "props": { "a": "1", @@ -211,6 +267,12 @@ "b": "2" } }, + "jdk9": { + "props": { + "a": "1", + "b": "2" + } + }, "jdk11": { "props": { "a": "1", @@ -231,6 +293,11 @@ "a": "1
b=2
c=3" } }, + "jdk9": { + "props": { + "a": "1
b=2
c=3" + } + }, "jdk11": { "props": { "a": "1
b=2
c=3" @@ -249,6 +316,11 @@ "a": "1…b=2" } }, + "jdk9": { + "props": { + "a": "1…b=2" + } + }, "jdk11": { "props": { "a": "1…b=2" @@ -267,6 +339,11 @@ "\u000ba": "1" } }, + "jdk9": { + "props": { + "\u000ba": "1" + } + }, "jdk11": { "props": { "\u000ba": "1" @@ -285,6 +362,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -303,6 +385,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -321,6 +408,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -339,6 +431,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -357,6 +454,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -375,6 +477,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -393,6 +500,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -411,6 +523,11 @@ "key": ":value" } }, + "jdk9": { + "props": { + "key": ":value" + } + }, "jdk11": { "props": { "key": ":value" @@ -429,6 +546,11 @@ "key": "=value" } }, + "jdk9": { + "props": { + "key": "=value" + } + }, "jdk11": { "props": { "key": "=value" @@ -447,6 +569,11 @@ "key": "=value" } }, + "jdk9": { + "props": { + "key": "=value" + } + }, "jdk11": { "props": { "key": "=value" @@ -465,6 +592,11 @@ "key": "= value" } }, + "jdk9": { + "props": { + "key": "= value" + } + }, "jdk11": { "props": { "key": "= value" @@ -483,6 +615,11 @@ "key": "value" } }, + "jdk9": { + "props": { + "key": "value" + } + }, "jdk11": { "props": { "key": "value" @@ -501,6 +638,11 @@ "key": "value=x" } }, + "jdk9": { + "props": { + "key": "value=x" + } + }, "jdk11": { "props": { "key": "value=x" @@ -519,6 +661,11 @@ "key": "" } }, + "jdk9": { + "props": { + "key": "" + } + }, "jdk11": { "props": { "key": "" @@ -537,6 +684,11 @@ "key": "" } }, + "jdk9": { + "props": { + "key": "" + } + }, "jdk11": { "props": { "key": "" @@ -555,6 +707,11 @@ "key": "" } }, + "jdk9": { + "props": { + "key": "" + } + }, "jdk11": { "props": { "key": "" @@ -573,6 +730,11 @@ "key": "" } }, + "jdk9": { + "props": { + "key": "" + } + }, "jdk11": { "props": { "key": "" @@ -591,6 +753,11 @@ "": "value" } }, + "jdk9": { + "props": { + "": "value" + } + }, "jdk11": { "props": { "": "value" @@ -609,6 +776,11 @@ "": "value" } }, + "jdk9": { + "props": { + "": "value" + } + }, "jdk11": { "props": { "": "value" @@ -627,6 +799,11 @@ "": "" } }, + "jdk9": { + "props": { + "": "" + } + }, "jdk11": { "props": { "": "" @@ -645,6 +822,11 @@ "": "value" } }, + "jdk9": { + "props": { + "": "value" + } + }, "jdk11": { "props": { "": "value" @@ -663,6 +845,11 @@ "a=b": "c" } }, + "jdk9": { + "props": { + "a=b": "c" + } + }, "jdk11": { "props": { "a=b": "c" @@ -681,6 +868,11 @@ "a:b": "c" } }, + "jdk9": { + "props": { + "a:b": "c" + } + }, "jdk11": { "props": { "a:b": "c" @@ -699,6 +891,11 @@ "a b": "c" } }, + "jdk9": { + "props": { + "a b": "c" + } + }, "jdk11": { "props": { "a b": "c" @@ -717,6 +914,11 @@ "a\tb": "c" } }, + "jdk9": { + "props": { + "a\tb": "c" + } + }, "jdk11": { "props": { "a\tb": "c" @@ -735,6 +937,11 @@ "a\\": "b" } }, + "jdk9": { + "props": { + "a\\": "b" + } + }, "jdk11": { "props": { "a\\": "b" @@ -753,6 +960,11 @@ "a\\=b": "c" } }, + "jdk9": { + "props": { + "a\\=b": "c" + } + }, "jdk11": { "props": { "a\\=b": "c" @@ -771,6 +983,11 @@ "k": "a=b:c d" } }, + "jdk9": { + "props": { + "k": "a=b:c d" + } + }, "jdk11": { "props": { "k": "a=b:c d" @@ -789,6 +1006,11 @@ "k": " v" } }, + "jdk9": { + "props": { + "k": " v" + } + }, "jdk11": { "props": { "k": " v" @@ -807,6 +1029,11 @@ "k": "v \t\f" } }, + "jdk9": { + "props": { + "k": "v \t\f" + } + }, "jdk11": { "props": { "k": "v \t\f" @@ -825,6 +1052,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -843,6 +1075,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -861,6 +1098,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -879,6 +1121,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -897,6 +1144,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -915,6 +1167,11 @@ "a#b": "c" } }, + "jdk9": { + "props": { + "a#b": "c" + } + }, "jdk11": { "props": { "a#b": "c" @@ -933,6 +1190,11 @@ "k": "v # not a comment" } }, + "jdk9": { + "props": { + "k": "v # not a comment" + } + }, "jdk11": { "props": { "k": "v # not a comment" @@ -951,6 +1213,11 @@ "#k": "v" } }, + "jdk9": { + "props": { + "#k": "v" + } + }, "jdk11": { "props": { "#k": "v" @@ -969,6 +1236,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -987,6 +1259,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1005,6 +1282,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1023,6 +1305,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1039,6 +1326,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -1053,6 +1343,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1071,6 +1366,11 @@ "k": "ab" } }, + "jdk9": { + "props": { + "k": "ab" + } + }, "jdk11": { "props": { "k": "ab" @@ -1089,6 +1389,11 @@ "k": "ab" } }, + "jdk9": { + "props": { + "k": "ab" + } + }, "jdk11": { "props": { "k": "ab" @@ -1107,6 +1412,11 @@ "k": "ab" } }, + "jdk9": { + "props": { + "k": "ab" + } + }, "jdk11": { "props": { "k": "ab" @@ -1125,6 +1435,11 @@ "k": "ab" } }, + "jdk9": { + "props": { + "k": "ab" + } + }, "jdk11": { "props": { "k": "ab" @@ -1144,6 +1459,12 @@ "k": "a" } }, + "jdk9": { + "props": { + "b": "c", + "k": "a" + } + }, "jdk11": { "props": { "b": "c", @@ -1164,6 +1485,11 @@ "k": "abc" } }, + "jdk9": { + "props": { + "k": "abc" + } + }, "jdk11": { "props": { "k": "abc" @@ -1182,6 +1508,11 @@ "key": "v" } }, + "jdk9": { + "props": { + "key": "v" + } + }, "jdk11": { "props": { "key": "v" @@ -1200,6 +1531,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1219,6 +1555,12 @@ "k": "a\\" } }, + "jdk9": { + "props": { + "b": "c", + "k": "a\\" + } + }, "jdk11": { "props": { "b": "c", @@ -1239,6 +1581,11 @@ "k": "a\\b" } }, + "jdk9": { + "props": { + "k": "a\\b" + } + }, "jdk11": { "props": { "k": "a\\b" @@ -1258,6 +1605,12 @@ "k": "a" } }, + "jdk9": { + "props": { + "b": "c", + "k": "a" + } + }, "jdk11": { "props": { "b": "c", @@ -1279,6 +1632,12 @@ "k": "a" } }, + "jdk9": { + "props": { + "b": "c", + "k": "a" + } + }, "jdk11": { "props": { "b": "c", @@ -1299,6 +1658,11 @@ "k": "a# not a comment" } }, + "jdk9": { + "props": { + "k": "a# not a comment" + } + }, "jdk11": { "props": { "k": "a# not a comment" @@ -1317,6 +1681,11 @@ "k": "a!x" } }, + "jdk9": { + "props": { + "k": "a!x" + } + }, "jdk11": { "props": { "k": "a!x" @@ -1335,6 +1704,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1353,6 +1727,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1371,6 +1750,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1389,6 +1773,11 @@ "k": "a b" } }, + "jdk9": { + "props": { + "k": "a b" + } + }, "jdk11": { "props": { "k": "a b" @@ -1408,6 +1797,12 @@ "k": "a" } }, + "jdk9": { + "props": { + "b": "c", + "k": "a" + } + }, "jdk11": { "props": { "b": "c", @@ -1428,6 +1823,11 @@ "": "" } }, + "jdk9": { + "props": { + "": "" + } + }, "jdk11": { "props": { "": "" @@ -1446,6 +1846,11 @@ "": "" } }, + "jdk9": { + "props": { + "": "" + } + }, "jdk11": { "props": { "": "" @@ -1462,6 +1867,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -1477,6 +1885,12 @@ "a": "1" } }, + "jdk9": { + "props": { + "": "", + "a": "1" + } + }, "jdk11": { "props": { "": "", @@ -1498,6 +1912,12 @@ "a": "1" } }, + "jdk9": { + "props": { + "": "", + "a": "1" + } + }, "jdk11": { "props": { "": "", @@ -1518,6 +1938,11 @@ "": "" } }, + "jdk9": { + "props": { + "": "" + } + }, "jdk11": { "props": { "": "" @@ -1534,6 +1959,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -1546,6 +1974,9 @@ "jdk8": { "props": {} }, + "jdk9": { + "props": {} + }, "jdk11": { "props": {} }, @@ -1560,6 +1991,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1579,6 +2015,12 @@ "baz": "1" } }, + "jdk9": { + "props": { + "#foo": "bar", + "baz": "1" + } + }, "jdk11": { "props": { "baz": "1" @@ -1597,6 +2039,11 @@ "!x": "1" } }, + "jdk9": { + "props": { + "!x": "1" + } + }, "jdk11": { "props": {} }, @@ -1612,6 +2059,12 @@ "k": "v" } }, + "jdk9": { + "props": { + "#c": "", + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1631,6 +2084,12 @@ "k": "v" } }, + "jdk9": { + "props": { + "#c": "", + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1650,6 +2109,12 @@ "k": "v" } }, + "jdk9": { + "props": { + "#c": "", + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -1668,6 +2133,11 @@ "\\": "" } }, + "jdk9": { + "props": { + "\\": "" + } + }, "jdk11": { "props": { "\\": "" @@ -1686,6 +2156,11 @@ "\u0000": "" } }, + "jdk9": { + "props": { + "\u0000": "" + } + }, "jdk11": { "props": { "": "" @@ -1704,6 +2179,11 @@ "k": "\\" } }, + "jdk9": { + "props": { + "k": "\\" + } + }, "jdk11": { "props": { "k": "\\" @@ -1722,6 +2202,11 @@ "\\\\": "" } }, + "jdk9": { + "props": { + "\\\\": "" + } + }, "jdk11": { "props": { "\\\\": "" @@ -1740,6 +2225,11 @@ "v": "" } }, + "jdk9": { + "props": { + "v": "" + } + }, "jdk11": { "props": { "": "" @@ -1759,6 +2249,12 @@ "abcdef12": "" } }, + "jdk9": { + "props": { + "a": "\u0012", + "abcdef12": "" + } + }, "jdk11": { "props": { "a": "\u0012", @@ -1776,6 +2272,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -1791,6 +2291,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -1800,6 +2304,52 @@ "message": "Malformed \\uxxxx encoding." } }, + "stale line buffer: comment chars before a backslash (skipped since JDK 9)": { + "input": "#ab\\\n\\", + "jdk8": { + "props": { + "b": "" + } + }, + "jdk9": { + "props": { + "\u0000": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, + "stale line buffer: comment ending with backslash (skipped since JDK 9)": { + "input": "#a\\\n\\", + "jdk8": { + "props": { + "\\": "" + } + }, + "jdk9": { + "props": { + "\u0000": "" + } + }, + "jdk11": { + "props": { + "": "" + } + }, + "jdk13": { + "props": { + "": "" + } + } + }, "escapes: standard escapes": { "input": "k=\\t\\n\\r\\f", "jdk8": { @@ -1807,6 +2357,11 @@ "k": "\t\n\r\f" } }, + "jdk9": { + "props": { + "k": "\t\n\r\f" + } + }, "jdk11": { "props": { "k": "\t\n\r\f" @@ -1825,6 +2380,11 @@ "k": "abcdegz09" } }, + "jdk9": { + "props": { + "k": "abcdegz09" + } + }, "jdk11": { "props": { "k": "abcdegz09" @@ -1843,6 +2403,11 @@ "k": "\\" } }, + "jdk9": { + "props": { + "k": "\\" + } + }, "jdk11": { "props": { "k": "\\" @@ -1861,6 +2426,11 @@ "k": "\"'" } }, + "jdk9": { + "props": { + "k": "\"'" + } + }, "jdk11": { "props": { "k": "\"'" @@ -1879,6 +2449,11 @@ "\t\n\r\fa": "v" } }, + "jdk9": { + "props": { + "\t\n\r\fa": "v" + } + }, "jdk11": { "props": { "\t\n\r\fa": "v" @@ -1897,6 +2472,11 @@ "k": "=: #!" } }, + "jdk9": { + "props": { + "k": "=: #!" + } + }, "jdk11": { "props": { "k": "=: #!" @@ -1915,6 +2495,11 @@ "k": "é❤" } }, + "jdk9": { + "props": { + "k": "é❤" + } + }, "jdk11": { "props": { "k": "é❤" @@ -1933,6 +2518,11 @@ "k": "
" } }, + "jdk9": { + "props": { + "k": "
" + } + }, "jdk11": { "props": { "k": "
" @@ -1951,6 +2541,11 @@ "k": "\u0000" } }, + "jdk9": { + "props": { + "k": "\u0000" + } + }, "jdk11": { "props": { "k": "\u0000" @@ -1969,6 +2564,11 @@ "k": "é" } }, + "jdk9": { + "props": { + "k": "é" + } + }, "jdk11": { "props": { "k": "é" @@ -1987,6 +2587,11 @@ "k": "é" } }, + "jdk9": { + "props": { + "k": "é" + } + }, "jdk11": { "props": { "k": "é" @@ -2005,6 +2610,11 @@ "k": "ꯍ" } }, + "jdk9": { + "props": { + "k": "ꯍ" + } + }, "jdk11": { "props": { "k": "ꯍ" @@ -2023,6 +2633,11 @@ "A": "v" } }, + "jdk9": { + "props": { + "A": "v" + } + }, "jdk11": { "props": { "A": "v" @@ -2041,6 +2656,11 @@ "k": "\u0000" } }, + "jdk9": { + "props": { + "k": "\u0000" + } + }, "jdk11": { "props": { "k": "\u0000" @@ -2059,6 +2679,11 @@ "k": "👑" } }, + "jdk9": { + "props": { + "k": "👑" + } + }, "jdk11": { "props": { "k": "👑" @@ -2077,6 +2702,11 @@ "k": "\ud83d" } }, + "jdk9": { + "props": { + "k": "\ud83d" + } + }, "jdk11": { "props": { "k": "\ud83d" @@ -2095,6 +2725,11 @@ "k": "\udc51" } }, + "jdk9": { + "props": { + "k": "\udc51" + } + }, "jdk11": { "props": { "k": "\udc51" @@ -2113,6 +2748,11 @@ "k=x": "v" } }, + "jdk9": { + "props": { + "k=x": "v" + } + }, "jdk11": { "props": { "k=x": "v" @@ -2131,6 +2771,11 @@ "k": "\\n" } }, + "jdk9": { + "props": { + "k": "\\n" + } + }, "jdk11": { "props": { "k": "\\n" @@ -2149,6 +2794,11 @@ "k": "A0" } }, + "jdk9": { + "props": { + "k": "A0" + } + }, "jdk11": { "props": { "k": "A0" @@ -2167,6 +2817,11 @@ "k": "U0041" } }, + "jdk9": { + "props": { + "k": "U0041" + } + }, "jdk11": { "props": { "k": "U0041" @@ -2185,6 +2840,11 @@ "k": "\\u0041" } }, + "jdk9": { + "props": { + "k": "\\u0041" + } + }, "jdk11": { "props": { "k": "\\u0041" @@ -2202,6 +2862,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2217,6 +2881,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2232,6 +2900,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2247,6 +2919,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2262,6 +2938,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2278,6 +2958,11 @@ "k": "A" } }, + "jdk9": { + "props": { + "k": "A" + } + }, "jdk11": { "props": { "k": "A" @@ -2295,6 +2980,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2310,6 +2999,10 @@ "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." }, + "jdk9": { + "throws": "java.lang.IllegalArgumentException", + "message": "Malformed \\uxxxx encoding." + }, "jdk11": { "throws": "java.lang.IllegalArgumentException", "message": "Malformed \\uxxxx encoding." @@ -2326,6 +3019,11 @@ "clé": "valeur é ❤" } }, + "jdk9": { + "props": { + "clé": "valeur é ❤" + } + }, "jdk11": { "props": { "clé": "valeur é ❤" @@ -2344,6 +3042,11 @@ "k": "👑" } }, + "jdk9": { + "props": { + "k": "👑" + } + }, "jdk11": { "props": { "k": "👑" @@ -2362,6 +3065,11 @@ "k": "\ud83d" } }, + "jdk9": { + "props": { + "k": "\ud83d" + } + }, "jdk11": { "props": { "k": "\ud83d" @@ -2380,6 +3088,11 @@ "k": "a\u0000b" } }, + "jdk9": { + "props": { + "k": "a\u0000b" + } + }, "jdk11": { "props": { "k": "a\u0000b" @@ -2398,6 +3111,11 @@ "k": "\u0001\u001f" } }, + "jdk9": { + "props": { + "k": "\u0001\u001f" + } + }, "jdk11": { "props": { "k": "\u0001\u001f" @@ -2416,6 +3134,11 @@ "k": "v" } }, + "jdk9": { + "props": { + "k": "v" + } + }, "jdk11": { "props": { "k": "v" @@ -2435,6 +3158,12 @@ "k": "v" } }, + "jdk9": { + "props": { + "a": "1", + "k": "v" + } + }, "jdk11": { "props": { "a": "1", @@ -2455,6 +3184,11 @@ " k": "v" } }, + "jdk9": { + "props": { + " k": "v" + } + }, "jdk11": { "props": { " k": "v" @@ -2473,6 +3207,11 @@ "k=v": "" } }, + "jdk9": { + "props": { + "k=v": "" + } + }, "jdk11": { "props": { "k=v": "" @@ -2491,6 +3230,11 @@ "k": "3" } }, + "jdk9": { + "props": { + "k": "3" + } + }, "jdk11": { "props": { "k": "3" @@ -2509,6 +3253,11 @@ "k": "2" } }, + "jdk9": { + "props": { + "k": "2" + } + }, "jdk11": { "props": { "k": "2" @@ -2530,6 +3279,14 @@ "toString": "d" } }, + "jdk9": { + "props": { + "__proto__": "a", + "constructor": "b", + "hasOwnProperty": "c", + "toString": "d" + } + }, "jdk11": { "props": { "__proto__": "a", @@ -2558,6 +3315,15 @@ "01": "e" } }, + "jdk9": { + "props": { + "1": "a", + "2": "b", + "10": "c", + "-1": "d", + "01": "e" + } + }, "jdk11": { "props": { "1": "a", @@ -2584,6 +3350,11 @@ "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" } }, + "jdk9": { + "props": { + "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" + } + }, "jdk11": { "props": { "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" @@ -2603,6 +3374,12 @@ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "y" } }, + "jdk9": { + "props": { + "a": "", + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": "y" + } + }, "jdk11": { "props": { "a": "", diff --git a/test/cases/parse-snapshot.json b/test/cases/parse-snapshot.json index 0c557f1..19371b0 100644 --- a/test/cases/parse-snapshot.json +++ b/test/cases/parse-snapshot.json @@ -594,6 +594,18 @@ "throws": "Error", "message": "Malformed \\uxxxx encoding." }, + "stale line buffer: comment chars before a backslash (skipped since JDK 9)": { + "input": "#ab\\\n\\", + "props": { + "": "" + } + }, + "stale line buffer: comment ending with backslash (skipped since JDK 9)": { + "input": "#a\\\n\\", + "props": { + "": "" + } + }, "escapes: standard escapes": { "input": "k=\\t\\n\\r\\f", "props": { diff --git a/test/cases/stringify-cases.mts b/test/cases/stringify-cases.mts new file mode 100644 index 0000000..68d1324 --- /dev/null +++ b/test/cases/stringify-cases.mts @@ -0,0 +1,66 @@ +// Cases for stringify() with javaVersion. Java results: stringify-expected.json (`pnpm run reference:gen`). +// Strings are JS strings: '\\' is a single backslash. + +export interface StringifyCase { + name: string; + props: Record; + comments?: string; +} + +export interface DateCase { + name: string; + /** Epoch time in milliseconds */ + time: number; + /** Time zone id for Java's TimeZone.getTimeZone() */ + javaTimeZone: string; + /** Same time zone, for the `timeZone` option */ + timeZone: string; +} + +export const stringifyCases: StringifyCase[] = [ + {name: 'empty', props: {}}, + {name: 'simple', props: {a: '1', b: 'two words'}}, + {name: 'order', props: {b: '1', a: '2', '10': '3', '2': '4', B: '5', é: '6', '': '7'}}, + {name: 'separators and comment chars', props: {'k=:#!': 'v=:#!', '=': ':', '#': '!'}}, + {name: 'spaces', props: {' k e y ': ' v a l u e ', ' ': ' ', tab: '\t', ff: '\f'}}, + {name: 'line terminators', props: {'a\nb': 'c\rd', 'e\r\n': '\n\n'}}, + {name: 'backslashes', props: {'\\': '\\\\', 'a\\b': 'c\\'}}, + {name: 'control chars', props: {'\u0000': '\u0001\u001f', del: '\u007f', vt: '\u000b'}}, + {name: 'non-ASCII', props: {clé: 'é ÿ', Ā: '❤ 👑', bom: '\ufeff', nbsp: '\u00a0', lone: '\ud83d', ls: '\u2028'}}, + {name: 'proto-like keys', props: {['__proto__']: 'a', constructor: 'b'}}, + {name: 'comments: simple', props: {k: 'v'}, comments: 'Generated file'}, + {name: 'comments: empty', props: {}, comments: ''}, + {name: 'comments: multi-line', props: {}, comments: 'line 1\nline 2\r\nline 3\rline 4'}, + {name: 'comments: line starting with # or !', props: {}, comments: 'a\n# b\n! c\n # d'}, + {name: 'comments: trailing line terminators', props: {}, comments: 'a\n'}, + {name: 'comments: CRLF and CR at the end', props: {}, comments: 'a\r\n\r'}, + {name: 'comments: non-ASCII', props: {}, comments: 'é ÿ Ā ❤ 👑 \u0000 \t'}, +] + // `__proto__` must be a data key + .map((c) => ({...c, props: Object.assign(Object.create(null), c.props)})); + +// Jan 15 and Jul 15 2026, 13:04:05.678 UTC +const WINTER = Date.UTC(2026, 0, 15, 13, 4, 5, 678); +const SUMMER = Date.UTC(2026, 6, 15, 13, 4, 5, 678); + +// Time zones where the date comment is exact (see the `timeZone` option) +export const dateCases: DateCase[] = [ + {name: 'UTC', time: WINTER, javaTimeZone: 'UTC', timeZone: 'UTC'}, + {name: 'fixed positive offset', time: WINTER, javaTimeZone: 'GMT+02:00', timeZone: '+02:00'}, + {name: 'fixed negative offset', time: WINTER, javaTimeZone: 'GMT-09:30', timeZone: '-09:30'}, + {name: 'fixed zero offset', time: WINTER, javaTimeZone: 'GMT+00:00', timeZone: '+00:00'}, + {name: 'day change', time: Date.UTC(2026, 11, 31, 23, 30), javaTimeZone: 'GMT+01:00', timeZone: '+01:00'}, + {name: 'single digit day', time: Date.UTC(2026, 1, 3, 1, 2, 3), javaTimeZone: 'UTC', timeZone: 'UTC'}, + {name: 'America/New_York winter', time: WINTER, javaTimeZone: 'America/New_York', timeZone: 'America/New_York'}, + {name: 'America/New_York summer', time: SUMMER, javaTimeZone: 'America/New_York', timeZone: 'America/New_York'}, + {name: 'Europe/Paris winter', time: WINTER, javaTimeZone: 'Europe/Paris', timeZone: 'Europe/Paris'}, + {name: 'Europe/Paris summer', time: SUMMER, javaTimeZone: 'Europe/Paris', timeZone: 'Europe/Paris'}, + {name: 'Europe/London winter', time: WINTER, javaTimeZone: 'Europe/London', timeZone: 'Europe/London'}, + {name: 'Europe/London summer', time: SUMMER, javaTimeZone: 'Europe/London', timeZone: 'Europe/London'}, + { + name: 'America/Sao_Paulo (no name in Java)', + time: WINTER, + javaTimeZone: 'America/Sao_Paulo', + timeZone: 'America/Sao_Paulo', + }, +]; diff --git a/test/cases/stringify-expected.json b/test/cases/stringify-expected.json new file mode 100644 index 0000000..7ad8120 --- /dev/null +++ b/test/cases/stringify-expected.json @@ -0,0 +1,205 @@ +{ + "cases": { + "empty": { + "props": {}, + "writer": "", + "stream": "" + }, + "simple": { + "props": { + "a": "1", + "b": "two words" + }, + "writer": "a=1\nb=two words\n", + "stream": "a=1\nb=two words\n" + }, + "order": { + "props": { + "2": "4", + "10": "3", + "b": "1", + "a": "2", + "B": "5", + "é": "6", + "": "7" + }, + "writer": "=7\n10=3\n2=4\nB=5\na=2\nb=1\né=6\n", + "stream": "=7\n10=3\n2=4\nB=5\na=2\nb=1\n\\u00E9=6\n" + }, + "separators and comment chars": { + "props": { + "k=:#!": "v=:#!", + "=": ":", + "#": "!" + }, + "writer": "\\#=\\!\n\\==\\:\nk\\=\\:\\#\\!=v\\=\\:\\#\\!\n", + "stream": "\\#=\\!\n\\==\\:\nk\\=\\:\\#\\!=v\\=\\:\\#\\!\n" + }, + "spaces": { + "props": { + " k e y ": " v a l u e ", + " ": " ", + "tab": "\t", + "ff": "\f" + }, + "writer": "\\ \\ =\\ \n\\ k\\ e\\ y\\ =\\ v a l u e \nff=\\f\ntab=\\t\n", + "stream": "\\ \\ =\\ \n\\ k\\ e\\ y\\ =\\ v a l u e \nff=\\f\ntab=\\t\n" + }, + "line terminators": { + "props": { + "a\nb": "c\rd", + "e\r\n": "\n\n" + }, + "writer": "a\\nb=c\\rd\ne\\r\\n=\\n\\n\n", + "stream": "a\\nb=c\\rd\ne\\r\\n=\\n\\n\n" + }, + "backslashes": { + "props": { + "\\": "\\\\", + "a\\b": "c\\" + }, + "writer": "\\\\=\\\\\\\\\na\\\\b=c\\\\\n", + "stream": "\\\\=\\\\\\\\\na\\\\b=c\\\\\n" + }, + "control chars": { + "props": { + "\u0000": "\u0001\u001f", + "del": "", + "vt": "\u000b" + }, + "writer": "\u0000=\u0001\u001f\ndel=\nvt=\u000b\n", + "stream": "\\u0000=\\u0001\\u001F\ndel=\\u007F\nvt=\\u000B\n" + }, + "non-ASCII": { + "props": { + "clé": "é ÿ", + "Ā": "❤ 👑", + "bom": "", + "nbsp": " ", + "lone": "\ud83d", + "ls": "
" + }, + "writer": "bom=\nclé=é ÿ\nlone=\ud83d\nls=
\nnbsp= \nĀ=❤ 👑\n", + "stream": "bom=\\uFEFF\ncl\\u00E9=\\u00E9 \\u00FF\nlone=\\uD83D\nls=\\u2028\nnbsp=\\u00A0\n\\u0100=\\u2764 \\uD83D\\uDC51\n" + }, + "proto-like keys": { + "props": { + "__proto__": "a", + "constructor": "b" + }, + "writer": "__proto__=a\nconstructor=b\n", + "stream": "__proto__=a\nconstructor=b\n" + }, + "comments: simple": { + "props": { + "k": "v" + }, + "comments": "Generated file", + "writer": "#Generated file\nk=v\n", + "stream": "#Generated file\nk=v\n" + }, + "comments: empty": { + "props": {}, + "comments": "", + "writer": "#\n", + "stream": "#\n" + }, + "comments: multi-line": { + "props": {}, + "comments": "line 1\nline 2\r\nline 3\rline 4", + "writer": "#line 1\n#line 2\n#line 3\n#line 4\n", + "stream": "#line 1\n#line 2\n#line 3\n#line 4\n" + }, + "comments: line starting with # or !": { + "props": {}, + "comments": "a\n# b\n! c\n # d", + "writer": "#a\n# b\n! c\n# # d\n", + "stream": "#a\n# b\n! c\n# # d\n" + }, + "comments: trailing line terminators": { + "props": {}, + "comments": "a\n", + "writer": "#a\n#\n", + "stream": "#a\n#\n" + }, + "comments: CRLF and CR at the end": { + "props": {}, + "comments": "a\r\n\r", + "writer": "#a\n#\n#\n", + "stream": "#a\n#\n#\n" + }, + "comments: non-ASCII": { + "props": {}, + "comments": "é ÿ Ā ❤ 👑 \u0000 \t", + "writer": "#é ÿ \\u0100 \\u2764 \\uD83D\\uDC51 \u0000 \t\n", + "stream": "#é ÿ \\u0100 \\u2764 \\uD83D\\uDC51 \u0000 \t\n" + } + }, + "dates": { + "UTC": { + "time": 1768482245678, + "timeZone": "UTC", + "java": "Thu Jan 15 13:04:05 UTC 2026" + }, + "fixed positive offset": { + "time": 1768482245678, + "timeZone": "+02:00", + "java": "Thu Jan 15 15:04:05 GMT+02:00 2026" + }, + "fixed negative offset": { + "time": 1768482245678, + "timeZone": "-09:30", + "java": "Thu Jan 15 03:34:05 GMT-09:30 2026" + }, + "fixed zero offset": { + "time": 1768482245678, + "timeZone": "+00:00", + "java": "Thu Jan 15 13:04:05 GMT+00:00 2026" + }, + "day change": { + "time": 1798759800000, + "timeZone": "+01:00", + "java": "Fri Jan 01 00:30:00 GMT+01:00 2027" + }, + "single digit day": { + "time": 1770080523000, + "timeZone": "UTC", + "java": "Tue Feb 03 01:02:03 UTC 2026" + }, + "America/New_York winter": { + "time": 1768482245678, + "timeZone": "America/New_York", + "java": "Thu Jan 15 08:04:05 EST 2026" + }, + "America/New_York summer": { + "time": 1784120645678, + "timeZone": "America/New_York", + "java": "Wed Jul 15 09:04:05 EDT 2026" + }, + "Europe/Paris winter": { + "time": 1768482245678, + "timeZone": "Europe/Paris", + "java": "Thu Jan 15 14:04:05 CET 2026" + }, + "Europe/Paris summer": { + "time": 1784120645678, + "timeZone": "Europe/Paris", + "java": "Wed Jul 15 15:04:05 CEST 2026" + }, + "Europe/London winter": { + "time": 1768482245678, + "timeZone": "Europe/London", + "java": "Thu Jan 15 13:04:05 GMT 2026" + }, + "Europe/London summer": { + "time": 1784120645678, + "timeZone": "Europe/London", + "java": "Wed Jul 15 14:04:05 BST 2026" + }, + "America/Sao_Paulo (no name in Java)": { + "time": 1768482245678, + "timeZone": "America/Sao_Paulo", + "java": "Thu Jan 15 10:04:05 GMT-03:00 2026" + } + } +} diff --git a/test/compat/api-snapshot.json b/test/compat/api-snapshot.json index f70901c..1bc1c40 100644 --- a/test/compat/api-snapshot.json +++ b/test/compat/api-snapshot.json @@ -1,8 +1,11 @@ { "main": [ "default: object", + "escapeKey: function", + "escapeValue: function", "parse: function", "parseFile: function", + "readEntries: function", "stringify: function" ], "mainDefault": [ @@ -12,7 +15,10 @@ ], "browser": [ "default: object", + "escapeKey: function", + "escapeValue: function", "parse: function", + "readEntries: function", "stringify: function" ], "browserDefault": [ diff --git a/test/compat/check-esm.mjs b/test/compat/check-esm.mjs index 36b09b1..3184b71 100644 --- a/test/compat/check-esm.mjs +++ b/test/compat/check-esm.mjs @@ -1,7 +1,14 @@ // ESM consumers of the built package (dist/). Runs on Node.js >= 12.20 (named imports from CommonJS). // Keep this file in Node 12 syntax (no top-level await). import assert from 'assert'; -import mainDefault, {parse, parseFile, stringify} from '../../dist/node-java-props.js'; +import mainDefault, { + escapeKey, + escapeValue, + parse, + parseFile, + readEntries, + stringify, +} from '../../dist/node-java-props.js'; import browserDefault, * as browser from '../../dist/java-props.js'; import {decodeLine, encodeLine, LineReader, rawParse, rawParseLine, convertLine} from '../../dist/utils.js'; @@ -16,6 +23,9 @@ assert.strictEqual(new LineReader('a=b').readLine(), 'a=b'); assert.deepStrictEqual(Object.assign({}, parse('a=1\nb : 2')), {a: '1', b: '2'}); assert.strictEqual(stringify({a: '1'}), 'a: 1\n'); +assert.strictEqual(escapeKey('a b') + escapeValue(' c'), 'a\\ b\\ c'); +const reader = readEntries('k=v', {javaVersion: 25}); +assert.ok(reader.next() && reader.key === 'k' && reader.value === 'v'); parseFile(new URL('../fixtures/backslashes.properties', import.meta.url)).then( (props) => { assert.ok(Object.keys(props).length > 0); diff --git a/test/compat/check-package.mjs b/test/compat/check-package.mjs index c64bda0..036c00e 100644 --- a/test/compat/check-package.mjs +++ b/test/compat/check-package.mjs @@ -32,10 +32,18 @@ assert.ok(pkg.type === undefined || pkg.type === 'commonjs', 'the package must s assert.equal(pkg.engines, undefined, '"engines" could reject installs on old Node.js versions'); assert.equal(pkg.dependencies, undefined, 'no runtime dependencies'); -// The browser entry must not require Node.js modules +// The browser entry, and every module it requires, must not require Node.js modules const requires = (file) => [...fs.readFileSync(path.join(ROOT, file), 'utf8').matchAll(/require\("([^"]+)"\)/g)].map((m) => m[1]); -assert.deepEqual(requires(pkg.browser), ['./utils'], 'requires of the browser entry'); -assert.deepEqual(requires('dist/utils.js'), [], 'requires of dist/utils.js'); +const seen = new Set(); +const visit = (file) => { + if (seen.has(file)) return; + seen.add(file); + for (const dep of requires(file)) { + assert.ok(dep.startsWith('./') || dep.startsWith('../'), `${file} requires ${dep}`); + visit(path.join(path.dirname(file), `${dep}.js`)); + } +}; +visit(pkg.browser); console.log(`OK: package content (${files.length} files)`); diff --git a/test/compat/run-smoke.js b/test/compat/run-smoke.js index 5b2b1cf..2ba5304 100644 --- a/test/compat/run-smoke.js +++ b/test/compat/run-smoke.js @@ -91,6 +91,72 @@ Object.keys(cases).forEach((name) => { }); }); +// Edge cases with javaVersion: same results as Java (test/unit/parse-cases.test.ts) +const ERA_VERSIONS = {jdk8: 8, jdk9: 9, jdk11: '11.0.5', jdk13: 13}; +const javaCases = JSON.parse(fs.readFileSync(path.join(ROOT, 'test/cases/parse-expected.json'), 'utf8')); +Object.keys(ERA_VERSIONS).forEach((era) => { + check('cases with javaVersion ' + ERA_VERSIONS[era], () => { + Object.keys(javaCases).forEach((name) => { + const c = javaCases[name]; + let res; + try { + res = {props: sortedEntries(main.parse(c.input, {javaVersion: ERA_VERSIONS[era]}))}; + } catch (err) { + res = {throws: err.message}; + } + const java = c[era]; + assert.deepStrictEqual( + res, + java.throws ? {throws: java.message} : {props: sortedEntries(java.props)}, + name + ); + }); + }); +}); + +// stringify with javaVersion: same output as Java (test/unit/java-props.test.ts) +const storeCases = JSON.parse(fs.readFileSync(path.join(ROOT, 'test/cases/stringify-expected.json'), 'utf8')); +check('stringify with javaVersion', () => { + Object.keys(storeCases.cases).forEach((name) => { + const c = storeCases.cases[name]; + const options = {javaVersion: 25, comments: c.comments, date: false, lineSeparator: '\n'}; + assert.strictEqual(main.stringify(c.props, options), c.writer, name); + options.escapeUnicode = true; + assert.strictEqual(main.stringify(c.props, options), c.stream, name); + }); + // Exact time zones only: names of the others depend on the Intl support of each Node.js version + Object.keys(storeCases.dates).forEach((name) => { + const d = storeCases.dates[name]; + if (d.timeZone !== 'UTC' && !/^[+-]/.test(d.timeZone)) return; + const options = {javaVersion: 25, date: new Date(d.time), timeZone: d.timeZone, lineSeparator: '\n'}; + assert.strictEqual(main.stringify({}, options), '#' + d.java + '\n', name); + }); +}); + +// readEntries and escapeKey/escapeValue +check('readEntries', () => { + const reader = main.readEntries('# c\nb=\\u00e9\r\na : 1\\\n 2', {positions: true}); + const entries = []; + while (reader.next()) { + entries.push([ + reader.key, + reader.value, + reader.rawValue, + reader.start, + reader.end, + reader.startLine, + reader.startColumn, + ]); + } + assert.deepStrictEqual(entries, [ + ['b', '\u00e9', '\\u00e9', 4, 12, 2, 1], + ['a', '12', '12', 14, 24, 3, 1], + ]); + assert.strictEqual(browser.readEntries, main.readEntries); + assert.strictEqual(main.escapeKey('k =é'), 'k\\ \\=\\u00E9'); + assert.strictEqual(main.escapeValue(' v=é', {javaVersion: 17}), '\\ v\\=é'); +}); + // stringify check('stringify', () => { assert.strictEqual(main.stringify({a: 'b c', ' k=': ' é\n'}), 'a: b c\n\\ k\\=: \\ \\u00E9\\n\n'); diff --git a/test/compat/types/consumer.ts b/test/compat/types/consumer.ts index af2120e..cdb71b1 100644 --- a/test/compat/types/consumer.ts +++ b/test/compat/types/consumer.ts @@ -1,7 +1,8 @@ // Uses every public declaration. Compiled by TypeScript 2.9+ (check-types.mjs): keep it in TypeScript 2.9 syntax. import javaProps = require('java-props'); import defaultProps from 'java-props'; -import {parse, parseFile, stringify, Properties} from 'java-props'; +import {parse, parseFile, stringify, Properties, ParseOptions, ParseFileOptions, StringifyOptions} from 'java-props'; +import {readEntries, escapeKey, escapeValue, EntryReader, EntryPositions, EscapeOptions} from 'java-props'; import * as browser from 'java-props/dist/java-props'; import browserDefault from 'java-props/dist/java-props'; import { @@ -40,3 +41,36 @@ rawParse(s, (res: ParsedLine) => { }); const parsed: ParsedLine = rawParseLine('a=b'); const decoded: string = decodeLine('a') + encodeLine('a') + encodeLine('a', true) + convertLine('a'); + +// Options +const parseOptions: ParseOptions = {javaVersion: 8}; +const fileOptions: ParseFileOptions = {javaVersion: '11.0.4', encoding: 'latin1'}; +const stringifyOptions: StringifyOptions = { + javaVersion: 'latest', + separator: ' = ', + escapeValueSpecials: true, + escapeUnicode: false, + comments: 'c', + date: new Date(), + timeZone: 'UTC', + lineSeparator: '\r\n', + order: 'sorted', +}; +const escapeOptions: EscapeOptions = {javaVersion: 17, escapeUnicode: false}; +const p4: Properties = parse('a=b', parseOptions) || javaProps.parse('a=b', {javaVersion: 17}); +const s2: string = + stringify(p4, stringifyOptions) + stringify(p4, {date: false}) + escapeKey('a', escapeOptions) + escapeValue('b'); +const promises2: Array> = [ + parseFile('file', 'latin1', parseOptions), + parseFile('file', fileOptions), + parseFile('file', {javaVersion: 8}), + defaultProps.parseFile('file', fileOptions), +]; + +// readEntries +const reader: EntryReader = readEntries(s2, parseOptions); +while (reader.next()) { + const entry: string = reader.key + reader.value + reader.rawKey + reader.rawValue + reader.line; +} +const positioned: EntryReader & EntryPositions = readEntries(s2, {javaVersion: 8, positions: true}); +const position: number = positioned.start + positioned.end + positioned.startLine + positioned.startColumn; diff --git a/test/support/arbitraries.mts b/test/support/arbitraries.mts new file mode 100644 index 0000000..1699fc3 --- /dev/null +++ b/test/support/arbitraries.mts @@ -0,0 +1,26 @@ +// fast-check arbitraries for .properties content. +import fc from 'fast-check'; + +// Pieces of .properties text that matter to the parser, and some that don't (backslash weighted) +const PIECES = [ + ...['\\', '\\', '\\', '\\u', '\\u00', '\\u0041', '\\\n', '\\\r\n'], + ...['\n', '\r', '\r\n', '\n#', '\n!', ' ', '\t', '\f', '=', ':', '#', '!'], + ...['a', 'k', 'v', 'u', '0', 'F', 'g', 'n', 't', 'é'], + ...['\u0000', '\u007F', '\u00A0', '\uFEFF', '\uD83D', '\uDC51'], +]; +export const text = (maxPieces: number) => + fc.array(fc.constantFrom(...PIECES), {maxLength: maxPieces}).map((pieces) => pieces.join('')); + +/** Text with long lines too, to exceed Java's initial line buffer (1024 chars) */ +export const longText = fc + .array( + fc.oneof( + text(24), + fc.constantFrom('a', '\\\\', '=').map((c) => c.repeat(1000)) + ), + {maxLength: 6} + ) + .map((parts) => parts.join('')); + +/** Properties with tricky keys and values */ +export const props = fc.dictionary(text(8), text(8)); diff --git a/test/support/data.mts b/test/support/data.mts index d3dd445..5f77751 100644 --- a/test/support/data.mts +++ b/test/support/data.mts @@ -1,10 +1,12 @@ // Test fixtures (test/fixtures) and the Java results of the edge cases (test/cases). import fs from 'node:fs'; import path from 'node:path'; -import type {LoadEra, LoadResult} from './load-result.mts'; +import type {LoadEra} from '../../src/internal/java-version.ts'; +import type {LoadResult} from './load-result.mts'; export const FIXTURES_DIR = path.join(import.meta.dirname, '../fixtures'); export const PARSE_EXPECTED_FILE = path.join(import.meta.dirname, '../cases/parse-expected.json'); +export const STRINGIFY_EXPECTED_FILE = path.join(import.meta.dirname, '../cases/stringify-expected.json'); /** The .properties files of test/fixtures. */ export function fixtureFiles(): string[] { @@ -31,3 +33,14 @@ export type ParseExpected = Record; comments?: string; writer: string; stream: string}>; + /** Inputs (`timeZone` option), and the Date.toString() of Java */ + dates: Record; +} + +export function readStringifyExpected(): StringifyExpected { + return JSON.parse(fs.readFileSync(STRINGIFY_EXPECTED_FILE, 'utf8')); +} diff --git a/test/support/load-result.mts b/test/support/load-result.mts index cce355b..e767fcd 100644 --- a/test/support/load-result.mts +++ b/test/support/load-result.mts @@ -1,21 +1,7 @@ -// Results of Properties.load() / parse(), and the behavior eras of Properties.load(). +// Results of Properties.load() / parse(). export type LoadResult = {props: Record} | {throws: string; message: string}; -/** - * Behavior eras of Properties.load(), each named after a representative JDK: - * - jdk8: the original implementation (unchanged from JDK 6 to 10, also in 11.0.0 to 11.0.4 and in 12). - * - jdk11: JDK-8224202 "Speed up Properties.load" (https://bugs.openjdk.org/browse/JDK-8224202), a rewrite of - * Properties.LineReader that also changed its behavior: `#`/`!` right after an empty continuation line starts a - * comment, and no state (backslash count, stale buffer content) leaks from comment lines anymore. - * Released in JDK 13, and backported to 11.0.5+ (but not to 12). - * - jdk13: jdk11 + JDK-8224240 "Properties.load fails to throw IAE on malformed unicode in certain circumstances" - * (https://bugs.openjdk.org/browse/JDK-8224240): a truncated `\uXXXX` at the end of a line throws, instead of - * decoding stale buffer content. JDK 13+ (not backported to 11). - */ -export const LOAD_ERAS = ['jdk8', 'jdk11', 'jdk13'] as const; -export type LoadEra = (typeof LOAD_ERAS)[number]; - /** Copies properties into a null-prototype object (so "__proto__" stays a data key), sorted by key. */ export function sortedProps(props: Record): Record { const res: Record = Object.create(null); diff --git a/test/unit/java-props.test.ts b/test/unit/java-props.test.ts index 9384335..c2a0481 100644 --- a/test/unit/java-props.test.ts +++ b/test/unit/java-props.test.ts @@ -1,6 +1,7 @@ -import {describe, expect, it} from 'vitest'; -import JavaProps, {type Properties} from '../../src/java-props'; -import {fixtureFiles, readFixture, readFixtureResult} from '../support/data.mts'; +import {afterEach, describe, expect, it, vi} from 'vitest'; +import JavaProps, {parse, type Properties, stringify} from '../../src/java-props'; +import {dateCases, stringifyCases} from '../cases/stringify-cases.mts'; +import {fixtureFiles, readFixture, readFixtureResult, readStringifyExpected} from '../support/data.mts'; const fixtures = fixtureFiles(); @@ -84,3 +85,96 @@ describe('stringify', () => { expect(JavaProps.stringify(obj)).toBe('key: value\n'); }); }); + +describe('stringify with javaVersion', () => { + const expected = readStringifyExpected(); + const base = {javaVersion: 25, date: false, lineSeparator: '\n'} as const; + + for (const c of stringifyCases) { + it(`writes like Properties.store(): ${c.name}`, () => { + const options = {...base, comments: c.comments}; + expect(stringify(c.props, options)).toBe(expected.cases[c.name].writer); + expect(stringify(c.props, {...options, escapeUnicode: true})).toBe(expected.cases[c.name].stream); + }); + } + + for (const d of dateCases) { + it(`writes the date like Java: ${d.name}`, () => { + const options = {...base, date: new Date(d.time), timeZone: d.timeZone}; + expect(stringify({}, options)).toBe(`#${expected.dates[d.name].java}\n`); + }); + } +}); + +describe('stringify options', () => { + afterEach(() => { + vi.useRealTimers(); + vi.unstubAllEnvs(); + }); + + // Replaces a property for the duration of `fn` + const withProperty = (obj: object, name: string, value: unknown, fn: () => void) => { + const descriptor = Object.getOwnPropertyDescriptor(obj, name) as PropertyDescriptor; + Object.defineProperty(obj, name, {value, configurable: true}); + try { + fn(); + } finally { + Object.defineProperty(obj, name, descriptor); + } + }; + + it('keep the historical format by default', () => { + const props = {b: '1', a: ' x=y '}; + expect(stringify(props, {})).toBe(stringify(props)); + expect(stringify(props, {order: 'sorted', separator: ' = ', lineSeparator: '\r\n'})).toBe( + 'a = \\ x=y \r\nb = 1\r\n' + ); + expect(stringify(props, {escapeValueSpecials: true, escapeUnicode: false})).toBe('b: 1\na: \\ x\\=y \n'); + expect(stringify(Object.create({inherited: 'x'}), {javaVersion: 25, date: false})).toBe(''); + }); + + it('write a date string as a comment', () => { + expect(stringify({}, {date: 'Mon\nTue'})).toBe('#Mon\n#Tue\n'); + }); + + it('write the current date by default with javaVersion', () => { + vi.useFakeTimers({now: Date.UTC(2026, 0, 15, 13, 4, 5)}); + const expected = '#Thu Jan 15 13:04:05 UTC 2026\nk=v\n'; + expect(stringify({k: 'v'}, {javaVersion: 25, timeZone: 'UTC', lineSeparator: '\n'})).toBe(expected); + expect(stringify({k: 'v'}, {javaVersion: 25, timeZone: 'UTC', lineSeparator: '\n', date: ''})).toBe(expected); + }); + + it('use the system line separator with javaVersion', () => { + withProperty(process, 'platform', 'win32', () => { + expect(stringify({k: 'v'}, {javaVersion: 25, date: false})).toBe('k=v\r\n'); + }); + withProperty(process, 'platform', 'linux', () => { + expect(stringify({k: 'v'}, {javaVersion: 25, date: false})).toBe('k=v\n'); + }); + }); + + it('fall back to the GMT offset without Intl.DateTimeFormat#formatToParts', () => { + vi.stubEnv('TZ', 'Etc/GMT-2'); + const date = new Date(Date.UTC(2026, 0, 15, 13, 4, 5)); + withProperty(Intl.DateTimeFormat.prototype, 'formatToParts', undefined, () => { + expect(stringify({}, {date})).toBe('#Thu Jan 15 15:04:05 GMT+02:00 2026\n'); + expect(() => stringify({}, {date, timeZone: 'Europe/Paris'})).toThrow(RangeError); + }); + }); + + it('reject invalid options', () => { + expect(() => stringify({k: 1 as unknown as string}, {javaVersion: 25})).toThrow(TypeError); + expect(() => stringify({}, {javaVersion: 'nope'})).toThrow(RangeError); + expect(() => stringify({}, {date: true, timeZone: 'Nowhere/Nope'})).toThrow(RangeError); + }); +}); + +describe('parse options', () => { + it('reject an invalid javaVersion', () => { + expect(() => parse('a=b', {javaVersion: 5})).toThrow(RangeError); + }); + + it('ignore an undefined javaVersion', () => { + expect(parse('\\', {javaVersion: undefined})).toEqual(parse('\\')); + }); +}); diff --git a/test/unit/java-version.test.ts b/test/unit/java-version.test.ts new file mode 100644 index 0000000..72616e8 --- /dev/null +++ b/test/unit/java-version.test.ts @@ -0,0 +1,38 @@ +import {describe, expect, it} from 'vitest'; +import {type LoadEra, loadEra} from '../../src/internal/java-version'; + +describe('loadEra', () => { + it('maps Java versions to load eras', () => { + const eras: [number | string, LoadEra][] = [ + [6, 'jdk8'], + ['1.6.0_45', 'jdk8'], + [8, 'jdk8'], + ['1.8.0_392', 'jdk8'], + [9, 'jdk9'], + ['9.0.7.1', 'jdk9'], + ['10.0.2', 'jdk9'], + ['11.0.4', 'jdk9'], + [11, 'jdk11'], + ['11', 'jdk11'], + ['11.0', 'jdk11'], + ['11.0.5', 'jdk11'], + ['11.0.32.1', 'jdk11'], + [12, 'jdk9'], + [13, 'jdk13'], + ['17-ea', 'jdk13'], + ['21.0.2+13-LTS', 'jdk13'], + [99, 'jdk13'], + ['latest', 'jdk13'], + ]; + for (const [version, era] of eras) expect(loadEra(version), String(version)).toBe(era); + }); + + it('rejects invalid versions', () => { + for (const version of [null, true, {}]) { + expect(() => loadEra(version as unknown as number)).toThrow(TypeError); + } + for (const version of [5, 8.5, Number.NaN, '', 'abc', '1', '1.5.0', '5', '8.x', ' 8']) { + expect(() => loadEra(version), String(version)).toThrow(RangeError); + } + }); +}); diff --git a/test/unit/node-java-props.test.ts b/test/unit/node-java-props.test.ts index 4ed3f6c..4b1c7b2 100644 --- a/test/unit/node-java-props.test.ts +++ b/test/unit/node-java-props.test.ts @@ -47,12 +47,26 @@ describe('parseFile', () => { expect(await JavaProps.parseFile(bomFile)).toEqual({'\ufeffk': 'v'}); }); + it('accepts parse options', async () => { + const badEnd = path.join(tmpDir, 'bad-end.properties'); + fs.writeFileSync(badEnd, Buffer.from('k=é\\', 'latin1')); + expect(await JavaProps.parseFile(badEnd, 'latin1', {javaVersion: 8})).toEqual({k: 'é'}); + expect(await JavaProps.parseFile(badEnd, {encoding: 'latin1', javaVersion: 8})).toEqual({k: 'é'}); + expect(await JavaProps.parseFile(file, {javaVersion: 25})).toEqual(expected); + }); + it('rejects on read error', async () => { await expect(JavaProps.parseFile(path.join(FIXTURES_DIR, 'nonexistent.properties'))).rejects.toMatchObject({ code: 'ENOENT', }); }); + it('rejects on invalid options, without reading the file', async () => { + const missing = path.join(FIXTURES_DIR, 'nonexistent.properties'); + await expect(JavaProps.parseFile(missing, {javaVersion: 'nope'})).rejects.toThrow(RangeError); + await expect(JavaProps.parseFile(missing, 'utf8', {javaVersion: 5})).rejects.toThrow(RangeError); + }); + it('rejects on invalid encoding', async () => { await expect(JavaProps.parseFile(file, 'not-an-encoding')).rejects.toThrow(); }); diff --git a/test/unit/parse-cases.test.ts b/test/unit/parse-cases.test.ts index a1e28e4..5926808 100644 --- a/test/unit/parse-cases.test.ts +++ b/test/unit/parse-cases.test.ts @@ -1,10 +1,11 @@ // Runs the edge cases of test/cases/parse-cases.mts: their results are snapshotted in parse-snapshot.json, and compared -// to the results of real JVMs (parse-expected.json, generated by tools/gen-parse-expected.mts). +// to the results of real JVMs (parse-expected.json, generated by tools/gen-expected.mts). import {describe, expect, it} from 'vitest'; import {parse} from '../../src/java-props'; import cases from '../cases/parse-cases.mts'; import {readParseExpected} from '../support/data.mts'; -import {captureLoad, LOAD_ERAS, type LoadEra, type LoadResult} from '../support/load-result.mts'; +import {LOAD_ERAS, type LoadEra} from '../../src/internal/java-version'; +import {captureLoad, type LoadResult} from '../support/load-result.mts'; const expected = readParseExpected(); const results: Record = {}; @@ -24,6 +25,22 @@ const JAVA_DIVERGENCES: Record = { 'comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes', 'stale line buffer: lone backslash decode after comment', 'stale line buffer: truncated unicode after longer line', + 'stale line buffer: comment chars before a backslash (skipped since JDK 9)', + 'stale line buffer: comment ending with backslash (skipped since JDK 9)', + ], + jdk9: [ + 'lone continuation backslash: alone', + 'lone continuation backslash: alone with LF', + 'lone continuation backslash: after property', + 'lone continuation backslash: after property, with LF', + 'lone continuation backslash: indented', + 'comment ending with backslash, then backslashes: comment ending with backslash, then backslash-only line', + 'comment ending with backslash, then backslashes: bang comment, CRLF, lone backslash', + 'comment ending with backslash, then backslashes: comment with 3 backslashes, then backslashes', + 'stale line buffer: lone backslash decode after comment', + 'stale line buffer: truncated unicode after longer line', + 'stale line buffer: comment chars before a backslash (skipped since JDK 9)', + 'stale line buffer: comment ending with backslash (skipped since JDK 9)', ], jdk11: [ 'lone continuation backslash: alone', @@ -56,6 +73,17 @@ const JAVA_DIVERGENCES: Record = { ], }; +// A version of each era: parse() with this javaVersion must return exactly what Java returns +const ERA_VERSIONS: Record = {jdk8: 8, jdk9: 9, jdk11: '11.0.5', jdk13: 13}; + +// Same properties, or both throw (same message for IllegalArgumentException, Java's only expected exception) +const sameAsJava = (js: LoadResult, java: LoadResult) => { + if ('throws' in java) { + return 'throws' in js && (java.throws !== 'java.lang.IllegalArgumentException' || js.message === java.message); + } + return sameOutcome(js, java); +}; + const sameOutcome = (a: LoadResult, b: LoadResult) => { if ('throws' in a || 'throws' in b) return 'throws' in a && 'throws' in b && a.message === b.message; return JSON.stringify(Object.entries(a.props).sort()) === JSON.stringify(Object.entries(b.props).sort()); @@ -72,6 +100,19 @@ describe('parse cases', () => { }); for (const era of LOAD_ERAS) { + it(`match Java (${era}) with javaVersion: ${ERA_VERSIONS[era]}`, () => { + const mismatches = cases + .filter( + (c) => + !sameAsJava( + captureLoad(() => parse(c.input, {javaVersion: ERA_VERSIONS[era]})), + expected[c.name][era] + ) + ) + .map((c) => c.name); + expect(mismatches).toEqual([]); + }); + it(`only diverge from Java (${era}) on the known cases`, () => { const divergences = cases .filter((c) => !sameOutcome(results[c.name], expected[c.name][era])) diff --git a/test/unit/properties.test.ts b/test/unit/properties.test.ts index 722741b..e71dee4 100644 --- a/test/unit/properties.test.ts +++ b/test/unit/properties.test.ts @@ -2,16 +2,7 @@ import fc from 'fast-check'; import {describe, expect, it} from 'vitest'; import {parse, stringify} from '../../src/java-props'; - -// Pieces of .properties text that matter to the parser, and some that don't (backslash weighted) -const PIECES = [ - ...['\\', '\\', '\\', '\\u', '\\u00', '\\u0041', '\\\n', '\\\r\n'], - ...['\n', '\r', '\r\n', '\n#', '\n!', ' ', '\t', '\f', '=', ':', '#', '!'], - ...['a', 'k', 'v', 'u', '0', 'F', 'g', 'n', 't', 'é'], - ...['\u0000', '\u007F', '\u00A0', '\uFEFF', '\uD83D', '\uDC51'], -]; -const text = (maxPieces: number) => - fc.array(fc.constantFrom(...PIECES), {maxLength: maxPieces}).map((pieces) => pieces.join('')); +import {props, text} from '../support/arbitraries.mts'; describe('properties', () => { it('parse only throws on malformed \\uXXXX', () => { @@ -33,4 +24,14 @@ describe('properties', () => { }) ); }); + + it('stringify output is parsed back to the same properties, with every javaVersion', () => { + for (const javaVersion of [8, 9, '11.0.5', 13]) { + fc.assert( + fc.property(props, (p) => { + expect({...parse(stringify(p, {javaVersion}), {javaVersion})}).toEqual({...p}); + }) + ); + } + }); }); diff --git a/test/unit/read-entries.test.ts b/test/unit/read-entries.test.ts new file mode 100644 index 0000000..8d4222e --- /dev/null +++ b/test/unit/read-entries.test.ts @@ -0,0 +1,181 @@ +import fc from 'fast-check'; +import {describe, expect, it} from 'vitest'; +import {escapeKey, escapeValue, readEntries} from '../../src/java-props'; +import {decodeLine, encodeLine, rawParse} from '../../src/utils'; +import {text} from '../support/arbitraries.mts'; + +const entries = (str: string, javaVersion?: number) => { + const res: string[][] = []; + const reader = readEntries(str, {javaVersion}); + while (reader.next()) res.push([reader.rawKey, reader.rawValue, reader.key, reader.value]); + return res; +}; + +describe('readEntries', () => { + it('reads entries in order, duplicates included', () => { + expect(entries('b=1\n# c\na : 2\\\n 3\nb=\\u00e9')).toEqual([ + ['b', '1', 'b', '1'], + ['a', '23', 'a', '23'], + ['b', '\\u00e9', 'b', 'é'], + ]); + }); + + it('gives the logical line', () => { + const reader = readEntries('k = a\\\n b'); + expect(reader.next()).toBe(true); + expect(reader.line).toBe('k = ab'); + expect(reader.next()).toBe(false); + }); + + it('has no entry before next() and at the end', () => { + const reader = readEntries('k=v', {positions: true}); + expect([reader.line, reader.rawKey, reader.key, reader.value, reader.start, reader.startLine]).toEqual( + Array(6).fill(undefined) + ); + reader.next(); + reader.next(); + expect([reader.line, reader.rawKey, reader.rawValue, reader.key, reader.end, reader.startColumn]).toEqual( + Array(6).fill(undefined) + ); + }); + + it('decodes only on access', () => { + const reader = readEntries('k=\\u12\nk2=v'); + expect(reader.next()).toBe(true); + expect(reader.rawValue).toBe('\\u12'); + expect(() => reader.value).toThrow('Malformed \\uxxxx encoding.'); + expect(reader.next()).toBe(true); + expect(reader.value).toBe('v'); + }); + + it('decodes like Java, leftovers of its line buffer included (see parse-cases.mts)', () => { + // Java 8 to 12 read the "12" left by the first line, Java 13+ throws + const reader = readEntries('abcdef12\na=\\u00', {javaVersion: 8}); + reader.next(); + reader.next(); + expect(reader.value).toBe('\u0012'); + const reader13 = readEntries('abcdef12\na=\\u00', {javaVersion: 13}); + reader13.next(); + reader13.next(); + expect(() => reader13.value).toThrow('Malformed \\uxxxx encoding.'); + // Java 8 reads the comment's leftover "\\", Java 9 a never written '\0' + expect(entries('#a\\\n\\', 8)).toEqual([['\\', '', '\\', '']]); + expect(entries('#a\\\n\\', 9)).toEqual([['\\', '', '\0', '']]); + }); + + it('rejects an invalid javaVersion', () => { + expect(() => readEntries('', {javaVersion: 'x'})).toThrow(RangeError); + }); + + it('reads like the deprecated utils without javaVersion', () => { + const outcome = (fn: () => string[][]) => { + try { + return fn(); + } catch (err) { + return (err as Error).message; + } + }; + fc.assert( + fc.property(text(30), (input) => { + const deprecated = () => { + const res: string[][] = []; + rawParse(input, ({line, sepStart, valueStart}) => { + const [rawKey, rawValue] = [line.substring(0, sepStart), line.substring(valueStart)]; + res.push([rawKey, rawValue, decodeLine(rawKey), decodeLine(rawValue)]); + }); + return res; + }; + expect(outcome(() => entries(input))).toEqual(outcome(deprecated)); + }) + ); + }); +}); + +describe('readEntries positions', () => { + const positions = (str: string, javaVersion?: number | string) => { + const res: number[][] = []; + const reader = readEntries(str, {javaVersion, positions: true}); + while (reader.next()) res.push([reader.start, reader.end, reader.startLine, reader.startColumn]); + return res; + }; + + it('give the range, line and column of each entry', () => { + const input = '# c\r\n a=1\r\nb = 2\\\r\n 3\n\n\tc\rd'; + const expected = [ + [7, 10, 2, 3], + [12, 24, 3, 1], + [27, 28, 6, 2], + [29, 30, 7, 1], + ]; + for (const javaVersion of [undefined, 8, 9, '11.0.5', 13]) { + expect(positions(input, javaVersion), String(javaVersion)).toEqual(expected); + } + }); + + it('start an entry at its first char, after an empty continuation line', () => { + for (const javaVersion of [undefined, 8, 13]) { + expect(positions('\\\nk=v', javaVersion)).toEqual([[2, 5, 2, 1]]); + } + }); + + it('start an empty entry at its continuation backslash (Java only)', () => { + expect(positions('a=1\n \\', 8)).toEqual([ + [0, 3, 1, 1], + [6, 7, 2, 3], + ]); + expect(positions('a=1\n \\')).toEqual([[0, 3, 1, 1]]); + }); + + it('skip comments, even those written to Java 8 line buffer', () => { + for (const javaVersion of [undefined, 8, 9, 13]) { + expect(positions('#ab\\c\nk=v', javaVersion)).toEqual([[6, 9, 2, 1]]); + } + }); + + it('are consistent with the line', () => { + fc.assert( + fc.property(text(30), fc.constantFrom(undefined, 8, 9, '11.0.5', 13), (input, javaVersion) => { + const reader = readEntries(input, {javaVersion, positions: true}); + let previousEnd = -1; + while (reader.next()) { + const {start, end, line} = reader; + expect(start).toBeGreaterThan(previousEnd); + expect(end).toBeGreaterThan(start); + expect(end).toBeLessThanOrEqual(input.length); + if (line !== '') expect(input[start]).toBe(line[0]); + // startLine/startColumn: like a naive count + const before = input.substring(0, start).split(/\r\n|\r|\n/); + expect([reader.startLine, reader.startColumn]).toEqual([ + before.length, + before[before.length - 1].length + 1, + ]); + previousEnd = end; + } + }) + ); + }); +}); + +describe('escapeKey / escapeValue', () => { + it('escape like the deprecated encodeLine by default', () => { + fc.assert( + fc.property(text(8), (s) => { + expect(escapeKey(s)).toBe(encodeLine(s, true)); + expect(escapeValue(s)).toBe(encodeLine(s)); + }) + ); + }); + + it('escape like Properties.store() with javaVersion', () => { + expect(escapeKey('a=b é', {javaVersion: 17})).toBe('a\\=b\\ é'); + expect(escapeValue(' a=b é', {javaVersion: 17})).toBe('\\ a\\=b é'); + expect(escapeValue(' a=b é', {javaVersion: 17, escapeUnicode: true, escapeValueSpecials: false})).toBe( + '\\ a=b \\u00E9' + ); + }); + + it('reject invalid arguments', () => { + expect(() => escapeKey(1 as unknown as string)).toThrow(TypeError); + expect(() => escapeValue('', {javaVersion: 'x'})).toThrow(RangeError); + }); +}); diff --git a/test/unit/types.test-d.ts b/test/unit/types.test-d.ts index 53b170b..51a4a2a 100644 --- a/test/unit/types.test-d.ts +++ b/test/unit/types.test-d.ts @@ -1,6 +1,21 @@ // Public types (vitest --typecheck). Compatibility with old TypeScript versions: test/compat/check-types.mjs. import {describe, expectTypeOf, it} from 'vitest'; -import javaProps, {type Properties, parse, parseFile, stringify} from '../../src/node-java-props'; +import javaProps, { + type EntryPositions, + type EntryReader, + type EscapeOptions, + escapeKey, + escapeValue, + type ParseFileOptions, + type ParseOptions, + type Properties, + parse, + parseFile, + type ReadEntriesOptions, + readEntries, + type StringifyOptions, + stringify, +} from '../../src/node-java-props'; import { convertLine, decodeLine, @@ -11,14 +26,19 @@ import { rawParseLine, } from '../../src/utils'; +type Path = string | Buffer | URL | number; + describe('types', () => { it('main', () => { expectTypeOf().toEqualTypeOf<{[key: string]: string}>(); - expectTypeOf(parse).toEqualTypeOf<(str: string) => Properties>(); - expectTypeOf(stringify).toEqualTypeOf<(props: Properties) => string>(); - expectTypeOf(parseFile).toEqualTypeOf< - (path: string | Buffer | URL | number, encoding?: string) => Promise - >(); + expectTypeOf().toEqualTypeOf<{javaVersion?: number | string}>(); + expectTypeOf().toEqualTypeOf<{javaVersion?: number | string; encoding?: string}>(); + expectTypeOf(parse).toEqualTypeOf<(str: string, options?: ParseOptions) => Properties>(); + expectTypeOf(stringify).toEqualTypeOf<(props: Properties, options?: StringifyOptions) => string>(); + expectTypeOf(parseFile).toEqualTypeOf<{ + (path: Path, encoding?: string, options?: ParseOptions): Promise; + (path: Path, options?: ParseFileOptions): Promise; + }>(); expectTypeOf(javaProps).toEqualTypeOf<{ parse: typeof parse; parseFile: typeof parseFile; @@ -26,7 +46,49 @@ describe('types', () => { }>(); }); - it('utils', () => { + it('readEntries', () => { + expectTypeOf().toEqualTypeOf<{javaVersion?: number | string; positions?: boolean}>(); + expectTypeOf().toEqualTypeOf<{ + next(): boolean; + readonly line: string; + readonly rawKey: string; + readonly rawValue: string; + readonly key: string; + readonly value: string; + }>(); + expectTypeOf().toEqualTypeOf<{ + readonly start: number; + readonly end: number; + readonly startLine: number; + readonly startColumn: number; + }>(); + expectTypeOf(readEntries('')).toEqualTypeOf(); + expectTypeOf(readEntries('', {javaVersion: 8})).toEqualTypeOf(); + expectTypeOf(readEntries('', {positions: true})).toEqualTypeOf(); + }); + + it('escape and stringify options', () => { + expectTypeOf().toEqualTypeOf<{ + javaVersion?: number | string; + escapeValueSpecials?: boolean; + escapeUnicode?: boolean; + }>(); + expectTypeOf(escapeKey).toEqualTypeOf<(key: string, options?: EscapeOptions) => string>(); + expectTypeOf(escapeValue).toEqualTypeOf<(value: string, options?: EscapeOptions) => string>(); + expectTypeOf().toEqualTypeOf<{ + javaVersion?: number | string; + separator?: '=' | ':' | ' ' | ': ' | ' = '; + escapeValueSpecials?: boolean; + escapeUnicode?: boolean; + comments?: string; + date?: boolean | Date | string; + timeZone?: string; + lineSeparator?: '\n' | '\r\n' | '\r'; + order?: 'insertion' | 'sorted'; + }>(); + }); + + it('utils (deprecated: the 2.3.1 declarations, with a fixed readLine)', () => { expectTypeOf().toEqualTypeOf<{line: string; sepStart: number; valueStart: number}>(); expectTypeOf(rawParse).toEqualTypeOf<(str: string, consumeFn: (res: ParsedLine) => void) => void>(); expectTypeOf(rawParseLine).toEqualTypeOf<(line: string) => ParsedLine>(); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 5b4cbfb..434f841 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -109,6 +109,13 @@ describe('utils.encodeLine', () => { it('defaults to value encoding', () => { expect(encodeLine(' a=b')).toBe(encodeLine(' a=b', false)); }); + + it('throws a TypeError for anything but a string', () => { + for (const value of [undefined, null, 5]) { + expect(() => encodeLine(value as unknown as string)).toThrow(TypeError); + } + expect(encodeLine(new String('a b') as unknown as string, true)).toBe('a\\ b'); + }); }); describe('utils.LineReader', () => { diff --git a/tools/fuzz.mts b/tools/fuzz.mts new file mode 100644 index 0000000..1eaafdf --- /dev/null +++ b/tools/fuzz.mts @@ -0,0 +1,84 @@ +// Differential fuzzing of the built package (dist/) against real JVMs: +// - parse(input, {javaVersion}) vs Properties.load() of that JDK; +// - stringify(props, {javaVersion}) vs Properties.store() (Writer and OutputStream), date line excluded. +// +// Usage: node tools/fuzz.mts [--runs N] [--seed N] (needs a build: `pnpm run reference:fuzz` builds first) +// JDKs: JAVA_REFERENCE_JDKS, like tools/gen-expected.mts. +import {createRequire} from 'node:module'; +import fc from 'fast-check'; +import {longText, props, text} from '../test/support/arbitraries.mts'; +import {captureLoad, type LoadResult} from '../test/support/load-result.mts'; +import {jvmLoad, jvmStore, listJdks} from './java-reference/reference.mts'; + +const arg = (name: string, def: number) => { + const i = process.argv.indexOf(name); + return i >= 0 ? Number(process.argv[i + 1]) : def; +}; +const runs = arg('--runs', 20000); +const seed = arg('--seed', Date.now() % 1e9); +console.log(`runs: ${runs}, seed: ${seed}`); + +const {parse, stringify} = createRequire(import.meta.url)('../dist/node-java-props.js'); + +const inputs = fc.sample(fc.oneof({arbitrary: text(30), weight: 9}, {arbitrary: longText, weight: 1}), { + numRuns: runs, + seed, +}); +const stores = fc.sample(fc.record({props, comments: fc.option(text(8), {nil: undefined})}), { + numRuns: Math.ceil(runs / 4), + seed, +}); + +// Same properties, or both throw (same message for IllegalArgumentException) +const sameAsJava = (js: LoadResult, java: LoadResult) => { + if ('throws' in java) { + return 'throws' in js && (java.throws !== 'java.lang.IllegalArgumentException' || js.message === java.message); + } + return ( + !('throws' in js) && + JSON.stringify(Object.entries(js.props).sort()) === JSON.stringify(Object.entries(java.props).sort()) + ); +}; + +let failures = 0; +const report = (what: string, details: unknown) => { + if (++failures <= 10) console.error(`MISMATCH ${what}\n${JSON.stringify(details, null, 2)}`); +}; + +for (const jdk of listJdks()) { + const major = Number(jdk.version.replace(/^1\./, '').split(/\D/)[0]); + const before = failures; + + jvmLoad(jdk, inputs).forEach((java, i) => { + const js = captureLoad(() => parse(inputs[i], {javaVersion: jdk.version})); + if (!sameAsJava(js, java)) report(`parse (${jdk.name})`, {input: inputs[i], js, java}); + }); + + const requests = stores.flatMap((s) => [s, {...s, stream: true}, {props: {}, comments: s.comments}]); + const outputs = jvmStore(jdk, requests); + stores.forEach((s, i) => { + const dateLine = outputs[i * 3 + 2].split('\n').length - 2; + for (const [k, stream] of [ + [0, false], + [1, true], + ] as const) { + const java = outputs[i * 3 + k].split('\n').filter((_, n) => n !== dateLine); + const js = stringify(s.props, { + javaVersion: jdk.version, + comments: s.comments, + date: false, + lineSeparator: '\n', + escapeUnicode: stream, + }).split('\n'); + // Before Java 18, store() writes in hash order + const same = + major >= 18 + ? js.join('\n') === java.join('\n') + : JSON.stringify(js.sort()) === JSON.stringify(java.sort()); + if (!same) report(`stringify (${jdk.name}, ${stream ? 'OutputStream' : 'Writer'})`, {...s, js, java}); + } + }); + + console.log(`${jdk.name} (${jdk.version}): ${failures === before ? 'OK' : `${failures - before} mismatches`}`); +} +process.exit(failures ? 1 : 0); diff --git a/tools/gen-expected.mts b/tools/gen-expected.mts new file mode 100644 index 0000000..0c68770 --- /dev/null +++ b/tools/gen-expected.mts @@ -0,0 +1,149 @@ +// Generates the Java results of the test cases, by running real JVMs: +// - test/cases/parse-expected.json: Properties.load() for each case of parse-cases.mts (one key per era, see LOAD_ERAS +// in src/internal/java-version.ts); +// - test/cases/stringify-expected.json: Properties.store() (Java 18+, without the date line) and Date.toString() for +// the cases of stringify-cases.mts. +// Also checks the test/fixtures files against every JVM, and that every JVM writes the same store() lines. +// +// Usage: node tools/gen-expected.mts [--check] +// JDKs: JAVA_REFERENCE_JDKS="name=/path/to/bin/java,..." (default: `java` from PATH). Results without a matching JDK +// (load era, Java 18+) keep their committed values. +// --check: fail if a committed file is outdated, without writing it. +import fs from 'node:fs'; +import path from 'node:path'; +import {LOAD_ERAS, loadEra} from '../src/internal/java-version.ts'; +import cases from '../test/cases/parse-cases.mts'; +import {dateCases, stringifyCases} from '../test/cases/stringify-cases.mts'; +import { + fixtureFiles, + PARSE_EXPECTED_FILE, + readFixture, + readFixtureResult, + readParseExpected, + STRINGIFY_EXPECTED_FILE, + type StringifyExpected, +} from '../test/support/data.mts'; +import {type LoadResult, sortedProps} from '../test/support/load-result.mts'; +import {type Jdk, jvmDate, jvmLoad, jvmStore, listJdks} from './java-reference/reference.mts'; + +const ROOT = path.join(import.meta.dirname, '..'); +const check = process.argv.includes('--check'); + +const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b); +let failed = false; +const fail = (msg: string) => { + console.error(`ERROR: ${msg}`); + failed = true; +}; + +// Run every JDK, grouped by era +const jdks = listJdks(); +const byEra = new Map(); +const inputs = cases.map((c) => c.input); +const fixtures = fixtureFiles(); +const fixtureInputs = fixtures.map(readFixture); +for (const jdk of jdks) { + const era = loadEra(jdk.version); + console.log(`${jdk.name}: java.version=${jdk.version} -> era ${era}`); + const results = jvmLoad(jdk, [...inputs, ...fixtureInputs]); + if (!byEra.has(era)) byEra.set(era, []); + byEra.get(era)?.push({jdk, results}); +} + +// Fixtures: every JVM must match the committed .expected.json +fixtures.forEach((file, i) => { + const expected = {props: sortedProps(readFixtureResult(file))}; + for (const runs of byEra.values()) { + for (const {jdk, results} of runs) { + if (!same(results[inputs.length + i], expected)) fail(`fixture ${file}: ${jdk.name} result differs`); + } + } +}); + +// Cases +const previous: Record> = fs.existsSync(PARSE_EXPECTED_FILE) ? readParseExpected() : {}; +const expected: Record> = {}; +const names = new Set(); +cases.forEach((c, i) => { + if (names.has(c.name)) fail(`duplicate case name: ${c.name}`); + names.add(c.name); + const entry: Record = {input: c.input}; + for (const era of LOAD_ERAS) { + const runs = byEra.get(era); + if (!runs) { + if (previous[c.name]?.[era]) entry[era] = previous[c.name][era]; + continue; + } + const [first, ...others] = runs; + for (const other of others) { + if (!same(first.results[i], other.results[i])) { + fail(`${c.name}: ${first.jdk.name} and ${other.jdk.name} disagree (same era ${era}): new era?`); + } + } + entry[era] = first.results[i]; + } + expected[c.name] = entry; +}); + +for (const era of LOAD_ERAS) + if (!byEra.has(era)) console.warn(`WARNING: no JDK for era ${era}, keeping committed results`); +output(PARSE_EXPECTED_FILE, expected); + +// Stringify cases: store() of Java 18+ (sorted keys), date line removed +const major = (jdk: Jdk) => Number(jdk.version.replace(/^1\./, '').split(/\D/)[0]); +const storeJdks = jdks.filter((jdk) => major(jdk) >= 18).sort((a, b) => major(b) - major(a)); +if (storeJdks.length === 0) { + console.warn('WARNING: no JDK 18+, keeping committed stringify results'); +} else { + const storeExpected: StringifyExpected = {cases: {}, dates: {}}; + const lines = (out: string) => out.replace(/\r\n/g, '\n').split('\n'); + for (const jdk of [storeJdks[0], ...jdks.filter((j) => j !== storeJdks[0])]) { + const requests = stringifyCases.flatMap((c) => [ + {props: c.props, comments: c.comments}, + {props: c.props, comments: c.comments, stream: true}, + {props: {}, comments: c.comments}, // to find the date line + ]); + const outputs = jvmStore(jdk, requests); + stringifyCases.forEach((c, i) => { + const dateLine = lines(outputs[i * 3 + 2]).length - 2; + const [writer, stream] = [outputs[i * 3], outputs[i * 3 + 1]].map((out) => + lines(out) + .filter((_, n) => n !== dateLine) + .join('\n') + ); + if (jdk === storeJdks[0]) { + storeExpected.cases[c.name] = {props: c.props, comments: c.comments, writer, stream}; + } else { + // Same lines (in hash order before Java 18) + const ref = storeExpected.cases[c.name]; + const sameLines = (a: string, b: string) => same(a.split('\n').sort(), b.split('\n').sort()); + if (!sameLines(writer, ref.writer) || !sameLines(stream, ref.stream)) { + fail(`stringify ${c.name}: ${jdk.name} and ${storeJdks[0].name} write different lines`); + } + } + }); + } + // Time zone names change across Java versions: the newest JDK is the reference + const dates = jvmDate( + storeJdks[0], + dateCases.map((d) => ({time: d.time, timeZone: d.javaTimeZone})) + ); + dateCases.forEach((d, i) => { + storeExpected.dates[d.name] = {time: d.time, timeZone: d.timeZone, java: dates[i]}; + }); + output(STRINGIFY_EXPECTED_FILE, storeExpected); +} +process.exit(failed ? 1 : 0); + +function output(file: string, value: unknown) { + const json = `${JSON.stringify(value, null, 2)}\n`; + const name = path.relative(ROOT, file); + if (!check) { + fs.writeFileSync(file, json); + console.log(`written ${name}`); + } else if (fs.existsSync(file) && fs.readFileSync(file, 'utf8') === json) { + console.log(`${name} is up to date`); + } else { + fail(`${name} is outdated: run "pnpm run reference:gen"`); + } +} diff --git a/tools/gen-parse-expected.mts b/tools/gen-parse-expected.mts deleted file mode 100644 index 7a50a95..0000000 --- a/tools/gen-parse-expected.mts +++ /dev/null @@ -1,95 +0,0 @@ -// Generates test/cases/parse-expected.json: the result of each case of test/cases/parse-cases.mts with real JVMs -// (one key per era, see LOAD_ERAS in test/support/load-result.mts). -// Also checks the test/fixtures files against every JVM. -// -// Usage: node tools/gen-parse-expected.mts [--check] -// JDKs: JAVA_REFERENCE_JDKS="name=/path/to/bin/java,..." (default: `java` from PATH). Eras without a JDK keep -// their committed results. -// --check: fail if the committed file is outdated, without writing it. -import fs from 'node:fs'; -import path from 'node:path'; -import cases from '../test/cases/parse-cases.mts'; -import { - fixtureFiles, - PARSE_EXPECTED_FILE, - readFixture, - readFixtureResult, - readParseExpected, -} from '../test/support/data.mts'; -import {LOAD_ERAS, type LoadResult, sortedProps} from '../test/support/load-result.mts'; -import {type Jdk, jvmLoad, listJdks, loadEra} from './java-reference/reference.mts'; - -const ROOT = path.join(import.meta.dirname, '..'); -const check = process.argv.includes('--check'); - -const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b); -let failed = false; -const fail = (msg: string) => { - console.error(`ERROR: ${msg}`); - failed = true; -}; - -// Run every JDK, grouped by era -const jdks = listJdks(); -const byEra = new Map(); -const inputs = cases.map((c) => c.input); -const fixtures = fixtureFiles(); -const fixtureInputs = fixtures.map(readFixture); -for (const jdk of jdks) { - const era = loadEra(jdk.version); - console.log(`${jdk.name}: java.version=${jdk.version} -> era ${era}`); - const results = jvmLoad(jdk, [...inputs, ...fixtureInputs]); - if (!byEra.has(era)) byEra.set(era, []); - byEra.get(era)?.push({jdk, results}); -} - -// Fixtures: every JVM must match the committed .expected.json -fixtures.forEach((file, i) => { - const expected = {props: sortedProps(readFixtureResult(file))}; - for (const runs of byEra.values()) { - for (const {jdk, results} of runs) { - if (!same(results[inputs.length + i], expected)) fail(`fixture ${file}: ${jdk.name} result differs`); - } - } -}); - -// Cases -const previous: Record> = fs.existsSync(PARSE_EXPECTED_FILE) ? readParseExpected() : {}; -const expected: Record> = {}; -const names = new Set(); -cases.forEach((c, i) => { - if (names.has(c.name)) fail(`duplicate case name: ${c.name}`); - names.add(c.name); - const entry: Record = {input: c.input}; - for (const era of LOAD_ERAS) { - const runs = byEra.get(era); - if (!runs) { - if (previous[c.name]?.[era]) entry[era] = previous[c.name][era]; - continue; - } - const [first, ...others] = runs; - for (const other of others) { - if (!same(first.results[i], other.results[i])) { - fail(`${c.name}: ${first.jdk.name} and ${other.jdk.name} disagree (same era ${era}): new era?`); - } - } - entry[era] = first.results[i]; - } - expected[c.name] = entry; -}); - -for (const era of LOAD_ERAS) - if (!byEra.has(era)) console.warn(`WARNING: no JDK for era ${era}, keeping committed results`); - -const json = `${JSON.stringify(expected, null, 2)}\n`; -if (check) { - if (fs.existsSync(PARSE_EXPECTED_FILE) && fs.readFileSync(PARSE_EXPECTED_FILE, 'utf8') === json) { - console.log(`${path.relative(ROOT, PARSE_EXPECTED_FILE)} is up to date`); - } else { - fail(`${path.relative(ROOT, PARSE_EXPECTED_FILE)} is outdated: run "pnpm run reference:gen"`); - } -} else { - fs.writeFileSync(PARSE_EXPECTED_FILE, json); - console.log(`written ${path.relative(ROOT, PARSE_EXPECTED_FILE)} (${cases.length} cases)`); -} -process.exit(failed ? 1 : 0); diff --git a/tools/java-reference/JavaReference.java b/tools/java-reference/JavaReference.java index 99a3522..5e39256 100644 --- a/tools/java-reference/JavaReference.java +++ b/tools/java-reference/JavaReference.java @@ -1,9 +1,13 @@ import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.StringReader; +import java.io.StringWriter; +import java.util.Date; import java.util.Map; import java.util.Properties; +import java.util.TimeZone; import java.util.TreeMap; /** @@ -13,6 +17,9 @@ * units (4 digits per char), so that any char gets through (line terminators, lone surrogates...): * - "version" -> "" * - "load " -> "ok :,..." (sorted by key), or "throw " + * - "store :,..." -> "ok ": the output of + * store(Writer) or store(OutputStream) (decoded as ISO-8859-1), date line included + * - "date